Coverage Report

Created: 2025-06-22 06:59

/src/proj/src/iso19111/io.cpp
Line
Count
Source (jump to first uncovered line)
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.40M
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
33.9k
static inline std::string normalizeExponent(const std::string &in) {
581
33.9k
    return in;
582
33.9k
}
583
#endif
584
585
33.9k
static inline std::string normalizeSerializedString(const std::string &in) {
586
33.9k
    auto ret(normalizeExponent(in));
587
33.9k
    return ret;
588
33.9k
}
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
540k
static inline bool isNull(const WKTNodeNNPtr &node) {
901
540k
    return &node == &null_node;
902
540k
}
903
904
struct WKTNode::Private {
905
    std::string value_{};
906
    std::vector<WKTNodeNNPtr> children_{};
907
908
332k
    explicit Private(const std::string &valueIn) : value_(valueIn) {}
909
910
    // cppcheck-suppress functionStatic
911
3.73M
    inline const std::string &value() PROJ_PURE_DEFN { return value_; }
912
913
    // cppcheck-suppress functionStatic
914
192k
    inline const std::vector<WKTNodeNNPtr> &children() PROJ_PURE_DEFN {
915
192k
        return children_;
916
192k
    }
917
918
    // cppcheck-suppress functionStatic
919
37.8k
    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.96M
#define GP() getPrivate()
945
946
// ---------------------------------------------------------------------------
947
948
const WKTNodeNNPtr &
949
WKTNode::Private::lookForChild(const std::string &childName,
950
518
                               int occurrence) const noexcept {
951
518
    int occCount = 0;
952
3.72k
    for (const auto &child : children_) {
953
3.72k
        if (ci_equal(child->GP()->value(), childName)) {
954
733
            if (occurrence == occCount) {
955
518
                return child;
956
518
            }
957
215
            occCount++;
958
215
        }
959
3.72k
    }
960
0
    return null_node;
961
518
}
962
963
const WKTNodeNNPtr &
964
522k
WKTNode::Private::lookForChild(const std::string &name) const noexcept {
965
2.82M
    for (const auto &child : children_) {
966
2.82M
        const auto &v = child->GP()->value();
967
2.82M
        if (ci_equal(v, name)) {
968
5.93k
            return child;
969
5.93k
        }
970
2.82M
    }
971
516k
    return null_node;
972
522k
}
973
974
const WKTNodeNNPtr &
975
WKTNode::Private::lookForChild(const std::string &name,
976
14.1k
                               const std::string &name2) const noexcept {
977
60.4k
    for (const auto &child : children_) {
978
60.4k
        const auto &v = child->GP()->value();
979
60.4k
        if (ci_equal(v, name) || ci_equal(v, name2)) {
980
7.26k
            return child;
981
7.26k
        }
982
60.4k
    }
983
6.88k
    return null_node;
984
14.1k
}
985
986
const WKTNodeNNPtr &
987
WKTNode::Private::lookForChild(const std::string &name,
988
                               const std::string &name2,
989
4.76k
                               const std::string &name3) const noexcept {
990
21.2k
    for (const auto &child : children_) {
991
21.2k
        const auto &v = child->GP()->value();
992
21.2k
        if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) {
993
4.58k
            return child;
994
4.58k
        }
995
21.2k
    }
996
185
    return null_node;
997
4.76k
}
998
999
const WKTNodeNNPtr &WKTNode::Private::lookForChild(
1000
    const std::string &name, const std::string &name2, const std::string &name3,
1001
2.36k
    const std::string &name4) const noexcept {
1002
6.68k
    for (const auto &child : children_) {
1003
6.68k
        const auto &v = child->GP()->value();
1004
6.68k
        if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) ||
1005
6.68k
            ci_equal(v, name4)) {
1006
1.98k
            return child;
1007
1.98k
        }
1008
6.68k
    }
1009
380
    return null_node;
1010
2.36k
}
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
332k
    : d(std::make_unique<Private>(valueIn)) {}
1022
1023
// ---------------------------------------------------------------------------
1024
1025
//! @cond Doxygen_Suppress
1026
332k
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
321k
void WKTNode::addChild(WKTNodeNNPtr &&child) {
1036
321k
    d->children_.push_back(std::move(child));
1037
321k
}
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.94k
                                        int occurrence) const noexcept {
1049
3.94k
    int occCount = 0;
1050
45.1k
    for (const auto &child : d->children_) {
1051
45.1k
        if (ci_equal(child->GP()->value(), childName)) {
1052
160
            if (occurrence == occCount) {
1053
160
                return child;
1054
160
            }
1055
0
            occCount++;
1056
0
        }
1057
45.1k
    }
1058
3.78k
    return null_node;
1059
3.94k
}
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.2k
int WKTNode::countChildrenOfName(const std::string &childName) const noexcept {
1069
11.2k
    int occCount = 0;
1070
86.3k
    for (const auto &child : d->children_) {
1071
86.3k
        if (ci_equal(child->GP()->value(), childName)) {
1072
541
            occCount++;
1073
541
        }
1074
86.3k
    }
1075
11.2k
    return occCount;
1076
11.2k
}
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
1.11M
static size_t skipSpace(const std::string &str, size_t start) {
1096
1.11M
    size_t i = start;
1097
1.12M
    while (i < str.size() && ::isspace(static_cast<unsigned char>(str[i]))) {
1098
11.4k
        ++i;
1099
11.4k
    }
1100
1.11M
    return i;
1101
1.11M
}
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
332k
                                 int recLevel, size_t &indexEnd) {
1114
332k
    if (recLevel == 16) {
1115
1
        throw ParsingException("too many nesting levels");
1116
1
    }
1117
332k
    std::string value;
1118
332k
    size_t i = skipSpace(wkt, indexStart);
1119
332k
    if (i == wkt.size()) {
1120
0
        throw ParsingException("whitespace only string");
1121
0
    }
1122
332k
    std::string closingStringMarker;
1123
332k
    bool inString = false;
1124
1125
3.77M
    for (; i < wkt.size() &&
1126
3.77M
           (inString ||
1127
3.77M
            (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && wkt[i] != ']' &&
1128
3.33M
             wkt[i] != ')' && !::isspace(static_cast<unsigned char>(wkt[i]))));
1129
3.44M
         ++i) {
1130
3.44M
        if (wkt[i] == '"') {
1131
20.5k
            if (!inString) {
1132
9.70k
                inString = true;
1133
9.70k
                closingStringMarker = "\"";
1134
10.8k
            } else if (closingStringMarker == "\"") {
1135
10.6k
                if (i + 1 < wkt.size() && wkt[i + 1] == '"') {
1136
971
                    i++;
1137
9.68k
                } else {
1138
9.68k
                    inString = false;
1139
9.68k
                    closingStringMarker.clear();
1140
9.68k
                }
1141
10.6k
            }
1142
3.41M
        } else if (i + 3 <= wkt.size() &&
1143
3.41M
                   wkt.substr(i, 3) == startPrintedQuote) {
1144
84
            if (!inString) {
1145
32
                inString = true;
1146
32
                closingStringMarker = endPrintedQuote;
1147
32
                value += '"';
1148
32
                i += 2;
1149
32
                continue;
1150
32
            }
1151
3.41M
        } else if (i + 3 <= wkt.size() &&
1152
3.41M
                   closingStringMarker == endPrintedQuote &&
1153
3.41M
                   wkt.substr(i, 3) == endPrintedQuote) {
1154
0
            inString = false;
1155
0
            closingStringMarker.clear();
1156
0
            value += '"';
1157
0
            i += 2;
1158
0
            continue;
1159
0
        }
1160
3.44M
        value += wkt[i];
1161
3.44M
    }
1162
332k
    i = skipSpace(wkt, i);
1163
332k
    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
332k
    auto node = NN_NO_CHECK(std::make_unique<WKTNode>(value));
1172
1173
332k
    if (indexStart > 0) {
1174
322k
        if (wkt[i] == ',') {
1175
142k
            indexEnd = i + 1;
1176
142k
            return node;
1177
142k
        }
1178
179k
        if (wkt[i] == ']' || wkt[i] == ')') {
1179
87.8k
            indexEnd = i;
1180
87.8k
            return node;
1181
87.8k
        }
1182
179k
    }
1183
101k
    if (wkt[i] != '[' && wkt[i] != '(') {
1184
44
        throw ParsingException("missing [");
1185
44
    }
1186
101k
    ++i; // skip [
1187
101k
    i = skipSpace(wkt, i);
1188
424k
    while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') {
1189
322k
        size_t indexEndChild;
1190
322k
        node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild));
1191
322k
        assert(indexEndChild > i);
1192
322k
        i = indexEndChild;
1193
322k
        i = skipSpace(wkt, i);
1194
322k
        if (i < wkt.size() && wkt[i] == ',') {
1195
27.6k
            ++i;
1196
27.6k
            i = skipSpace(wkt, i);
1197
27.6k
        }
1198
322k
    }
1199
101k
    if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) {
1200
62
        throw ParsingException("missing ]");
1201
62
    }
1202
101k
    indexEnd = i + 1;
1203
101k
    return node;
1204
101k
}
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
19.2k
                        const std::string &rhs) const noexcept {
1262
19.2k
            return ci_less(lhs, rhs);
1263
19.2k
        }
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
9.93k
    Private() = default;
1281
9.93k
    ~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
1350
    CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node);
1351
1352
    static UnitOfMeasure
1353
    guessUnitForParameter(const std::string &paramName,
1354
                          const UnitOfMeasure &defaultLinearUnit,
1355
                          const UnitOfMeasure &defaultAngularUnit);
1356
1357
    void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged,
1358
                           std::vector<OperationParameterNNPtr> &parameters,
1359
                           std::vector<ParameterValueNNPtr> &values,
1360
                           const UnitOfMeasure &defaultLinearUnit,
1361
                           const UnitOfMeasure &defaultAngularUnit);
1362
1363
    static std::string getExtensionProj4(const WKTNode::Private *nodeP);
1364
1365
    static void addExtensionProj4ToProp(const WKTNode::Private *nodeP,
1366
                                        PropertyMap &props);
1367
1368
    ConversionNNPtr buildConversion(const WKTNodeNNPtr &node,
1369
                                    const UnitOfMeasure &defaultLinearUnit,
1370
                                    const UnitOfMeasure &defaultAngularUnit);
1371
1372
    static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode,
1373
                                      const WKTNodeNNPtr &projectionNode);
1374
1375
    static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
1376
                                              const char *paramName);
1377
1378
    ConversionNNPtr buildProjection(const GeodeticCRSNNPtr &baseGeodCRS,
1379
                                    const WKTNodeNNPtr &projCRSNode,
1380
                                    const WKTNodeNNPtr &projectionNode,
1381
                                    const UnitOfMeasure &defaultLinearUnit,
1382
                                    const UnitOfMeasure &defaultAngularUnit);
1383
1384
    ConversionNNPtr
1385
    buildProjectionStandard(const GeodeticCRSNNPtr &baseGeodCRS,
1386
                            const WKTNodeNNPtr &projCRSNode,
1387
                            const WKTNodeNNPtr &projectionNode,
1388
                            const UnitOfMeasure &defaultLinearUnit,
1389
                            const UnitOfMeasure &defaultAngularUnit);
1390
1391
    const ESRIMethodMapping *
1392
    getESRIMapping(const WKTNodeNNPtr &projCRSNode,
1393
                   const WKTNodeNNPtr &projectionNode,
1394
                   std::map<std::string, std::string, ci_less_struct>
1395
                       &mapParamNameToValue);
1396
1397
    static ConversionNNPtr
1398
    buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS,
1399
                            const WKTNodeNNPtr &projCRSNode,
1400
                            const WKTNodeNNPtr &projectionNode,
1401
                            const UnitOfMeasure &defaultLinearUnit,
1402
                            const UnitOfMeasure &defaultAngularUnit,
1403
                            const ESRIMethodMapping *esriMapping,
1404
                            std::map<std::string, std::string, ci_less_struct>
1405
                                &mapParamNameToValue);
1406
1407
    ConversionNNPtr
1408
    buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS,
1409
                            const WKTNodeNNPtr &projCRSNode,
1410
                            const WKTNodeNNPtr &projectionNode,
1411
                            const UnitOfMeasure &defaultLinearUnit,
1412
                            const UnitOfMeasure &defaultAngularUnit);
1413
1414
    ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node);
1415
1416
    VerticalReferenceFrameNNPtr
1417
    buildVerticalReferenceFrame(const WKTNodeNNPtr &node,
1418
                                const WKTNodeNNPtr &dynamicNode);
1419
1420
    TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node);
1421
1422
    EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node);
1423
1424
    ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node);
1425
1426
    CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node);
1427
1428
    DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node);
1429
1430
    CRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node);
1431
1432
    BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node);
1433
1434
    TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode);
1435
1436
    TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node);
1437
1438
    DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node);
1439
1440
    EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node);
1441
1442
    EngineeringCRSNNPtr
1443
    buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node);
1444
1445
    DerivedEngineeringCRSNNPtr
1446
    buildDerivedEngineeringCRS(const WKTNodeNNPtr &node);
1447
1448
    ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode);
1449
1450
    ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node);
1451
1452
    DerivedParametricCRSNNPtr
1453
    buildDerivedParametricCRS(const WKTNodeNNPtr &node);
1454
1455
    DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node);
1456
1457
    CRSPtr buildCRS(const WKTNodeNNPtr &node);
1458
1459
    TransformationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node);
1460
1461
    PointMotionOperationNNPtr
1462
    buildPointMotionOperation(const WKTNodeNNPtr &node);
1463
1464
    ConcatenatedOperationNNPtr
1465
    buildConcatenatedOperation(const WKTNodeNNPtr &node);
1466
1467
    CoordinateMetadataNNPtr buildCoordinateMetadata(const WKTNodeNNPtr &node);
1468
};
1469
//! @endcond
1470
1471
// ---------------------------------------------------------------------------
1472
1473
9.93k
WKTParser::WKTParser() : d(std::make_unique<Private>()) {}
1474
1475
// ---------------------------------------------------------------------------
1476
1477
//! @cond Doxygen_Suppress
1478
9.93k
WKTParser::~WKTParser() = default;
1479
//! @endcond
1480
1481
// ---------------------------------------------------------------------------
1482
1483
/** \brief Set whether parsing should be done in strict mode.
1484
 */
1485
9.93k
WKTParser &WKTParser::setStrict(bool strict) {
1486
9.93k
    d->strict_ = strict;
1487
9.93k
    return *this;
1488
9.93k
}
1489
1490
// ---------------------------------------------------------------------------
1491
1492
/** \brief Set whether object identifiers should be unset when there is
1493
 *         a contradiction between the definition from WKT and the one from
1494
 *         the database.
1495
 *
1496
 * At time of writing, this only applies to the base geographic CRS of a
1497
 * projected CRS, when comparing its coordinate system.
1498
 */
1499
0
WKTParser &WKTParser::setUnsetIdentifiersIfIncompatibleDef(bool unset) {
1500
0
    d->unsetIdentifiersIfIncompatibleDef_ = unset;
1501
0
    return *this;
1502
0
}
1503
1504
// ---------------------------------------------------------------------------
1505
1506
/** \brief Return the list of warnings found during parsing.
1507
 *
1508
 * \note The list might be non-empty only is setStrict(false) has been called.
1509
 */
1510
0
std::list<std::string> WKTParser::warningList() const {
1511
0
    return d->warningList_;
1512
0
}
1513
1514
// ---------------------------------------------------------------------------
1515
1516
/** \brief Return the list of grammar errors found during parsing.
1517
 *
1518
 * Grammar errors are non-compliance issues with respect to the WKT grammar.
1519
 *
1520
 * \note The list might be non-empty only is setStrict(false) has been called.
1521
 *
1522
 * @since PROJ 9.5
1523
 */
1524
0
std::list<std::string> WKTParser::grammarErrorList() const {
1525
0
    return d->grammarErrorList_;
1526
0
}
1527
1528
// ---------------------------------------------------------------------------
1529
1530
//! @cond Doxygen_Suppress
1531
9.39k
void WKTParser::Private::emitRecoverableWarning(const std::string &errorMsg) {
1532
9.39k
    if (strict_) {
1533
0
        throw ParsingException(errorMsg);
1534
9.39k
    } else {
1535
9.39k
        warningList_.push_back(errorMsg);
1536
9.39k
    }
1537
9.39k
}
1538
//! @endcond
1539
1540
// ---------------------------------------------------------------------------
1541
1542
//! @cond Doxygen_Suppress
1543
7.63k
void WKTParser::Private::emitGrammarError(const std::string &errorMsg) {
1544
7.63k
    if (strict_) {
1545
0
        throw ParsingException(errorMsg);
1546
7.63k
    } else {
1547
7.63k
        grammarErrorList_.push_back(errorMsg);
1548
7.63k
    }
1549
7.63k
}
1550
//! @endcond
1551
1552
// ---------------------------------------------------------------------------
1553
1554
28.2k
static double asDouble(const std::string &val) { return c_locale_stod(val); }
1555
1556
// ---------------------------------------------------------------------------
1557
1558
191
PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) {
1559
191
    throw ParsingException(
1560
191
        concat("not enough children in ", nodeName, " node"));
1561
191
}
1562
1563
// ---------------------------------------------------------------------------
1564
1565
PROJ_NO_RETURN static void
1566
18
ThrowNotRequiredNumberOfChildren(const std::string &nodeName) {
1567
18
    throw ParsingException(
1568
18
        concat("not required number of children in ", nodeName, " node"));
1569
18
}
1570
1571
// ---------------------------------------------------------------------------
1572
1573
208
PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) {
1574
208
    throw ParsingException(concat("missing ", nodeName, " node"));
1575
208
}
1576
1577
// ---------------------------------------------------------------------------
1578
1579
PROJ_NO_RETURN static void
1580
0
ThrowNotExpectedCSType(const std::string &expectedCSType) {
1581
0
    throw ParsingException(concat("CS node is not of type ", expectedCSType));
1582
0
}
1583
1584
// ---------------------------------------------------------------------------
1585
1586
static ParsingException buildRethrow(const char *funcName,
1587
81
                                     const std::exception &e) {
1588
81
    std::string res(funcName);
1589
81
    res += ": ";
1590
81
    res += e.what();
1591
81
    return ParsingException(res);
1592
81
}
1593
1594
// ---------------------------------------------------------------------------
1595
1596
//! @cond Doxygen_Suppress
1597
85.0k
std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) {
1598
85.0k
    return ::stripQuotes(node->GP()->value());
1599
85.0k
}
1600
1601
// ---------------------------------------------------------------------------
1602
1603
22.4k
double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) {
1604
22.4k
    return io::asDouble(node->GP()->value());
1605
22.4k
}
1606
1607
// ---------------------------------------------------------------------------
1608
1609
IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &parentNode,
1610
                                          const WKTNodeNNPtr &node,
1611
8.29k
                                          bool tolerant, bool removeInverseOf) {
1612
8.29k
    const auto *nodeP = node->GP();
1613
8.29k
    const auto &nodeChildren = nodeP->children();
1614
8.29k
    if (nodeChildren.size() >= 2) {
1615
7.71k
        auto codeSpace = stripQuotes(nodeChildren[0]);
1616
7.71k
        if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
1617
7.71k
            codeSpace.back() == ')') {
1618
0
            codeSpace = codeSpace.substr(strlen("INVERSE("));
1619
0
            codeSpace.resize(codeSpace.size() - 1);
1620
0
        }
1621
1622
7.71k
        PropertyMap propertiesId;
1623
7.71k
        if (nodeChildren.size() >= 3 &&
1624
7.71k
            nodeChildren[2]->GP()->childrenSize() == 0) {
1625
2.25k
            std::string version = stripQuotes(nodeChildren[2]);
1626
1627
            // IAU + 2015 -> IAU_2015
1628
2.25k
            if (dbContext_) {
1629
2.02k
                std::string codeSpaceOut;
1630
2.02k
                if (dbContext_->getVersionedAuthority(codeSpace, version,
1631
2.02k
                                                      codeSpaceOut)) {
1632
0
                    codeSpace = std::move(codeSpaceOut);
1633
0
                    version.clear();
1634
0
                }
1635
2.02k
            }
1636
1637
2.25k
            if (!version.empty()) {
1638
2.22k
                propertiesId.set(Identifier::VERSION_KEY, version);
1639
2.22k
            }
1640
2.25k
        }
1641
1642
7.71k
        auto code = stripQuotes(nodeChildren[1]);
1643
1644
        // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone
1645
        // south, we generated a wrong value. Auto-fix that
1646
7.71k
        const auto &parentNodeKeyword(parentNode->GP()->value());
1647
7.71k
        if (parentNodeKeyword == WKTConstants::CONVERSION &&
1648
7.71k
            codeSpace == Identifier::EPSG) {
1649
182
            const auto &parentNodeChildren = parentNode->GP()->children();
1650
182
            if (!parentNodeChildren.empty()) {
1651
182
                const auto parentNodeName(stripQuotes(parentNodeChildren[0]));
1652
182
                if (ci_starts_with(parentNodeName, "UTM Zone ") &&
1653
182
                    parentNodeName.find('S') != std::string::npos) {
1654
0
                    const int nZone =
1655
0
                        atoi(parentNodeName.c_str() + strlen("UTM Zone "));
1656
0
                    if (nZone >= 1 && nZone <= 60) {
1657
0
                        code = internal::toString(16100 + nZone);
1658
0
                    }
1659
0
                }
1660
182
            }
1661
182
        }
1662
1663
7.71k
        auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION);
1664
7.71k
        auto &uriNode = nodeP->lookForChild(WKTConstants::URI);
1665
1666
7.71k
        propertiesId.set(Identifier::CODESPACE_KEY, codeSpace);
1667
7.71k
        bool authoritySet = false;
1668
7.71k
        /*if (!isNull(citationNode))*/ {
1669
7.71k
            const auto *citationNodeP = citationNode->GP();
1670
7.71k
            if (citationNodeP->childrenSize() == 1) {
1671
2
                authoritySet = true;
1672
2
                propertiesId.set(Identifier::AUTHORITY_KEY,
1673
2
                                 stripQuotes(citationNodeP->children()[0]));
1674
2
            }
1675
7.71k
        }
1676
7.71k
        if (!authoritySet) {
1677
7.71k
            propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace);
1678
7.71k
        }
1679
7.71k
        /*if (!isNull(uriNode))*/ {
1680
7.71k
            const auto *uriNodeP = uriNode->GP();
1681
7.71k
            if (uriNodeP->childrenSize() == 1) {
1682
0
                propertiesId.set(Identifier::URI_KEY,
1683
0
                                 stripQuotes(uriNodeP->children()[0]));
1684
0
            }
1685
7.71k
        }
1686
7.71k
        return Identifier::create(code, propertiesId);
1687
7.71k
    } else if (strict_ || !tolerant) {
1688
1
        ThrowNotEnoughChildren(nodeP->value());
1689
573
    } else {
1690
573
        std::string msg("not enough children in ");
1691
573
        msg += nodeP->value();
1692
573
        msg += " node";
1693
573
        warningList_.emplace_back(std::move(msg));
1694
573
    }
1695
573
    return nullptr;
1696
8.29k
}
1697
1698
// ---------------------------------------------------------------------------
1699
1700
PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node,
1701
                                                 bool removeInverseOf,
1702
37.8k
                                                 bool hasName) {
1703
1704
37.8k
    if (properties_.size() >= MAX_PROPERTY_SIZE) {
1705
0
        throw ParsingException("MAX_PROPERTY_SIZE reached");
1706
0
    }
1707
37.8k
    properties_.push_back(std::make_unique<PropertyMap>());
1708
37.8k
    auto properties = properties_.back().get();
1709
1710
37.8k
    std::string authNameFromAlias;
1711
37.8k
    std::string codeFromAlias;
1712
37.8k
    const auto *nodeP = node->GP();
1713
37.8k
    const auto &nodeChildren = nodeP->children();
1714
1715
37.8k
    auto identifiers = ArrayOfBaseObject::create();
1716
184k
    for (const auto &subNode : nodeChildren) {
1717
184k
        const auto &subNodeName(subNode->GP()->value());
1718
184k
        if (ci_equal(subNodeName, WKTConstants::ID) ||
1719
184k
            ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
1720
6.80k
            auto id = buildId(node, subNode, true, removeInverseOf);
1721
6.80k
            if (id) {
1722
6.22k
                identifiers->add(NN_NO_CHECK(id));
1723
6.22k
            }
1724
6.80k
        }
1725
184k
    }
1726
1727
37.8k
    if (hasName && !nodeChildren.empty()) {
1728
37.0k
        const auto &nodeName(nodeP->value());
1729
37.0k
        auto name(stripQuotes(nodeChildren[0]));
1730
37.0k
        if (removeInverseOf && starts_with(name, "Inverse of ")) {
1731
0
            name = name.substr(strlen("Inverse of "));
1732
0
        }
1733
1734
37.0k
        if (ends_with(name, " (deprecated)")) {
1735
87
            name.resize(name.size() - strlen(" (deprecated)"));
1736
87
            properties->set(common::IdentifiedObject::DEPRECATED_KEY, true);
1737
87
        }
1738
1739
        // Oracle WKT can contain names like
1740
        // "Reseau Geodesique Francais 1993 (EPSG ID 6171)"
1741
        // for WKT attributes to the auth_name = "IGN - Paris"
1742
        // Strip that suffix from the name and assign a true EPSG code to the
1743
        // object
1744
37.0k
        if (identifiers->empty()) {
1745
33.8k
            const auto pos = name.find(" (EPSG ID ");
1746
33.8k
            if (pos != std::string::npos && name.back() == ')') {
1747
215
                const auto code =
1748
215
                    name.substr(pos + strlen(" (EPSG ID "),
1749
215
                                name.size() - 1 - pos - strlen(" (EPSG ID "));
1750
215
                name.resize(pos);
1751
1752
215
                PropertyMap propertiesId;
1753
215
                propertiesId.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
1754
215
                propertiesId.set(Identifier::AUTHORITY_KEY, Identifier::EPSG);
1755
215
                identifiers->add(Identifier::create(code, propertiesId));
1756
215
            }
1757
33.8k
        }
1758
1759
37.0k
        const char *tableNameForAlias = nullptr;
1760
37.0k
        if (ci_equal(nodeName, WKTConstants::GEOGCS)) {
1761
3.24k
            if (starts_with(name, "GCS_")) {
1762
379
                esriStyle_ = true;
1763
379
                if (name == "GCS_WGS_1984") {
1764
36
                    name = "WGS 84";
1765
343
                } else if (name == "GCS_unknown") {
1766
0
                    name = "unknown";
1767
343
                } else {
1768
343
                    tableNameForAlias = "geodetic_crs";
1769
343
                }
1770
379
            }
1771
33.7k
        } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) {
1772
65
            if (name == "WGS_1984") {
1773
36
                name = "WGS 84";
1774
36
                authNameFromAlias = Identifier::EPSG;
1775
36
                codeFromAlias = "7030";
1776
36
            } else {
1777
29
                tableNameForAlias = "ellipsoid";
1778
29
            }
1779
65
        }
1780
1781
37.0k
        if (dbContext_ && tableNameForAlias) {
1782
224
            std::string outTableName;
1783
224
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
1784
224
                                                        std::string());
1785
224
            auto officialName = authFactory->getOfficialNameFromAlias(
1786
224
                name, tableNameForAlias, "ESRI", false, outTableName,
1787
224
                authNameFromAlias, codeFromAlias);
1788
224
            if (!officialName.empty()) {
1789
23
                name = std::move(officialName);
1790
1791
                // Clearing authority for geodetic_crs because of
1792
                // potential axis order mismatch.
1793
23
                if (strcmp(tableNameForAlias, "geodetic_crs") == 0) {
1794
13
                    authNameFromAlias.clear();
1795
13
                    codeFromAlias.clear();
1796
13
                }
1797
23
            }
1798
224
        }
1799
1800
37.0k
        properties->set(IdentifiedObject::NAME_KEY, name);
1801
37.0k
    }
1802
1803
37.8k
    if (identifiers->empty() && !authNameFromAlias.empty()) {
1804
46
        identifiers->add(Identifier::create(
1805
46
            codeFromAlias,
1806
46
            PropertyMap()
1807
46
                .set(Identifier::CODESPACE_KEY, authNameFromAlias)
1808
46
                .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
1809
46
    }
1810
37.8k
    if (!identifiers->empty()) {
1811
3.39k
        properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
1812
3.39k
    }
1813
1814
37.8k
    auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK);
1815
37.8k
    if (!isNull(remarkNode)) {
1816
288
        const auto &remarkChildren = remarkNode->GP()->children();
1817
288
        if (remarkChildren.size() == 1) {
1818
283
            properties->set(IdentifiedObject::REMARKS_KEY,
1819
283
                            stripQuotes(remarkChildren[0]));
1820
283
        } else {
1821
5
            ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value());
1822
5
        }
1823
288
    }
1824
1825
37.8k
    ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
1826
184k
    for (const auto &subNode : nodeP->children()) {
1827
184k
        const auto &subNodeName(subNode->GP()->value());
1828
184k
        if (ci_equal(subNodeName, WKTConstants::USAGE)) {
1829
363
            auto objectDomain = buildObjectDomain(subNode);
1830
363
            if (!objectDomain) {
1831
8
                throw ParsingException(
1832
8
                    concat("missing children in ", subNodeName, " node"));
1833
8
            }
1834
355
            array->add(NN_NO_CHECK(objectDomain));
1835
355
        }
1836
184k
    }
1837
37.8k
    if (!array->empty()) {
1838
142
        properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
1839
37.6k
    } else {
1840
37.6k
        auto objectDomain = buildObjectDomain(node);
1841
37.6k
        if (objectDomain) {
1842
400
            properties->set(ObjectUsage::OBJECT_DOMAIN_KEY,
1843
400
                            NN_NO_CHECK(objectDomain));
1844
400
        }
1845
37.6k
    }
1846
1847
37.8k
    auto &versionNode = nodeP->lookForChild(WKTConstants::VERSION);
1848
37.8k
    if (!isNull(versionNode)) {
1849
144
        const auto &versionChildren = versionNode->GP()->children();
1850
144
        if (versionChildren.size() == 1) {
1851
140
            properties->set(CoordinateOperation::OPERATION_VERSION_KEY,
1852
140
                            stripQuotes(versionChildren[0]));
1853
140
        } else {
1854
4
            ThrowNotRequiredNumberOfChildren(versionNode->GP()->value());
1855
4
        }
1856
144
    }
1857
1858
37.7k
    return *properties;
1859
37.8k
}
1860
1861
// ---------------------------------------------------------------------------
1862
1863
ObjectDomainPtr
1864
38.0k
WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) {
1865
1866
38.0k
    const auto *nodeP = node->GP();
1867
38.0k
    auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE);
1868
38.0k
    auto &areaNode = nodeP->lookForChild(WKTConstants::AREA);
1869
38.0k
    auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX);
1870
38.0k
    auto &verticalExtentNode =
1871
38.0k
        nodeP->lookForChild(WKTConstants::VERTICALEXTENT);
1872
38.0k
    auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT);
1873
38.0k
    if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) ||
1874
38.0k
        !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) {
1875
764
        optional<std::string> scope;
1876
764
        const auto *scopeNodeP = scopeNode->GP();
1877
764
        const auto &scopeChildren = scopeNodeP->children();
1878
764
        if (scopeChildren.size() == 1) {
1879
12
            scope = stripQuotes(scopeChildren[0]);
1880
12
        }
1881
764
        ExtentPtr extent;
1882
764
        if (!isNull(areaNode) || !isNull(bboxNode)) {
1883
126
            util::optional<std::string> description;
1884
126
            std::vector<GeographicExtentNNPtr> geogExtent;
1885
126
            std::vector<VerticalExtentNNPtr> verticalExtent;
1886
126
            std::vector<TemporalExtentNNPtr> temporalExtent;
1887
126
            if (!isNull(areaNode)) {
1888
125
                const auto &areaChildren = areaNode->GP()->children();
1889
125
                if (areaChildren.size() == 1) {
1890
123
                    description = stripQuotes(areaChildren[0]);
1891
123
                } else {
1892
2
                    ThrowNotRequiredNumberOfChildren(areaNode->GP()->value());
1893
2
                }
1894
125
            }
1895
124
            if (!isNull(bboxNode)) {
1896
1
                const auto &bboxChildren = bboxNode->GP()->children();
1897
1
                if (bboxChildren.size() == 4) {
1898
0
                    double south, west, north, east;
1899
0
                    try {
1900
0
                        south = asDouble(bboxChildren[0]);
1901
0
                        west = asDouble(bboxChildren[1]);
1902
0
                        north = asDouble(bboxChildren[2]);
1903
0
                        east = asDouble(bboxChildren[3]);
1904
0
                    } catch (const std::exception &) {
1905
0
                        throw ParsingException(concat("not 4 double values in ",
1906
0
                                                      bboxNode->GP()->value(),
1907
0
                                                      " node"));
1908
0
                    }
1909
0
                    try {
1910
0
                        auto bbox = GeographicBoundingBox::create(west, south,
1911
0
                                                                  east, north);
1912
0
                        geogExtent.emplace_back(bbox);
1913
0
                    } catch (const std::exception &e) {
1914
0
                        throw ParsingException(concat("Invalid ",
1915
0
                                                      bboxNode->GP()->value(),
1916
0
                                                      " node: ") +
1917
0
                                               e.what());
1918
0
                    }
1919
1
                } else {
1920
1
                    ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value());
1921
1
                }
1922
1
            }
1923
1924
123
            if (!isNull(verticalExtentNode)) {
1925
3
                const auto &verticalExtentChildren =
1926
3
                    verticalExtentNode->GP()->children();
1927
3
                const auto verticalExtentChildrenSize =
1928
3
                    verticalExtentChildren.size();
1929
3
                if (verticalExtentChildrenSize == 2 ||
1930
3
                    verticalExtentChildrenSize == 3) {
1931
0
                    double min;
1932
0
                    double max;
1933
0
                    try {
1934
0
                        min = asDouble(verticalExtentChildren[0]);
1935
0
                        max = asDouble(verticalExtentChildren[1]);
1936
0
                    } catch (const std::exception &) {
1937
0
                        throw ParsingException(
1938
0
                            concat("not 2 double values in ",
1939
0
                                   verticalExtentNode->GP()->value(), " node"));
1940
0
                    }
1941
0
                    UnitOfMeasure unit = UnitOfMeasure::METRE;
1942
0
                    if (verticalExtentChildrenSize == 3) {
1943
0
                        unit = buildUnit(verticalExtentChildren[2],
1944
0
                                         UnitOfMeasure::Type::LINEAR);
1945
0
                    }
1946
0
                    verticalExtent.emplace_back(VerticalExtent::create(
1947
0
                        min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
1948
3
                } else {
1949
3
                    ThrowNotRequiredNumberOfChildren(
1950
3
                        verticalExtentNode->GP()->value());
1951
3
                }
1952
3
            }
1953
1954
120
            if (!isNull(temporalExtentNode)) {
1955
67
                const auto &temporalExtentChildren =
1956
67
                    temporalExtentNode->GP()->children();
1957
67
                if (temporalExtentChildren.size() == 2) {
1958
64
                    temporalExtent.emplace_back(TemporalExtent::create(
1959
64
                        stripQuotes(temporalExtentChildren[0]),
1960
64
                        stripQuotes(temporalExtentChildren[1])));
1961
64
                } else {
1962
3
                    ThrowNotRequiredNumberOfChildren(
1963
3
                        temporalExtentNode->GP()->value());
1964
3
                }
1965
67
            }
1966
117
            extent = Extent::create(description, geogExtent, verticalExtent,
1967
117
                                    temporalExtent)
1968
117
                         .as_nullable();
1969
117
        }
1970
755
        return ObjectDomain::create(scope, extent).as_nullable();
1971
764
    }
1972
1973
37.2k
    return nullptr;
1974
38.0k
}
1975
1976
// ---------------------------------------------------------------------------
1977
1978
UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node,
1979
1.68k
                                            UnitOfMeasure::Type type) {
1980
1.68k
    const auto *nodeP = node->GP();
1981
1.68k
    const auto &children = nodeP->children();
1982
1.68k
    if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) ||
1983
1.68k
        (type == UnitOfMeasure::Type::TIME && children.size() < 1)) {
1984
9
        ThrowNotEnoughChildren(nodeP->value());
1985
9
    }
1986
1.67k
    try {
1987
1.67k
        std::string unitName(stripQuotes(children[0]));
1988
1.67k
        PropertyMap properties(buildProperties(node));
1989
1.67k
        auto &idNode =
1990
1.67k
            nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY);
1991
1.67k
        if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) {
1992
12
            emitRecoverableWarning("not enough children in " +
1993
12
                                   idNode->GP()->value() + " node");
1994
12
        }
1995
1.67k
        const bool hasValidIdNode =
1996
1.67k
            !isNull(idNode) && idNode->GP()->childrenSize() >= 2;
1997
1998
1.67k
        const auto &idNodeChildren(idNode->GP()->children());
1999
1.67k
        std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0])
2000
1.67k
                                             : std::string());
2001
1.67k
        std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1])
2002
1.67k
                                        : std::string());
2003
2004
1.67k
        bool queryDb = true;
2005
1.67k
        if (type == UnitOfMeasure::Type::UNKNOWN) {
2006
40
            if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) {
2007
0
                type = UnitOfMeasure::Type::LINEAR;
2008
0
                unitName = "metre";
2009
0
                if (codeSpace.empty()) {
2010
0
                    codeSpace = Identifier::EPSG;
2011
0
                    code = "9001";
2012
0
                    queryDb = false;
2013
0
                }
2014
40
            } else if (ci_equal(unitName, "DEGREE") ||
2015
40
                       ci_equal(unitName, "GRAD")) {
2016
0
                type = UnitOfMeasure::Type::ANGULAR;
2017
0
            }
2018
40
        }
2019
2020
1.67k
        if (esriStyle_ && dbContext_ && queryDb) {
2021
487
            std::string outTableName;
2022
487
            std::string authNameFromAlias;
2023
487
            std::string codeFromAlias;
2024
487
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2025
487
                                                        std::string());
2026
487
            auto officialName = authFactory->getOfficialNameFromAlias(
2027
487
                unitName, "unit_of_measure", "ESRI", false, outTableName,
2028
487
                authNameFromAlias, codeFromAlias);
2029
487
            if (!officialName.empty()) {
2030
124
                unitName = std::move(officialName);
2031
124
                codeSpace = std::move(authNameFromAlias);
2032
124
                code = std::move(codeFromAlias);
2033
124
            }
2034
487
        }
2035
2036
1.67k
        double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0;
2037
1.67k
        constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
2038
1.67k
        constexpr double REL_ERROR = 1e-10;
2039
        // Fix common rounding errors
2040
1.67k
        if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) <
2041
1.67k
            REL_ERROR * convFactor) {
2042
134
            convFactor = UnitOfMeasure::DEGREE.conversionToSI();
2043
1.54k
        } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) <
2044
1.54k
                   REL_ERROR * convFactor) {
2045
2
            convFactor = US_FOOT_CONV_FACTOR;
2046
2
        }
2047
2048
1.67k
        return UnitOfMeasure(unitName, convFactor, type, codeSpace, code);
2049
1.67k
    } catch (const std::exception &e) {
2050
14
        throw buildRethrow(__FUNCTION__, e);
2051
14
    }
2052
1.67k
}
2053
2054
// ---------------------------------------------------------------------------
2055
2056
// node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node
2057
UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node,
2058
24.7k
                                                     UnitOfMeasure::Type type) {
2059
24.7k
    const auto *nodeP = node->GP();
2060
24.7k
    {
2061
24.7k
        auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT);
2062
24.7k
        if (!isNull(unitNode)) {
2063
17
            return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR);
2064
17
        }
2065
24.7k
    }
2066
2067
24.6k
    {
2068
24.6k
        auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT);
2069
24.6k
        if (!isNull(unitNode)) {
2070
2
            return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR);
2071
2
        }
2072
24.6k
    }
2073
2074
24.6k
    {
2075
24.6k
        auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT);
2076
24.6k
        if (!isNull(unitNode)) {
2077
1
            return buildUnit(unitNode, UnitOfMeasure::Type::SCALE);
2078
1
        }
2079
24.6k
    }
2080
2081
24.6k
    {
2082
24.6k
        auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT);
2083
24.6k
        if (!isNull(unitNode)) {
2084
0
            return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
2085
0
        }
2086
24.6k
    }
2087
24.6k
    {
2088
24.6k
        auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY);
2089
24.6k
        if (!isNull(unitNode)) {
2090
1
            return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
2091
1
        }
2092
24.6k
    }
2093
2094
24.6k
    {
2095
24.6k
        auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT);
2096
24.6k
        if (!isNull(unitNode)) {
2097
3
            return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC);
2098
3
        }
2099
24.6k
    }
2100
2101
24.6k
    {
2102
24.6k
        auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT);
2103
24.6k
        if (!isNull(unitNode)) {
2104
1.62k
            return buildUnit(unitNode, type);
2105
1.62k
        }
2106
24.6k
    }
2107
2108
23.0k
    return UnitOfMeasure::NONE;
2109
24.6k
}
2110
2111
// ---------------------------------------------------------------------------
2112
2113
5.57k
EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) {
2114
5.57k
    const auto *nodeP = node->GP();
2115
5.57k
    const auto &children = nodeP->children();
2116
5.57k
    if (children.size() < 3) {
2117
19
        ThrowNotEnoughChildren(nodeP->value());
2118
19
    }
2119
5.55k
    try {
2120
5.55k
        UnitOfMeasure unit =
2121
5.55k
            buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR);
2122
5.55k
        if (unit == UnitOfMeasure::NONE) {
2123
5.08k
            unit = UnitOfMeasure::METRE;
2124
5.08k
        }
2125
5.55k
        Length semiMajorAxis(asDouble(children[1]), unit);
2126
        // Some WKT in the wild use "inf". Cf SPHEROID["unnamed",6370997,"inf"]
2127
        // in https://zenodo.org/record/3878979#.Y_P4g4CZNH4,
2128
        // https://zenodo.org/record/5831940#.Y_P4i4CZNH5
2129
        // or https://grasswiki.osgeo.org/wiki/Marine_Science
2130
5.55k
        const auto &invFlatteningChild = children[2];
2131
5.55k
        if (invFlatteningChild->GP()->value() == "\"inf\"") {
2132
0
            emitRecoverableWarning("Inverse flattening = \"inf\" is not "
2133
0
                                   "conformant, but understood");
2134
0
        }
2135
5.55k
        Scale invFlattening(invFlatteningChild->GP()->value() == "\"inf\""
2136
5.55k
                                ? 0
2137
5.55k
                                : asDouble(invFlatteningChild));
2138
5.55k
        const auto ellpsProperties = buildProperties(node);
2139
5.55k
        std::string ellpsName;
2140
5.55k
        ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName);
2141
5.55k
        const auto celestialBody(Ellipsoid::guessBodyName(
2142
5.55k
            dbContext_, semiMajorAxis.getSIValue(), ellpsName));
2143
5.55k
        if (invFlattening.getSIValue() == 0) {
2144
3.14k
            return Ellipsoid::createSphere(ellpsProperties, semiMajorAxis,
2145
3.14k
                                           celestialBody);
2146
3.14k
        } else {
2147
2.41k
            return Ellipsoid::createFlattenedSphere(
2148
2.41k
                ellpsProperties, semiMajorAxis, invFlattening, celestialBody);
2149
2.41k
        }
2150
5.55k
    } catch (const std::exception &e) {
2151
51
        throw buildRethrow(__FUNCTION__, e);
2152
51
    }
2153
5.55k
}
2154
2155
// ---------------------------------------------------------------------------
2156
2157
PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian(
2158
598
    const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) {
2159
598
    const auto *nodeP = node->GP();
2160
598
    const auto &children = nodeP->children();
2161
598
    if (children.size() < 2) {
2162
3
        ThrowNotEnoughChildren(nodeP->value());
2163
3
    }
2164
595
    auto name = stripQuotes(children[0]);
2165
595
    UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2166
595
    if (unit == UnitOfMeasure::NONE) {
2167
594
        unit = defaultAngularUnit;
2168
594
        if (unit == UnitOfMeasure::NONE) {
2169
524
            unit = UnitOfMeasure::DEGREE;
2170
524
        }
2171
594
    }
2172
595
    try {
2173
595
        double angleValue = asDouble(children[1]);
2174
2175
        // Correct for GDAL WKT1 and WKT1-ESRI departure
2176
595
        if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 &&
2177
595
            unit._isEquivalentTo(UnitOfMeasure::GRAD,
2178
0
                                 util::IComparable::Criterion::EQUIVALENT)) {
2179
0
            angleValue = 2.5969213;
2180
595
        } else {
2181
595
            static const struct {
2182
595
                const char *name;
2183
595
                int deg;
2184
595
                int min;
2185
595
                double sec;
2186
595
            } primeMeridiansDMS[] = {
2187
595
                {"Lisbon", -9, 7, 54.862},  {"Bogota", -74, 4, 51.3},
2188
595
                {"Madrid", -3, 41, 14.55},  {"Rome", 12, 27, 8.4},
2189
595
                {"Bern", 7, 26, 22.5},      {"Jakarta", 106, 48, 27.79},
2190
595
                {"Ferro", -17, 40, 0},      {"Brussels", 4, 22, 4.71},
2191
595
                {"Stockholm", 18, 3, 29.8}, {"Athens", 23, 42, 58.815},
2192
595
                {"Oslo", 10, 43, 22.5},     {"Paris RGS", 2, 20, 13.95},
2193
595
                {"Paris_RGS", 2, 20, 13.95}};
2194
2195
            // Current epsg.org output may use the EPSG:9110 "sexagesimal DMS"
2196
            // unit and a DD.MMSSsss value, but this will likely be changed to
2197
            // use decimal degree.
2198
            // Or WKT1 may for example use the Paris RGS decimal degree value
2199
            // but with a GEOGCS with UNIT["Grad"]
2200
6.77k
            for (const auto &pmDef : primeMeridiansDMS) {
2201
6.77k
                if (name == pmDef.name) {
2202
255
                    double dmsAsDecimalValue =
2203
255
                        (pmDef.deg >= 0 ? 1 : -1) *
2204
255
                        (std::abs(pmDef.deg) + pmDef.min / 100. +
2205
255
                         pmDef.sec / 10000.);
2206
255
                    double dmsAsDecimalDegreeValue =
2207
255
                        (pmDef.deg >= 0 ? 1 : -1) *
2208
255
                        (std::abs(pmDef.deg) + pmDef.min / 60. +
2209
255
                         pmDef.sec / 3600.);
2210
255
                    if (std::fabs(angleValue - dmsAsDecimalValue) < 1e-8 ||
2211
255
                        std::fabs(angleValue - dmsAsDecimalDegreeValue) <
2212
255
                            1e-8) {
2213
0
                        angleValue = dmsAsDecimalDegreeValue;
2214
0
                        unit = UnitOfMeasure::DEGREE;
2215
0
                    }
2216
255
                    break;
2217
255
                }
2218
6.77k
            }
2219
595
        }
2220
2221
595
        auto &properties = buildProperties(node);
2222
595
        if (dbContext_ && esriStyle_) {
2223
374
            std::string outTableName;
2224
374
            std::string codeFromAlias;
2225
374
            std::string authNameFromAlias;
2226
374
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2227
374
                                                        std::string());
2228
374
            auto officialName = authFactory->getOfficialNameFromAlias(
2229
374
                name, "prime_meridian", "ESRI", false, outTableName,
2230
374
                authNameFromAlias, codeFromAlias);
2231
374
            if (!officialName.empty()) {
2232
0
                properties.set(IdentifiedObject::NAME_KEY, officialName);
2233
0
                if (!authNameFromAlias.empty()) {
2234
0
                    auto identifiers = ArrayOfBaseObject::create();
2235
0
                    identifiers->add(Identifier::create(
2236
0
                        codeFromAlias,
2237
0
                        PropertyMap()
2238
0
                            .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2239
0
                            .set(Identifier::AUTHORITY_KEY,
2240
0
                                 authNameFromAlias)));
2241
0
                    properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2242
0
                                   identifiers);
2243
0
                }
2244
0
            }
2245
374
        }
2246
2247
595
        Angle angle(angleValue, unit);
2248
595
        return PrimeMeridian::create(properties, angle);
2249
595
    } catch (const std::exception &e) {
2250
3
        throw buildRethrow(__FUNCTION__, e);
2251
3
    }
2252
595
}
2253
2254
// ---------------------------------------------------------------------------
2255
2256
6.38k
optional<std::string> WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) {
2257
2258
6.38k
    auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR);
2259
6.38k
    if (anchorNode->GP()->childrenSize() == 1) {
2260
405
        return optional<std::string>(
2261
405
            stripQuotes(anchorNode->GP()->children()[0]));
2262
405
    }
2263
5.97k
    return optional<std::string>();
2264
6.38k
}
2265
2266
// ---------------------------------------------------------------------------
2267
2268
optional<common::Measure>
2269
5.94k
WKTParser::Private::getAnchorEpoch(const WKTNodeNNPtr &node) {
2270
2271
5.94k
    auto &anchorEpochNode = node->GP()->lookForChild(WKTConstants::ANCHOREPOCH);
2272
5.94k
    if (anchorEpochNode->GP()->childrenSize() == 1) {
2273
0
        try {
2274
0
            double value = asDouble(anchorEpochNode->GP()->children()[0]);
2275
0
            return optional<common::Measure>(
2276
0
                common::Measure(value, common::UnitOfMeasure::YEAR));
2277
0
        } catch (const std::exception &e) {
2278
0
            throw buildRethrow(__FUNCTION__, e);
2279
0
        }
2280
0
    }
2281
5.94k
    return optional<common::Measure>();
2282
5.94k
}
2283
// ---------------------------------------------------------------------------
2284
2285
static const PrimeMeridianNNPtr &
2286
fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid,
2287
10.0k
                  const PrimeMeridianNNPtr &pm) {
2288
10.0k
    return (ellipsoid->celestialBody() != Ellipsoid::EARTH &&
2289
10.0k
            pm.get() == PrimeMeridian::GREENWICH.get())
2290
10.0k
               ? PrimeMeridian::REFERENCE_MERIDIAN
2291
10.0k
               : pm;
2292
10.0k
}
2293
2294
// ---------------------------------------------------------------------------
2295
2296
GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame(
2297
    const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian,
2298
3.82k
    const WKTNodeNNPtr &dynamicNode) {
2299
3.82k
    const auto *nodeP = node->GP();
2300
3.82k
    auto &ellipsoidNode =
2301
3.82k
        nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2302
3.82k
    if (isNull(ellipsoidNode)) {
2303
48
        ThrowMissing(WKTConstants::ELLIPSOID);
2304
48
    }
2305
3.78k
    auto &properties = buildProperties(node);
2306
2307
    // do that before buildEllipsoid() so that esriStyle_ can be set
2308
3.78k
    auto name = stripQuotes(nodeP->children()[0]);
2309
2310
3.78k
    const auto identifyFromName = [&](const std::string &l_name) {
2311
1.85k
        if (dbContext_) {
2312
1.72k
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2313
1.72k
                                                        std::string());
2314
1.72k
            auto res = authFactory->createObjectsFromName(
2315
1.72k
                l_name,
2316
1.72k
                {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true,
2317
1.72k
                1);
2318
1.72k
            if (!res.empty()) {
2319
560
                bool foundDatumName = false;
2320
560
                const auto &refDatum = res.front();
2321
560
                if (metadata::Identifier::isEquivalentName(
2322
560
                        l_name.c_str(), refDatum->nameStr().c_str())) {
2323
2
                    foundDatumName = true;
2324
558
                } else if (refDatum->identifiers().size() == 1) {
2325
558
                    const auto &id = refDatum->identifiers()[0];
2326
558
                    const auto aliases =
2327
558
                        authFactory->databaseContext()->getAliases(
2328
558
                            *id->codeSpace(), id->code(), refDatum->nameStr(),
2329
558
                            "geodetic_datum", std::string());
2330
1.08k
                    for (const auto &alias : aliases) {
2331
1.08k
                        if (metadata::Identifier::isEquivalentName(
2332
1.08k
                                l_name.c_str(), alias.c_str())) {
2333
64
                            foundDatumName = true;
2334
64
                            break;
2335
64
                        }
2336
1.08k
                    }
2337
558
                }
2338
560
                if (foundDatumName) {
2339
66
                    properties.set(IdentifiedObject::NAME_KEY,
2340
66
                                   refDatum->nameStr());
2341
66
                    if (!properties.get(Identifier::CODESPACE_KEY) &&
2342
66
                        refDatum->identifiers().size() == 1) {
2343
66
                        const auto &id = refDatum->identifiers()[0];
2344
66
                        auto identifiers = ArrayOfBaseObject::create();
2345
66
                        identifiers->add(Identifier::create(
2346
66
                            id->code(), PropertyMap()
2347
66
                                            .set(Identifier::CODESPACE_KEY,
2348
66
                                                 *id->codeSpace())
2349
66
                                            .set(Identifier::AUTHORITY_KEY,
2350
66
                                                 *id->codeSpace())));
2351
66
                        properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2352
66
                                       identifiers);
2353
66
                    }
2354
66
                    return true;
2355
66
                }
2356
1.16k
            } else {
2357
                // Get official name from database if AUTHORITY is present
2358
1.16k
                auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY);
2359
1.16k
                if (!isNull(idNode)) {
2360
0
                    try {
2361
0
                        auto id = buildId(node, idNode, false, false);
2362
0
                        auto authFactory2 = AuthorityFactory::create(
2363
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
2364
0
                        auto dbDatum =
2365
0
                            authFactory2->createGeodeticDatum(id->code());
2366
0
                        properties.set(IdentifiedObject::NAME_KEY,
2367
0
                                       dbDatum->nameStr());
2368
0
                        return true;
2369
0
                    } catch (const std::exception &) {
2370
0
                    }
2371
0
                }
2372
1.16k
            }
2373
1.72k
        }
2374
1.78k
        return false;
2375
1.85k
    };
2376
2377
    // Remap GDAL WGS_1984 to EPSG v9 "World Geodetic System 1984" official
2378
    // name.
2379
    // Also remap EPSG v10 datum ensemble names to non-ensemble EPSG v9
2380
3.78k
    bool nameSet = false;
2381
3.78k
    if (name == "WGS_1984" || name == "World Geodetic System 1984 ensemble") {
2382
48
        nameSet = true;
2383
48
        properties.set(IdentifiedObject::NAME_KEY,
2384
48
                       GeodeticReferenceFrame::EPSG_6326->nameStr());
2385
3.73k
    } else if (name == "European Terrestrial Reference System 1989 ensemble") {
2386
0
        nameSet = true;
2387
0
        properties.set(IdentifiedObject::NAME_KEY,
2388
0
                       "European Terrestrial Reference System 1989");
2389
0
    }
2390
2391
    // If we got hints this might be a ESRI WKT, then check in the DB to
2392
    // confirm
2393
3.78k
    std::string officialName;
2394
3.78k
    std::string authNameFromAlias;
2395
3.78k
    std::string codeFromAlias;
2396
3.78k
    if (!nameSet && maybeEsriStyle_ && dbContext_ &&
2397
3.78k
        !(starts_with(name, "D_") || esriStyle_)) {
2398
107
        std::string outTableName;
2399
107
        auto authFactory =
2400
107
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
2401
107
        officialName = authFactory->getOfficialNameFromAlias(
2402
107
            name, "geodetic_datum", "ESRI", false, outTableName,
2403
107
            authNameFromAlias, codeFromAlias);
2404
107
        if (!officialName.empty()) {
2405
0
            maybeEsriStyle_ = false;
2406
0
            esriStyle_ = true;
2407
0
        }
2408
107
    }
2409
2410
3.78k
    if (!nameSet && (starts_with(name, "D_") || esriStyle_)) {
2411
2.54k
        esriStyle_ = true;
2412
2.54k
        const char *tableNameForAlias = nullptr;
2413
2.54k
        if (name == "D_WGS_1984") {
2414
68
            name = "World Geodetic System 1984";
2415
68
            authNameFromAlias = Identifier::EPSG;
2416
68
            codeFromAlias = "6326";
2417
2.47k
        } else if (name == "D_ETRS_1989") {
2418
0
            name = "European Terrestrial Reference System 1989";
2419
0
            authNameFromAlias = Identifier::EPSG;
2420
0
            codeFromAlias = "6258";
2421
2.47k
        } else if (name == "D_unknown") {
2422
201
            name = "unknown";
2423
2.27k
        } else if (name == "D_Unknown_based_on_WGS_84_ellipsoid") {
2424
0
            name = "Unknown based on WGS 84 ellipsoid";
2425
2.27k
        } else {
2426
2.27k
            tableNameForAlias = "geodetic_datum";
2427
2.27k
        }
2428
2429
2.54k
        bool setNameAndId = true;
2430
2.54k
        if (dbContext_ && tableNameForAlias) {
2431
1.81k
            if (officialName.empty()) {
2432
1.81k
                std::string outTableName;
2433
1.81k
                auto authFactory = AuthorityFactory::create(
2434
1.81k
                    NN_NO_CHECK(dbContext_), std::string());
2435
1.81k
                officialName = authFactory->getOfficialNameFromAlias(
2436
1.81k
                    name, tableNameForAlias, "ESRI", false, outTableName,
2437
1.81k
                    authNameFromAlias, codeFromAlias);
2438
1.81k
            }
2439
1.81k
            if (officialName.empty()) {
2440
1.80k
                if (starts_with(name, "D_")) {
2441
                    // For the case of "D_GDA2020" where there is no D_GDA2020
2442
                    // ESRI alias, so just try without the D_ prefix.
2443
1.25k
                    const auto nameWithoutDPrefix = name.substr(2);
2444
1.25k
                    if (identifyFromName(nameWithoutDPrefix)) {
2445
64
                        setNameAndId =
2446
64
                            false; // already done in identifyFromName()
2447
64
                    }
2448
1.25k
                }
2449
1.80k
            } else {
2450
13
                if (primeMeridian->nameStr() !=
2451
13
                    PrimeMeridian::GREENWICH->nameStr()) {
2452
0
                    auto nameWithPM =
2453
0
                        officialName + " (" + primeMeridian->nameStr() + ")";
2454
0
                    if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) {
2455
0
                        officialName = std::move(nameWithPM);
2456
0
                    }
2457
0
                }
2458
13
                name = std::move(officialName);
2459
13
            }
2460
1.81k
        }
2461
2462
2.54k
        if (setNameAndId) {
2463
2.47k
            properties.set(IdentifiedObject::NAME_KEY, name);
2464
2.47k
            if (!authNameFromAlias.empty()) {
2465
81
                auto identifiers = ArrayOfBaseObject::create();
2466
81
                identifiers->add(Identifier::create(
2467
81
                    codeFromAlias,
2468
81
                    PropertyMap()
2469
81
                        .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2470
81
                        .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
2471
81
                properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
2472
81
            }
2473
2.47k
        }
2474
2.54k
    } else if (!nameSet && name.find('_') != std::string::npos) {
2475
        // Likely coming from WKT1
2476
598
        identifyFromName(name);
2477
598
    }
2478
2479
3.78k
    auto ellipsoid = buildEllipsoid(ellipsoidNode);
2480
3.78k
    const auto &primeMeridianModified =
2481
3.78k
        fixupPrimeMeridan(ellipsoid, primeMeridian);
2482
2483
3.78k
    auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84);
2484
3.78k
    if (!isNull(TOWGS84Node)) {
2485
0
        const auto &TOWGS84Children = TOWGS84Node->GP()->children();
2486
0
        const size_t TOWGS84Size = TOWGS84Children.size();
2487
0
        if (TOWGS84Size == 3 || TOWGS84Size == 7) {
2488
0
            try {
2489
0
                for (const auto &child : TOWGS84Children) {
2490
0
                    toWGS84Parameters_.push_back(asDouble(child));
2491
0
                }
2492
2493
0
                if (TOWGS84Size == 7 && dbContext_) {
2494
0
                    dbContext_->toWGS84AutocorrectWrongValues(
2495
0
                        toWGS84Parameters_[0], toWGS84Parameters_[1],
2496
0
                        toWGS84Parameters_[2], toWGS84Parameters_[3],
2497
0
                        toWGS84Parameters_[4], toWGS84Parameters_[5],
2498
0
                        toWGS84Parameters_[6]);
2499
0
                }
2500
2501
0
                for (size_t i = TOWGS84Size; i < 7; ++i) {
2502
0
                    toWGS84Parameters_.push_back(0.0);
2503
0
                }
2504
0
            } catch (const std::exception &) {
2505
0
                throw ParsingException("Invalid TOWGS84 node");
2506
0
            }
2507
0
        } else {
2508
0
            throw ParsingException("Invalid TOWGS84 node");
2509
0
        }
2510
0
    }
2511
2512
3.78k
    auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
2513
3.78k
    const auto &extensionChildren = extensionNode->GP()->children();
2514
3.78k
    if (extensionChildren.size() == 2) {
2515
3
        if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
2516
0
            datumPROJ4Grids_ = stripQuotes(extensionChildren[1]);
2517
0
        }
2518
3
    }
2519
2520
3.78k
    if (!isNull(dynamicNode)) {
2521
30
        double frameReferenceEpoch = 0.0;
2522
30
        util::optional<std::string> modelName;
2523
30
        parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
2524
30
        return DynamicGeodeticReferenceFrame::create(
2525
30
            properties, ellipsoid, getAnchor(node), primeMeridianModified,
2526
30
            common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
2527
30
            modelName);
2528
30
    }
2529
2530
3.75k
    return GeodeticReferenceFrame::create(properties, ellipsoid,
2531
3.75k
                                          getAnchor(node), getAnchorEpoch(node),
2532
3.75k
                                          primeMeridianModified);
2533
3.78k
}
2534
2535
// ---------------------------------------------------------------------------
2536
2537
DatumEnsembleNNPtr
2538
WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node,
2539
                                       const PrimeMeridianPtr &primeMeridian,
2540
256
                                       bool expectEllipsoid) {
2541
256
    const auto *nodeP = node->GP();
2542
256
    auto &ellipsoidNode =
2543
256
        nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2544
256
    if (expectEllipsoid && isNull(ellipsoidNode)) {
2545
4
        ThrowMissing(WKTConstants::ELLIPSOID);
2546
4
    }
2547
2548
252
    std::vector<DatumNNPtr> datums;
2549
6.50k
    for (const auto &subNode : nodeP->children()) {
2550
6.50k
        if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) {
2551
2.17k
            if (subNode->GP()->childrenSize() == 0) {
2552
4
                throw ParsingException("Invalid MEMBER node");
2553
4
            }
2554
2.16k
            if (expectEllipsoid) {
2555
1.79k
                datums.emplace_back(GeodeticReferenceFrame::create(
2556
1.79k
                    buildProperties(subNode), buildEllipsoid(ellipsoidNode),
2557
1.79k
                    optional<std::string>(),
2558
1.79k
                    primeMeridian ? NN_NO_CHECK(primeMeridian)
2559
1.79k
                                  : PrimeMeridian::GREENWICH));
2560
1.79k
            } else {
2561
373
                datums.emplace_back(
2562
373
                    VerticalReferenceFrame::create(buildProperties(subNode)));
2563
373
            }
2564
2.16k
        }
2565
6.50k
    }
2566
2567
248
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY);
2568
248
    auto &accuracyNodeChildren = accuracyNode->GP()->children();
2569
248
    if (accuracyNodeChildren.empty()) {
2570
82
        ThrowMissing(WKTConstants::ENSEMBLEACCURACY);
2571
82
    }
2572
166
    auto accuracy =
2573
166
        PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value());
2574
2575
166
    try {
2576
166
        return DatumEnsemble::create(buildProperties(node), datums, accuracy);
2577
166
    } catch (const util::Exception &e) {
2578
11
        throw buildRethrow(__FUNCTION__, e);
2579
11
    }
2580
166
}
2581
2582
// ---------------------------------------------------------------------------
2583
2584
4
MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) {
2585
4
    const auto *nodeP = node->GP();
2586
4
    const auto &children = nodeP->children();
2587
4
    if (children.size() < 2) {
2588
2
        ThrowNotEnoughChildren(nodeP->value());
2589
2
    }
2590
2
    UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2591
2
    try {
2592
2
        double angleValue = asDouble(children[0]);
2593
2
        Angle angle(angleValue, unit);
2594
2
        return Meridian::create(angle);
2595
2
    } catch (const std::exception &e) {
2596
2
        throw buildRethrow(__FUNCTION__, e);
2597
2
    }
2598
2
}
2599
2600
// ---------------------------------------------------------------------------
2601
2602
3
PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() {
2603
3
    throw ParsingException("buildCS: missing UNIT");
2604
3
}
2605
2606
// ---------------------------------------------------------------------------
2607
2608
CoordinateSystemAxisNNPtr
2609
WKTParser::Private::buildAxis(const WKTNodeNNPtr &node,
2610
                              const UnitOfMeasure &unitIn,
2611
                              const UnitOfMeasure::Type &unitType,
2612
518
                              bool isGeocentric, int expectedOrderNum) {
2613
518
    const auto *nodeP = node->GP();
2614
518
    const auto &children = nodeP->children();
2615
518
    if (children.size() < 2) {
2616
9
        ThrowNotEnoughChildren(nodeP->value());
2617
9
    }
2618
2619
509
    auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER);
2620
509
    if (!isNull(orderNode)) {
2621
6
        const auto &orderNodeChildren = orderNode->GP()->children();
2622
6
        if (orderNodeChildren.size() != 1) {
2623
3
            ThrowNotEnoughChildren(WKTConstants::ORDER);
2624
3
        }
2625
3
        const auto &order = orderNodeChildren[0]->GP()->value();
2626
3
        int orderNum;
2627
3
        try {
2628
3
            orderNum = std::stoi(order);
2629
3
        } catch (const std::exception &) {
2630
3
            throw ParsingException(
2631
3
                concat("buildAxis: invalid ORDER value: ", order));
2632
3
        }
2633
0
        if (orderNum != expectedOrderNum) {
2634
0
            throw ParsingException(
2635
0
                concat("buildAxis: did not get expected ORDER value: ", order));
2636
0
        }
2637
0
    }
2638
2639
    // The axis designation in WK2 can be: "name", "(abbrev)" or "name
2640
    // (abbrev)"
2641
503
    std::string axisDesignation(stripQuotes(children[0]));
2642
503
    size_t sepPos = axisDesignation.find(" (");
2643
503
    std::string axisName;
2644
503
    std::string abbreviation;
2645
503
    if (sepPos != std::string::npos && axisDesignation.back() == ')') {
2646
0
        axisName = CoordinateSystemAxis::normalizeAxisName(
2647
0
            axisDesignation.substr(0, sepPos));
2648
0
        abbreviation = axisDesignation.substr(sepPos + 2);
2649
0
        abbreviation.resize(abbreviation.size() - 1);
2650
503
    } else if (!axisDesignation.empty() && axisDesignation[0] == '(' &&
2651
503
               axisDesignation.back() == ')') {
2652
0
        abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2);
2653
0
        if (abbreviation == AxisAbbreviation::E) {
2654
0
            axisName = AxisName::Easting;
2655
0
        } else if (abbreviation == AxisAbbreviation::N) {
2656
0
            axisName = AxisName::Northing;
2657
0
        } else if (abbreviation == AxisAbbreviation::lat) {
2658
0
            axisName = AxisName::Latitude;
2659
0
        } else if (abbreviation == AxisAbbreviation::lon) {
2660
0
            axisName = AxisName::Longitude;
2661
0
        }
2662
503
    } else {
2663
503
        axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation);
2664
503
        if (axisName == AxisName::Latitude) {
2665
0
            abbreviation = AxisAbbreviation::lat;
2666
503
        } else if (axisName == AxisName::Longitude) {
2667
0
            abbreviation = AxisAbbreviation::lon;
2668
503
        } else if (axisName == AxisName::Ellipsoidal_height) {
2669
0
            abbreviation = AxisAbbreviation::h;
2670
0
        }
2671
503
    }
2672
503
    const std::string &dirString = children[1]->GP()->value();
2673
503
    const AxisDirection *direction = AxisDirection::valueOf(dirString);
2674
2675
    // WKT2, geocentric CS: axis names are omitted
2676
503
    if (axisName.empty()) {
2677
5
        if (direction == &AxisDirection::GEOCENTRIC_X &&
2678
5
            abbreviation == AxisAbbreviation::X) {
2679
0
            axisName = AxisName::Geocentric_X;
2680
5
        } else if (direction == &AxisDirection::GEOCENTRIC_Y &&
2681
5
                   abbreviation == AxisAbbreviation::Y) {
2682
0
            axisName = AxisName::Geocentric_Y;
2683
5
        } else if (direction == &AxisDirection::GEOCENTRIC_Z &&
2684
5
                   abbreviation == AxisAbbreviation::Z) {
2685
0
            axisName = AxisName::Geocentric_Z;
2686
0
        }
2687
5
    }
2688
2689
    // WKT1
2690
503
    if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) {
2691
0
        abbreviation = AxisAbbreviation::X;
2692
0
        direction = &AxisDirection::GEOCENTRIC_X;
2693
503
    } else if (!direction && isGeocentric &&
2694
503
               axisName == AxisName::Geocentric_Y) {
2695
0
        abbreviation = AxisAbbreviation::Y;
2696
0
        direction = &AxisDirection::GEOCENTRIC_Y;
2697
503
    } else if (isGeocentric && axisName == AxisName::Geocentric_Z &&
2698
503
               (dirString == AxisDirectionWKT1::NORTH.toString() ||
2699
0
                dirString == AxisDirectionWKT1::OTHER.toString())) {
2700
0
        abbreviation = AxisAbbreviation::Z;
2701
0
        direction = &AxisDirection::GEOCENTRIC_Z;
2702
503
    } else if (dirString == AxisDirectionWKT1::OTHER.toString()) {
2703
0
        direction = &AxisDirection::UNSPECIFIED;
2704
503
    } else if (dirString == "UNKNOWN") {
2705
        // Found in WKT1 of NSIDC's EASE-Grid Sea Ice Age datasets.
2706
        // Cf https://github.com/OSGeo/gdal/issues/7210
2707
0
        emitRecoverableWarning("UNKNOWN is not a valid direction name.");
2708
0
        direction = &AxisDirection::UNSPECIFIED;
2709
0
    }
2710
2711
503
    if (!direction) {
2712
66
        throw ParsingException(
2713
66
            concat("unhandled axis direction: ", children[1]->GP()->value()));
2714
66
    }
2715
437
    UnitOfMeasure unit(buildUnitInSubNode(node));
2716
437
    if (unit == UnitOfMeasure::NONE) {
2717
        // If no unit in the AXIS node, use the one potentially coming from
2718
        // the CS.
2719
415
        unit = unitIn;
2720
415
        if (unit == UnitOfMeasure::NONE &&
2721
415
            unitType != UnitOfMeasure::Type::NONE &&
2722
415
            unitType != UnitOfMeasure::Type::TIME) {
2723
3
            ThrowParsingExceptionMissingUNIT();
2724
3
        }
2725
415
    }
2726
2727
434
    auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN);
2728
2729
434
    util::optional<double> minVal;
2730
434
    auto &axisMinValueNode = nodeP->lookForChild(WKTConstants::AXISMINVALUE);
2731
434
    if (!isNull(axisMinValueNode)) {
2732
0
        const auto &axisMinValueNodeChildren =
2733
0
            axisMinValueNode->GP()->children();
2734
0
        if (axisMinValueNodeChildren.size() != 1) {
2735
0
            ThrowNotEnoughChildren(WKTConstants::AXISMINVALUE);
2736
0
        }
2737
0
        const auto &val = axisMinValueNodeChildren[0];
2738
0
        try {
2739
0
            minVal = asDouble(val);
2740
0
        } catch (const std::exception &) {
2741
0
            throw ParsingException(concat(
2742
0
                "buildAxis: invalid AXISMINVALUE value: ", val->GP()->value()));
2743
0
        }
2744
0
    }
2745
2746
434
    util::optional<double> maxVal;
2747
434
    auto &axisMaxValueNode = nodeP->lookForChild(WKTConstants::AXISMAXVALUE);
2748
434
    if (!isNull(axisMaxValueNode)) {
2749
3
        const auto &axisMaxValueNodeChildren =
2750
3
            axisMaxValueNode->GP()->children();
2751
3
        if (axisMaxValueNodeChildren.size() != 1) {
2752
1
            ThrowNotEnoughChildren(WKTConstants::AXISMAXVALUE);
2753
1
        }
2754
2
        const auto &val = axisMaxValueNodeChildren[0];
2755
2
        try {
2756
2
            maxVal = asDouble(val);
2757
2
        } catch (const std::exception &) {
2758
2
            throw ParsingException(concat(
2759
2
                "buildAxis: invalid AXISMAXVALUE value: ", val->GP()->value()));
2760
2
        }
2761
2
    }
2762
2763
431
    util::optional<RangeMeaning> rangeMeaning;
2764
431
    auto &rangeMeaningNode = nodeP->lookForChild(WKTConstants::RANGEMEANING);
2765
431
    if (!isNull(rangeMeaningNode)) {
2766
98
        const auto &rangeMeaningNodeChildren =
2767
98
            rangeMeaningNode->GP()->children();
2768
98
        if (rangeMeaningNodeChildren.size() != 1) {
2769
3
            ThrowNotEnoughChildren(WKTConstants::RANGEMEANING);
2770
3
        }
2771
95
        const std::string &val = rangeMeaningNodeChildren[0]->GP()->value();
2772
95
        const RangeMeaning *meaning = RangeMeaning::valueOf(val);
2773
95
        if (meaning == nullptr) {
2774
7
            throw ParsingException(
2775
7
                concat("buildAxis: invalid RANGEMEANING value: ", val));
2776
7
        }
2777
88
        rangeMeaning = util::optional<RangeMeaning>(*meaning);
2778
88
    }
2779
2780
421
    return CoordinateSystemAxis::create(
2781
421
        buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName),
2782
421
        abbreviation, *direction, unit, minVal, maxVal, rangeMeaning,
2783
421
        !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable()
2784
421
                              : nullptr);
2785
431
}
2786
2787
// ---------------------------------------------------------------------------
2788
2789
static const PropertyMap emptyPropertyMap{};
2790
2791
// ---------------------------------------------------------------------------
2792
2793
7
PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) {
2794
7
    throw ParsingException(msg);
2795
7
}
2796
2797
// ---------------------------------------------------------------------------
2798
2799
static ParsingException
2800
7
buildParsingExceptionInvalidAxisCount(const std::string &csType) {
2801
7
    return ParsingException(
2802
7
        concat("buildCS: invalid CS axis count for ", csType));
2803
7
}
2804
2805
// ---------------------------------------------------------------------------
2806
2807
void WKTParser::Private::emitRecoverableMissingUNIT(
2808
6.51k
    const std::string &parentNodeName, const UnitOfMeasure &fallbackUnit) {
2809
6.51k
    std::string msg("buildCS: missing UNIT in ");
2810
6.51k
    msg += parentNodeName;
2811
6.51k
    if (!strict_ && fallbackUnit == UnitOfMeasure::METRE) {
2812
3.47k
        msg += ". Assuming metre";
2813
3.47k
    } else if (!strict_ && fallbackUnit == UnitOfMeasure::DEGREE) {
2814
3.04k
        msg += ". Assuming degree";
2815
3.04k
    }
2816
6.51k
    emitRecoverableWarning(msg);
2817
6.51k
}
2818
2819
// ---------------------------------------------------------------------------
2820
2821
CoordinateSystemNNPtr
2822
WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */
2823
                            const WKTNodeNNPtr &parentNode,
2824
9.90k
                            const UnitOfMeasure &defaultAngularUnit) {
2825
9.90k
    bool isGeocentric = false;
2826
9.90k
    std::string csType;
2827
9.90k
    const int numberOfAxis =
2828
9.90k
        parentNode->countChildrenOfName(WKTConstants::AXIS);
2829
9.90k
    int axisCount = numberOfAxis;
2830
9.90k
    const auto &parentNodeName = parentNode->GP()->value();
2831
9.90k
    if (!isNull(node)) {
2832
11
        const auto *nodeP = node->GP();
2833
11
        const auto &children = nodeP->children();
2834
11
        if (children.size() < 2) {
2835
2
            ThrowNotEnoughChildren(nodeP->value());
2836
2
        }
2837
9
        csType = children[0]->GP()->value();
2838
9
        try {
2839
9
            axisCount = std::stoi(children[1]->GP()->value());
2840
9
        } catch (const std::exception &) {
2841
7
            ThrowParsingException(concat("buildCS: invalid CS axis count: ",
2842
7
                                         children[1]->GP()->value()));
2843
7
        }
2844
9.89k
    } else {
2845
9.89k
        const char *csTypeCStr = CartesianCS::WKT2_TYPE;
2846
9.89k
        if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) {
2847
            // csTypeCStr = CartesianCS::WKT2_TYPE;
2848
191
            isGeocentric = true;
2849
191
            if (axisCount == 0) {
2850
184
                auto unit =
2851
184
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2852
184
                if (unit == UnitOfMeasure::NONE) {
2853
175
                    unit = UnitOfMeasure::METRE;
2854
175
                    emitRecoverableMissingUNIT(parentNodeName, unit);
2855
175
                }
2856
184
                return CartesianCS::createGeocentric(unit);
2857
184
            }
2858
9.70k
        } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) {
2859
3.13k
            csTypeCStr = EllipsoidalCS::WKT2_TYPE;
2860
3.13k
            if (axisCount == 0) {
2861
                // Missing axis with GEOGCS ? Presumably Long/Lat order
2862
                // implied
2863
3.12k
                auto unit = buildUnitInSubNode(parentNode,
2864
3.12k
                                               UnitOfMeasure::Type::ANGULAR);
2865
3.12k
                if (unit == UnitOfMeasure::NONE) {
2866
3.04k
                    unit = defaultAngularUnit;
2867
3.04k
                    emitRecoverableMissingUNIT(parentNodeName, unit);
2868
3.04k
                }
2869
2870
                // ESRI WKT for geographic 3D CRS
2871
3.12k
                auto &linUnitNode =
2872
3.12k
                    parentNode->GP()->lookForChild(WKTConstants::LINUNIT);
2873
3.12k
                if (!isNull(linUnitNode)) {
2874
37
                    return EllipsoidalCS::
2875
37
                        createLongitudeLatitudeEllipsoidalHeight(
2876
37
                            unit, buildUnit(linUnitNode,
2877
37
                                            UnitOfMeasure::Type::LINEAR));
2878
37
                }
2879
2880
                // WKT1 --> long/lat
2881
3.08k
                return EllipsoidalCS::createLongitudeLatitude(unit);
2882
3.12k
            }
2883
6.57k
        } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) ||
2884
6.57k
                   ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) {
2885
0
            csTypeCStr = EllipsoidalCS::WKT2_TYPE;
2886
0
            if (axisCount == 0) {
2887
0
                auto unit = buildUnitInSubNode(parentNode,
2888
0
                                               UnitOfMeasure::Type::ANGULAR);
2889
0
                if (unit == UnitOfMeasure::NONE) {
2890
0
                    unit = defaultAngularUnit;
2891
0
                }
2892
                // WKT2 --> presumably lat/long
2893
0
                return EllipsoidalCS::createLatitudeLongitude(unit);
2894
0
            }
2895
6.57k
        } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) ||
2896
6.57k
                   ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) ||
2897
6.57k
                   ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) {
2898
1.22k
            csTypeCStr = CartesianCS::WKT2_TYPE;
2899
1.22k
            if (axisCount == 0) {
2900
1.22k
                auto unit =
2901
1.22k
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2902
1.22k
                if (unit == UnitOfMeasure::NONE) {
2903
1.14k
                    unit = UnitOfMeasure::METRE;
2904
1.14k
                    if (ci_equal(parentNodeName, WKTConstants::PROJCS)) {
2905
1.14k
                        emitRecoverableMissingUNIT(parentNodeName, unit);
2906
1.14k
                    }
2907
1.14k
                }
2908
1.22k
                return CartesianCS::createEastingNorthing(unit);
2909
1.22k
            }
2910
5.34k
        } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2911
5.34k
                   ci_equal(parentNodeName, WKTConstants::VERTCS) ||
2912
5.34k
                   ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) {
2913
2.29k
            csTypeCStr = VerticalCS::WKT2_TYPE;
2914
2915
2.29k
            bool downDirection = false;
2916
2.29k
            if (ci_equal(parentNodeName, WKTConstants::VERTCS)) // ESRI
2917
1.61k
            {
2918
10.3k
                for (const auto &childNode : parentNode->GP()->children()) {
2919
10.3k
                    const auto &childNodeChildren = childNode->GP()->children();
2920
10.3k
                    if (childNodeChildren.size() == 2 &&
2921
10.3k
                        ci_equal(childNode->GP()->value(),
2922
2.11k
                                 WKTConstants::PARAMETER) &&
2923
10.3k
                        childNodeChildren[0]->GP()->value() ==
2924
329
                            "\"Direction\"") {
2925
0
                        const auto &paramValue =
2926
0
                            childNodeChildren[1]->GP()->value();
2927
0
                        try {
2928
0
                            double val = asDouble(childNodeChildren[1]);
2929
0
                            if (val == 1.0) {
2930
                                // ok
2931
0
                            } else if (val == -1.0) {
2932
0
                                downDirection = true;
2933
0
                            }
2934
0
                        } catch (const std::exception &) {
2935
0
                            throw ParsingException(
2936
0
                                concat("unhandled parameter value type : ",
2937
0
                                       paramValue));
2938
0
                        }
2939
0
                    }
2940
10.3k
                }
2941
1.61k
            }
2942
2943
2.29k
            if (axisCount == 0) {
2944
2.25k
                auto unit =
2945
2.25k
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2946
2.25k
                if (unit == UnitOfMeasure::NONE) {
2947
2.11k
                    unit = UnitOfMeasure::METRE;
2948
2.11k
                    if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2949
2.11k
                        ci_equal(parentNodeName, WKTConstants::VERTCS)) {
2950
2.11k
                        emitRecoverableMissingUNIT(parentNodeName, unit);
2951
2.11k
                    }
2952
2.11k
                }
2953
2.25k
                if (downDirection) {
2954
0
                    return VerticalCS::create(
2955
0
                        util::PropertyMap(),
2956
0
                        CoordinateSystemAxis::create(
2957
0
                            util::PropertyMap().set(IdentifiedObject::NAME_KEY,
2958
0
                                                    "depth"),
2959
0
                            "D", AxisDirection::DOWN, unit));
2960
0
                }
2961
2.25k
                return VerticalCS::createGravityRelatedHeight(unit);
2962
2.25k
            }
2963
3.05k
        } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) {
2964
3.05k
            if (axisCount == 0) {
2965
2.75k
                auto unit =
2966
2.75k
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2967
2.75k
                if (unit == UnitOfMeasure::NONE) {
2968
2.42k
                    unit = UnitOfMeasure::METRE;
2969
2.42k
                }
2970
2.75k
                return CartesianCS::createEastingNorthing(unit);
2971
2.75k
            } else if (axisCount == 1) {
2972
183
                csTypeCStr = VerticalCS::WKT2_TYPE;
2973
183
            } else if (axisCount == 2 || axisCount == 3) {
2974
110
                csTypeCStr = CartesianCS::WKT2_TYPE;
2975
110
            } else {
2976
3
                throw ParsingException(
2977
3
                    "buildCS: unexpected AXIS count for LOCAL_CS");
2978
3
            }
2979
3.05k
        } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) {
2980
0
            csTypeCStr = ParametricCS::WKT2_TYPE;
2981
0
            if (axisCount == 0) {
2982
0
                auto unit =
2983
0
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2984
0
                if (unit == UnitOfMeasure::NONE) {
2985
0
                    unit = UnitOfMeasure("unknown", 1,
2986
0
                                         UnitOfMeasure::Type::PARAMETRIC);
2987
0
                }
2988
0
                return ParametricCS::create(
2989
0
                    emptyPropertyMap,
2990
0
                    CoordinateSystemAxis::create(
2991
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
2992
0
                                          "unknown parametric"),
2993
0
                        std::string(), AxisDirection::UNSPECIFIED, unit));
2994
0
            }
2995
0
        } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) {
2996
0
            csTypeCStr = TemporalCS::WKT2_2015_TYPE;
2997
0
            if (axisCount == 0) {
2998
0
                auto unit =
2999
0
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME);
3000
0
                if (unit == UnitOfMeasure::NONE) {
3001
0
                    unit =
3002
0
                        UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME);
3003
0
                }
3004
0
                return DateTimeTemporalCS::create(
3005
0
                    emptyPropertyMap,
3006
0
                    CoordinateSystemAxis::create(
3007
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
3008
0
                                          "unknown temporal"),
3009
0
                        std::string(), AxisDirection::FUTURE, unit));
3010
0
            }
3011
0
        } else {
3012
            // Shouldn't happen normally
3013
0
            throw ParsingException(
3014
0
                concat("buildCS: unexpected parent node: ", parentNodeName));
3015
0
        }
3016
354
        csType = csTypeCStr;
3017
354
    }
3018
3019
356
    if (axisCount != 1 && axisCount != 2 && axisCount != 3) {
3020
2
        throw buildParsingExceptionInvalidAxisCount(csType);
3021
2
    }
3022
354
    if (numberOfAxis != axisCount) {
3023
0
        throw ParsingException("buildCS: declared number of axis by CS node "
3024
0
                               "and number of AXIS are inconsistent");
3025
0
    }
3026
3027
354
    const auto unitType =
3028
354
        ci_equal(csType, EllipsoidalCS::WKT2_TYPE)
3029
354
            ? UnitOfMeasure::Type::ANGULAR
3030
354
        : ci_equal(csType, OrdinalCS::WKT2_TYPE) ? UnitOfMeasure::Type::NONE
3031
348
        : ci_equal(csType, ParametricCS::WKT2_TYPE)
3032
348
            ? UnitOfMeasure::Type::PARAMETRIC
3033
348
        : ci_equal(csType, CartesianCS::WKT2_TYPE) ||
3034
348
                ci_equal(csType, VerticalCS::WKT2_TYPE) ||
3035
348
                ci_equal(csType, AffineCS::WKT2_TYPE)
3036
348
            ? UnitOfMeasure::Type::LINEAR
3037
348
        : (ci_equal(csType, TemporalCS::WKT2_2015_TYPE) ||
3038
0
           ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE) ||
3039
0
           ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE) ||
3040
0
           ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE))
3041
0
            ? UnitOfMeasure::Type::TIME
3042
0
            : UnitOfMeasure::Type::UNKNOWN;
3043
354
    UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType);
3044
3045
354
    if (unit == UnitOfMeasure::NONE) {
3046
58
        if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
3047
58
            ci_equal(parentNodeName, WKTConstants::VERTCS)) {
3048
46
            unit = UnitOfMeasure::METRE;
3049
46
            emitRecoverableMissingUNIT(parentNodeName, unit);
3050
46
        }
3051
58
    }
3052
3053
354
    std::vector<CoordinateSystemAxisNNPtr> axisList;
3054
872
    for (int i = 0; i < axisCount; i++) {
3055
518
        axisList.emplace_back(
3056
518
            buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i),
3057
518
                      unit, unitType, isGeocentric, i + 1));
3058
518
    }
3059
3060
354
    const PropertyMap &csMap = emptyPropertyMap;
3061
354
    if (ci_equal(csType, EllipsoidalCS::WKT2_TYPE)) {
3062
4
        if (axisCount == 2) {
3063
2
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
3064
2
        } else if (axisCount == 3) {
3065
2
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
3066
2
                                         axisList[2]);
3067
2
        }
3068
350
    } else if (ci_equal(csType, CartesianCS::WKT2_TYPE)) {
3069
87
        if (axisCount == 2) {
3070
49
            return CartesianCS::create(csMap, axisList[0], axisList[1]);
3071
49
        } else if (axisCount == 3) {
3072
37
            return CartesianCS::create(csMap, axisList[0], axisList[1],
3073
37
                                       axisList[2]);
3074
37
        }
3075
263
    } else if (ci_equal(csType, AffineCS::WKT2_TYPE)) {
3076
0
        if (axisCount == 2) {
3077
0
            return AffineCS::create(csMap, axisList[0], axisList[1]);
3078
0
        } else if (axisCount == 3) {
3079
0
            return AffineCS::create(csMap, axisList[0], axisList[1],
3080
0
                                    axisList[2]);
3081
0
        }
3082
263
    } else if (ci_equal(csType, VerticalCS::WKT2_TYPE)) {
3083
156
        if (axisCount == 1) {
3084
152
            return VerticalCS::create(csMap, axisList[0]);
3085
152
        }
3086
156
    } else if (ci_equal(csType, SphericalCS::WKT2_TYPE)) {
3087
0
        if (axisCount == 2) {
3088
            // Extension to ISO19111 to support (planet)-ocentric CS with
3089
            // geocentric latitude
3090
0
            return SphericalCS::create(csMap, axisList[0], axisList[1]);
3091
0
        } else if (axisCount == 3) {
3092
0
            return SphericalCS::create(csMap, axisList[0], axisList[1],
3093
0
                                       axisList[2]);
3094
0
        }
3095
107
    } else if (ci_equal(csType, OrdinalCS::WKT2_TYPE)) { // WKT2-2019
3096
0
        return OrdinalCS::create(csMap, axisList);
3097
107
    } else if (ci_equal(csType, ParametricCS::WKT2_TYPE)) {
3098
0
        if (axisCount == 1) {
3099
0
            return ParametricCS::create(csMap, axisList[0]);
3100
0
        }
3101
107
    } else if (ci_equal(csType, TemporalCS::WKT2_2015_TYPE)) {
3102
0
        if (axisCount == 1) {
3103
0
            if (isNull(
3104
0
                    parentNode->GP()->lookForChild(WKTConstants::TIMEUNIT)) &&
3105
0
                isNull(parentNode->GP()->lookForChild(WKTConstants::UNIT))) {
3106
0
                return DateTimeTemporalCS::create(csMap, axisList[0]);
3107
0
            } else {
3108
                // Default to TemporalMeasureCS
3109
                // TemporalCount could also be possible
3110
0
                return TemporalMeasureCS::create(csMap, axisList[0]);
3111
0
            }
3112
0
        }
3113
107
    } else if (ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE)) {
3114
0
        if (axisCount == 1) {
3115
0
            return DateTimeTemporalCS::create(csMap, axisList[0]);
3116
0
        }
3117
107
    } else if (ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE)) {
3118
0
        if (axisCount == 1) {
3119
0
            return TemporalCountCS::create(csMap, axisList[0]);
3120
0
        }
3121
107
    } else if (ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE)) {
3122
0
        if (axisCount == 1) {
3123
0
            return TemporalMeasureCS::create(csMap, axisList[0]);
3124
0
        }
3125
107
    } else {
3126
107
        throw ParsingException(concat("unhandled CS type: ", csType));
3127
107
    }
3128
5
    throw buildParsingExceptionInvalidAxisCount(csType);
3129
354
}
3130
3131
// ---------------------------------------------------------------------------
3132
3133
std::string
3134
5.82k
WKTParser::Private::getExtensionProj4(const WKTNode::Private *nodeP) {
3135
5.82k
    auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
3136
5.82k
    const auto &extensionChildren = extensionNode->GP()->children();
3137
5.82k
    if (extensionChildren.size() == 2) {
3138
14
        if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
3139
0
            return stripQuotes(extensionChildren[1]);
3140
0
        }
3141
14
    }
3142
5.82k
    return std::string();
3143
5.82k
}
3144
3145
// ---------------------------------------------------------------------------
3146
3147
void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP,
3148
4.52k
                                                 PropertyMap &props) {
3149
4.52k
    const auto extensionProj4(getExtensionProj4(nodeP));
3150
4.52k
    if (!extensionProj4.empty()) {
3151
0
        props.set("EXTENSION_PROJ4", extensionProj4);
3152
0
    }
3153
4.52k
}
3154
3155
// ---------------------------------------------------------------------------
3156
3157
GeodeticCRSNNPtr
3158
3.51k
WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node) {
3159
3.51k
    const auto *nodeP = node->GP();
3160
3.51k
    auto &datumNode = nodeP->lookForChild(
3161
3.51k
        WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF);
3162
3.51k
    auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
3163
3.51k
    if (isNull(datumNode) && isNull(ensembleNode)) {
3164
55
        throw ParsingException("Missing DATUM or ENSEMBLE node");
3165
55
    }
3166
3167
    // Do that now so that esriStyle_ can be set before buildPrimeMeridian()
3168
3.45k
    auto props = buildProperties(node);
3169
3170
3.45k
    auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
3171
3172
3.45k
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3173
3.45k
    const auto &nodeName = nodeP->value();
3174
3.45k
    if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) &&
3175
3.45k
        !ci_equal(nodeName, WKTConstants::GEOCCS) &&
3176
3.45k
        !ci_equal(nodeName, WKTConstants::BASEGEODCRS) &&
3177
3.45k
        !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
3178
3
        ThrowMissing(WKTConstants::CS_);
3179
3
    }
3180
3181
3.45k
    auto &primeMeridianNode =
3182
3.45k
        nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN);
3183
3.45k
    if (isNull(primeMeridianNode)) {
3184
        // PRIMEM is required in WKT1
3185
2.86k
        if (ci_equal(nodeName, WKTConstants::GEOGCS) ||
3186
2.86k
            ci_equal(nodeName, WKTConstants::GEOCCS)) {
3187
2.86k
            emitRecoverableWarning(nodeName + " should have a PRIMEM node");
3188
2.86k
        }
3189
2.86k
    }
3190
3191
3.45k
    auto angularUnit =
3192
3.45k
        buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS)
3193
3.45k
                                     ? UnitOfMeasure::Type::ANGULAR
3194
3.45k
                                     : UnitOfMeasure::Type::UNKNOWN);
3195
3.45k
    if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) {
3196
3.32k
        angularUnit = UnitOfMeasure::NONE;
3197
3.32k
    }
3198
3199
3.45k
    auto primeMeridian =
3200
3.45k
        !isNull(primeMeridianNode)
3201
3.45k
            ? buildPrimeMeridian(primeMeridianNode, angularUnit)
3202
3.45k
            : PrimeMeridian::GREENWICH;
3203
3.45k
    if (angularUnit == UnitOfMeasure::NONE) {
3204
3.32k
        angularUnit = primeMeridian->longitude().unit();
3205
3.32k
    }
3206
3207
3.45k
    addExtensionProj4ToProp(nodeP, props);
3208
3209
    // No explicit AXIS node ? (WKT1)
3210
3.45k
    if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
3211
3.43k
        props.set("IMPLICIT_CS", true);
3212
3.43k
    }
3213
3214
3.45k
    const std::string crsName = stripQuotes(nodeP->children()[0]);
3215
3.45k
    if (esriStyle_ && dbContext_) {
3216
1.02k
        std::string outTableName;
3217
1.02k
        std::string authNameFromAlias;
3218
1.02k
        std::string codeFromAlias;
3219
1.02k
        auto authFactory =
3220
1.02k
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
3221
1.02k
        auto officialName = authFactory->getOfficialNameFromAlias(
3222
1.02k
            crsName, "geodetic_crs", "ESRI", false, outTableName,
3223
1.02k
            authNameFromAlias, codeFromAlias);
3224
1.02k
        if (!officialName.empty()) {
3225
49
            props.set(IdentifiedObject::NAME_KEY, officialName);
3226
49
        }
3227
1.02k
    }
3228
3229
3.45k
    auto datum =
3230
3.45k
        !isNull(datumNode)
3231
3.45k
            ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode)
3232
3.32k
                  .as_nullable()
3233
3.45k
            : nullptr;
3234
3.45k
    auto datumEnsemble =
3235
3.45k
        !isNull(ensembleNode)
3236
3.45k
            ? buildDatumEnsemble(ensembleNode, primeMeridian, true)
3237
123
                  .as_nullable()
3238
3.45k
            : nullptr;
3239
3.45k
    auto cs = buildCS(csNode, node, angularUnit);
3240
3241
    // If there's no CS[] node, typically for a BASEGEODCRS of a projected CRS,
3242
    // in a few rare cases, this might be a Geocentric CRS, and thus a
3243
    // Cartesian CS, and not the ellipsoidalCS we assumed above. The only way
3244
    // to figure that is to resolve the CRS from its code...
3245
3.45k
    if (isNull(csNode) && dbContext_ &&
3246
3.45k
        ci_equal(nodeName, WKTConstants::BASEGEODCRS)) {
3247
0
        const auto &nodeChildren = nodeP->children();
3248
0
        for (const auto &subNode : nodeChildren) {
3249
0
            const auto &subNodeName(subNode->GP()->value());
3250
0
            if (ci_equal(subNodeName, WKTConstants::ID) ||
3251
0
                ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
3252
0
                auto id = buildId(node, subNode, true, false);
3253
0
                if (id) {
3254
0
                    try {
3255
0
                        auto authFactory = AuthorityFactory::create(
3256
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
3257
0
                        auto dbCRS = authFactory->createGeodeticCRS(id->code());
3258
0
                        cs = dbCRS->coordinateSystem();
3259
0
                    } catch (const util::Exception &) {
3260
0
                    }
3261
0
                }
3262
0
            }
3263
0
        }
3264
0
    }
3265
3266
3.45k
    auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3267
3.45k
    if (ellipsoidalCS) {
3268
3.12k
        if (ci_equal(nodeName, WKTConstants::GEOCCS)) {
3269
0
            throw ParsingException("ellipsoidal CS not expected in GEOCCS");
3270
0
        }
3271
3.12k
        try {
3272
3.12k
            auto crs = GeographicCRS::create(props, datum, datumEnsemble,
3273
3.12k
                                             NN_NO_CHECK(ellipsoidalCS));
3274
            // In case of missing CS node, or to check it, query the coordinate
3275
            // system from the DB if possible (typically for the baseCRS of a
3276
            // ProjectedCRS)
3277
3.12k
            if (!crs->identifiers().empty() && dbContext_) {
3278
76
                GeographicCRSPtr dbCRS;
3279
76
                try {
3280
76
                    const auto &id = crs->identifiers()[0];
3281
76
                    auto authFactory = AuthorityFactory::create(
3282
76
                        NN_NO_CHECK(dbContext_), *id->codeSpace());
3283
76
                    dbCRS = authFactory->createGeographicCRS(id->code())
3284
76
                                .as_nullable();
3285
76
                } catch (const util::Exception &) {
3286
76
                }
3287
76
                if (dbCRS &&
3288
76
                    (!isNull(csNode) ||
3289
0
                     node->countChildrenOfName(WKTConstants::AXIS) != 0) &&
3290
76
                    !ellipsoidalCS->_isEquivalentTo(
3291
0
                        dbCRS->coordinateSystem().get(),
3292
0
                        util::IComparable::Criterion::EQUIVALENT)) {
3293
0
                    if (unsetIdentifiersIfIncompatibleDef_) {
3294
0
                        emitRecoverableWarning(
3295
0
                            "Coordinate system of GeographicCRS in the WKT "
3296
0
                            "definition is different from the one of the "
3297
0
                            "authority. Unsetting the identifier to avoid "
3298
0
                            "confusion");
3299
0
                        props.unset(Identifier::CODESPACE_KEY);
3300
0
                        props.unset(Identifier::AUTHORITY_KEY);
3301
0
                        props.unset(IdentifiedObject::IDENTIFIERS_KEY);
3302
0
                    }
3303
0
                    crs = GeographicCRS::create(props, datum, datumEnsemble,
3304
0
                                                NN_NO_CHECK(ellipsoidalCS));
3305
76
                } else if (dbCRS) {
3306
0
                    auto csFromDB = dbCRS->coordinateSystem();
3307
0
                    auto csFromDBAltered = csFromDB;
3308
0
                    if (!isNull(nodeP->lookForChild(WKTConstants::UNIT))) {
3309
0
                        csFromDBAltered =
3310
0
                            csFromDB->alterAngularUnit(angularUnit);
3311
0
                        if (unsetIdentifiersIfIncompatibleDef_ &&
3312
0
                            !csFromDBAltered->_isEquivalentTo(
3313
0
                                csFromDB.get(),
3314
0
                                util::IComparable::Criterion::EQUIVALENT)) {
3315
0
                            emitRecoverableWarning(
3316
0
                                "Coordinate system of GeographicCRS in the WKT "
3317
0
                                "definition is different from the one of the "
3318
0
                                "authority. Unsetting the identifier to avoid "
3319
0
                                "confusion");
3320
0
                            props.unset(Identifier::CODESPACE_KEY);
3321
0
                            props.unset(Identifier::AUTHORITY_KEY);
3322
0
                            props.unset(IdentifiedObject::IDENTIFIERS_KEY);
3323
0
                        }
3324
0
                    }
3325
0
                    crs = GeographicCRS::create(props, datum, datumEnsemble,
3326
0
                                                csFromDBAltered);
3327
0
                }
3328
76
            }
3329
3.12k
            return crs;
3330
3.12k
        } catch (const util::Exception &e) {
3331
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3332
0
                                   e.what());
3333
0
        }
3334
3.12k
    } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) ||
3335
329
               ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) ||
3336
329
               ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
3337
        // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3338
0
        throw ParsingException(concat("ellipsoidal CS expected, but found ",
3339
0
                                      cs->getWKT2Type(true)));
3340
0
    }
3341
3342
329
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3343
329
    if (cartesianCS) {
3344
187
        if (cartesianCS->axisList().size() != 3) {
3345
1
            throw ParsingException(
3346
1
                "Cartesian CS for a GeodeticCRS should have 3 axis");
3347
1
        }
3348
186
        try {
3349
186
            return GeodeticCRS::create(props, datum, datumEnsemble,
3350
186
                                       NN_NO_CHECK(cartesianCS));
3351
186
        } catch (const util::Exception &e) {
3352
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3353
0
                                   e.what());
3354
0
        }
3355
186
    }
3356
3357
142
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3358
142
    if (sphericalCS) {
3359
0
        try {
3360
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
3361
0
                                       NN_NO_CHECK(sphericalCS));
3362
0
        } catch (const util::Exception &e) {
3363
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3364
0
                                   e.what());
3365
0
        }
3366
0
    }
3367
3368
142
    throw ParsingException(
3369
142
        concat("unhandled CS type: ", cs->getWKT2Type(true)));
3370
142
}
3371
3372
// ---------------------------------------------------------------------------
3373
3374
4
CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) {
3375
4
    const auto *nodeP = node->GP();
3376
4
    auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS,
3377
4
                                                WKTConstants::BASEGEOGCRS);
3378
    // given the constraints enforced on calling code path
3379
4
    assert(!isNull(baseGeodCRSNode));
3380
3381
4
    auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
3382
3383
4
    auto &derivingConversionNode =
3384
4
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
3385
4
    if (isNull(derivingConversionNode)) {
3386
0
        ThrowMissing(WKTConstants::DERIVINGCONVERSION);
3387
0
    }
3388
4
    auto derivingConversion = buildConversion(
3389
4
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
3390
3391
4
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3392
4
    if (isNull(csNode)) {
3393
0
        ThrowMissing(WKTConstants::CS_);
3394
0
    }
3395
4
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
3396
3397
4
    auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3398
4
    if (ellipsoidalCS) {
3399
3400
0
        if (ellipsoidalCS->axisList().size() == 3 &&
3401
0
            baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
3402
0
            baseGeodCRS =
3403
0
                NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
3404
0
                    baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
3405
0
        }
3406
3407
0
        return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS,
3408
0
                                            derivingConversion,
3409
0
                                            NN_NO_CHECK(ellipsoidalCS));
3410
4
    } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) {
3411
        // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3412
0
        throw ParsingException(concat("ellipsoidal CS expected, but found ",
3413
0
                                      cs->getWKT2Type(true)));
3414
0
    }
3415
3416
4
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3417
4
    if (cartesianCS) {
3418
0
        if (cartesianCS->axisList().size() != 3) {
3419
0
            throw ParsingException(
3420
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
3421
0
        }
3422
0
        return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3423
0
                                          derivingConversion,
3424
0
                                          NN_NO_CHECK(cartesianCS));
3425
0
    }
3426
3427
4
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3428
4
    if (sphericalCS) {
3429
0
        return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3430
0
                                          derivingConversion,
3431
0
                                          NN_NO_CHECK(sphericalCS));
3432
0
    }
3433
3434
4
    throw ParsingException(
3435
4
        concat("unhandled CS type: ", cs->getWKT2Type(true)));
3436
4
}
3437
3438
// ---------------------------------------------------------------------------
3439
3440
UnitOfMeasure WKTParser::Private::guessUnitForParameter(
3441
    const std::string &paramName, const UnitOfMeasure &defaultLinearUnit,
3442
14.6k
    const UnitOfMeasure &defaultAngularUnit) {
3443
14.6k
    UnitOfMeasure unit;
3444
    // scale must be first because of 'Scale factor on pseudo standard parallel'
3445
14.6k
    if (ci_find(paramName, "scale") != std::string::npos ||
3446
14.6k
        ci_find(paramName, "scaling factor") != std::string::npos) {
3447
457
        unit = UnitOfMeasure::SCALE_UNITY;
3448
14.2k
    } else if (ci_find(paramName, "latitude") != std::string::npos ||
3449
14.2k
               ci_find(paramName, "longitude") != std::string::npos ||
3450
14.2k
               ci_find(paramName, "meridian") != std::string::npos ||
3451
14.2k
               ci_find(paramName, "parallel") != std::string::npos ||
3452
14.2k
               ci_find(paramName, "azimuth") != std::string::npos ||
3453
14.2k
               ci_find(paramName, "angle") != std::string::npos ||
3454
14.2k
               ci_find(paramName, "heading") != std::string::npos ||
3455
14.2k
               ci_find(paramName, "rotation") != std::string::npos) {
3456
2.39k
        unit = defaultAngularUnit;
3457
11.8k
    } else if (ci_find(paramName, "easting") != std::string::npos ||
3458
11.8k
               ci_find(paramName, "northing") != std::string::npos ||
3459
11.8k
               ci_find(paramName, "height") != std::string::npos) {
3460
605
        unit = defaultLinearUnit;
3461
605
    }
3462
14.6k
    return unit;
3463
14.6k
}
3464
3465
// ---------------------------------------------------------------------------
3466
3467
static bool
3468
5.54k
isEPSGCodeForInterpolationParameter(const OperationParameterNNPtr &parameter) {
3469
5.54k
    const auto &name = parameter->nameStr();
3470
5.54k
    const auto epsgCode = parameter->getEPSGCode();
3471
5.54k
    return name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3472
5.54k
           epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3473
5.54k
           name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS ||
3474
5.54k
           epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS;
3475
5.54k
}
3476
3477
// ---------------------------------------------------------------------------
3478
3479
3.55k
static bool isIntegerParameter(const OperationParameterNNPtr &parameter) {
3480
3.55k
    return isEPSGCodeForInterpolationParameter(parameter);
3481
3.55k
}
3482
3483
// ---------------------------------------------------------------------------
3484
3485
void WKTParser::Private::consumeParameters(
3486
    const WKTNodeNNPtr &node, bool isAbridged,
3487
    std::vector<OperationParameterNNPtr> &parameters,
3488
    std::vector<ParameterValueNNPtr> &values,
3489
    const UnitOfMeasure &defaultLinearUnit,
3490
2.56k
    const UnitOfMeasure &defaultAngularUnit) {
3491
17.6k
    for (const auto &childNode : node->GP()->children()) {
3492
17.6k
        const auto &childNodeChildren = childNode->GP()->children();
3493
17.6k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3494
3.87k
            if (childNodeChildren.size() < 2) {
3495
33
                ThrowNotEnoughChildren(childNode->GP()->value());
3496
33
            }
3497
3.83k
            parameters.push_back(
3498
3.83k
                OperationParameter::create(buildProperties(childNode)));
3499
3.83k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
3500
3.83k
            if (!paramValue.empty() && paramValue[0] == '"') {
3501
232
                values.push_back(
3502
232
                    ParameterValue::create(stripQuotes(childNodeChildren[1])));
3503
3.60k
            } else {
3504
3.60k
                try {
3505
3.60k
                    double val = asDouble(childNodeChildren[1]);
3506
3.60k
                    auto unit = buildUnitInSubNode(childNode);
3507
3.60k
                    if (unit == UnitOfMeasure::NONE) {
3508
3.55k
                        const auto &paramName =
3509
3.55k
                            childNodeChildren[0]->GP()->value();
3510
3.55k
                        unit = guessUnitForParameter(
3511
3.55k
                            paramName, defaultLinearUnit, defaultAngularUnit);
3512
3.55k
                    }
3513
3514
3.60k
                    if (isAbridged) {
3515
0
                        const auto &paramName = parameters.back()->nameStr();
3516
0
                        int paramEPSGCode = 0;
3517
0
                        const auto &paramIds = parameters.back()->identifiers();
3518
0
                        if (paramIds.size() == 1 &&
3519
0
                            ci_equal(*(paramIds[0]->codeSpace()),
3520
0
                                     Identifier::EPSG)) {
3521
0
                            paramEPSGCode = ::atoi(paramIds[0]->code().c_str());
3522
0
                        }
3523
0
                        const common::UnitOfMeasure *pUnit = nullptr;
3524
0
                        if (OperationParameterValue::convertFromAbridged(
3525
0
                                paramName, val, pUnit, paramEPSGCode)) {
3526
0
                            unit = *pUnit;
3527
0
                            parameters.back() = OperationParameter::create(
3528
0
                                buildProperties(childNode)
3529
0
                                    .set(Identifier::CODESPACE_KEY,
3530
0
                                         Identifier::EPSG)
3531
0
                                    .set(Identifier::CODE_KEY, paramEPSGCode));
3532
0
                        }
3533
0
                    }
3534
3535
3.60k
                    if (isIntegerParameter(parameters.back())) {
3536
237
                        values.push_back(ParameterValue::create(
3537
237
                            std::stoi(childNodeChildren[1]->GP()->value())));
3538
3.37k
                    } else {
3539
3.37k
                        values.push_back(
3540
3.37k
                            ParameterValue::create(Measure(val, unit)));
3541
3.37k
                    }
3542
3.60k
                } catch (const std::exception &) {
3543
51
                    throw ParsingException(concat(
3544
51
                        "unhandled parameter value type : ", paramValue));
3545
51
                }
3546
3.60k
            }
3547
13.8k
        } else if (ci_equal(childNode->GP()->value(),
3548
13.8k
                            WKTConstants::PARAMETERFILE)) {
3549
297
            if (childNodeChildren.size() < 2) {
3550
2
                ThrowNotEnoughChildren(childNode->GP()->value());
3551
2
            }
3552
295
            parameters.push_back(
3553
295
                OperationParameter::create(buildProperties(childNode)));
3554
295
            values.push_back(ParameterValue::createFilename(
3555
295
                stripQuotes(childNodeChildren[1])));
3556
295
        }
3557
17.6k
    }
3558
2.56k
}
3559
3560
// ---------------------------------------------------------------------------
3561
3562
static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
3563
    DatabaseContextPtr &dbContext,
3564
    std::vector<OperationParameterNNPtr> &parameters,
3565
    std::vector<ParameterValueNNPtr> &values);
3566
3567
ConversionNNPtr
3568
WKTParser::Private::buildConversion(const WKTNodeNNPtr &node,
3569
                                    const UnitOfMeasure &defaultLinearUnit,
3570
2.58k
                                    const UnitOfMeasure &defaultAngularUnit) {
3571
2.58k
    auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD,
3572
2.58k
                                                WKTConstants::PROJECTION);
3573
2.58k
    if (isNull(methodNode)) {
3574
18
        ThrowMissing(WKTConstants::METHOD);
3575
18
    }
3576
2.56k
    if (methodNode->GP()->childrenSize() == 0) {
3577
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3578
0
    }
3579
3580
2.56k
    std::vector<OperationParameterNNPtr> parameters;
3581
2.56k
    std::vector<ParameterValueNNPtr> values;
3582
2.56k
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3583
2.56k
                      defaultAngularUnit);
3584
3585
2.56k
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
3586
2.56k
        dbContext_, parameters, values);
3587
3588
2.56k
    auto &convProps = buildProperties(node);
3589
2.56k
    auto &methodProps = buildProperties(methodNode);
3590
2.56k
    std::string convName;
3591
2.56k
    std::string methodName;
3592
2.56k
    if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
3593
2.56k
        methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
3594
2.56k
        starts_with(convName, "Inverse of ") &&
3595
2.56k
        starts_with(methodName, "Inverse of ")) {
3596
3597
0
        auto &invConvProps = buildProperties(node, true);
3598
0
        auto &invMethodProps = buildProperties(methodNode, true);
3599
0
        auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
3600
0
            Conversion::create(invConvProps, invMethodProps, parameters, values)
3601
0
                ->inverse()));
3602
0
        if (interpolationCRS)
3603
0
            conv->setInterpolationCRS(interpolationCRS);
3604
0
        return conv;
3605
0
    }
3606
2.56k
    auto conv = Conversion::create(convProps, methodProps, parameters, values);
3607
2.56k
    if (interpolationCRS)
3608
0
        conv->setInterpolationCRS(interpolationCRS);
3609
2.56k
    return conv;
3610
2.56k
}
3611
3612
// ---------------------------------------------------------------------------
3613
3614
static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
3615
    DatabaseContextPtr &dbContext,
3616
    std::vector<OperationParameterNNPtr> &parameters,
3617
2.48k
    std::vector<ParameterValueNNPtr> &values) {
3618
    // Transform EPSG hacky PARAMETER["EPSG code for Interpolation CRS",
3619
    // crs_epsg_code] into proper interpolation CRS
3620
2.48k
    if (dbContext != nullptr) {
3621
4.19k
        for (size_t i = 0; i < parameters.size(); ++i) {
3622
1.98k
            if (isEPSGCodeForInterpolationParameter(parameters[i])) {
3623
96
                const int code = values[i]->integerValue();
3624
96
                try {
3625
96
                    auto authFactory = AuthorityFactory::create(
3626
96
                        NN_NO_CHECK(dbContext), Identifier::EPSG);
3627
96
                    auto interpolationCRS =
3628
96
                        authFactory
3629
96
                            ->createGeographicCRS(internal::toString(code))
3630
96
                            .as_nullable();
3631
96
                    parameters.erase(parameters.begin() + i);
3632
96
                    values.erase(values.begin() + i);
3633
96
                    return interpolationCRS;
3634
96
                } catch (const util::Exception &) {
3635
96
                }
3636
96
            }
3637
1.98k
        }
3638
2.20k
    }
3639
2.48k
    return nullptr;
3640
2.48k
}
3641
3642
// ---------------------------------------------------------------------------
3643
3644
TransformationNNPtr
3645
0
WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) {
3646
0
    const auto *nodeP = node->GP();
3647
0
    auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3648
0
    if (isNull(methodNode)) {
3649
0
        ThrowMissing(WKTConstants::METHOD);
3650
0
    }
3651
0
    if (methodNode->GP()->childrenSize() == 0) {
3652
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3653
0
    }
3654
3655
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3656
0
    if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3657
0
        ThrowMissing(WKTConstants::SOURCECRS);
3658
0
    }
3659
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3660
0
    if (!sourceCRS) {
3661
0
        throw ParsingException("Invalid content in SOURCECRS node");
3662
0
    }
3663
3664
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3665
0
    if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3666
0
        ThrowMissing(WKTConstants::TARGETCRS);
3667
0
    }
3668
0
    auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3669
0
    if (!targetCRS) {
3670
0
        throw ParsingException("Invalid content in TARGETCRS node");
3671
0
    }
3672
3673
0
    auto &interpolationCRSNode =
3674
0
        nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS);
3675
0
    CRSPtr interpolationCRS;
3676
0
    if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP()
3677
0
            ->childrenSize() == 1) {
3678
0
        interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]);
3679
0
    }
3680
3681
0
    std::vector<OperationParameterNNPtr> parameters;
3682
0
    std::vector<ParameterValueNNPtr> values;
3683
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
3684
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
3685
0
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3686
0
                      defaultAngularUnit);
3687
3688
0
    if (interpolationCRS == nullptr)
3689
0
        interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
3690
0
            dbContext_, parameters, values);
3691
3692
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3693
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3694
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3695
0
        accuracies.push_back(PositionalAccuracy::create(
3696
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3697
0
    }
3698
3699
0
    return Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS),
3700
0
                                  NN_NO_CHECK(targetCRS), interpolationCRS,
3701
0
                                  buildProperties(methodNode), parameters,
3702
0
                                  values, accuracies);
3703
0
}
3704
3705
// ---------------------------------------------------------------------------
3706
3707
PointMotionOperationNNPtr
3708
0
WKTParser::Private::buildPointMotionOperation(const WKTNodeNNPtr &node) {
3709
0
    const auto *nodeP = node->GP();
3710
0
    auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3711
0
    if (isNull(methodNode)) {
3712
0
        ThrowMissing(WKTConstants::METHOD);
3713
0
    }
3714
0
    if (methodNode->GP()->childrenSize() == 0) {
3715
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3716
0
    }
3717
3718
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3719
0
    if (sourceCRSNode->GP()->childrenSize() != 1) {
3720
0
        ThrowMissing(WKTConstants::SOURCECRS);
3721
0
    }
3722
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3723
0
    if (!sourceCRS) {
3724
0
        throw ParsingException("Invalid content in SOURCECRS node");
3725
0
    }
3726
3727
0
    std::vector<OperationParameterNNPtr> parameters;
3728
0
    std::vector<ParameterValueNNPtr> values;
3729
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
3730
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
3731
0
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3732
0
                      defaultAngularUnit);
3733
3734
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3735
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3736
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3737
0
        accuracies.push_back(PositionalAccuracy::create(
3738
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3739
0
    }
3740
3741
0
    return PointMotionOperation::create(
3742
0
        buildProperties(node), NN_NO_CHECK(sourceCRS),
3743
0
        buildProperties(methodNode), parameters, values, accuracies);
3744
0
}
3745
3746
// ---------------------------------------------------------------------------
3747
3748
ConcatenatedOperationNNPtr
3749
0
WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) {
3750
3751
0
    const auto *nodeP = node->GP();
3752
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3753
0
    if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3754
0
        ThrowMissing(WKTConstants::SOURCECRS);
3755
0
    }
3756
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3757
0
    if (!sourceCRS) {
3758
0
        throw ParsingException("Invalid content in SOURCECRS node");
3759
0
    }
3760
3761
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3762
0
    if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3763
0
        ThrowMissing(WKTConstants::TARGETCRS);
3764
0
    }
3765
0
    auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3766
0
    if (!targetCRS) {
3767
0
        throw ParsingException("Invalid content in TARGETCRS node");
3768
0
    }
3769
3770
0
    std::vector<CoordinateOperationNNPtr> operations;
3771
0
    for (const auto &childNode : nodeP->children()) {
3772
0
        if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) {
3773
0
            if (childNode->GP()->childrenSize() != 1) {
3774
0
                throw ParsingException("Invalid content in STEP node");
3775
0
            }
3776
0
            auto op = nn_dynamic_pointer_cast<CoordinateOperation>(
3777
0
                build(childNode->GP()->children()[0]));
3778
0
            if (!op) {
3779
0
                throw ParsingException("Invalid content in STEP node");
3780
0
            }
3781
0
            operations.emplace_back(NN_NO_CHECK(op));
3782
0
        }
3783
0
    }
3784
3785
0
    ConcatenatedOperation::fixSteps(
3786
0
        NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), operations, dbContext_,
3787
0
        /* fixDirectionAllowed = */ true);
3788
3789
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3790
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3791
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3792
0
        accuracies.push_back(PositionalAccuracy::create(
3793
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3794
0
    }
3795
3796
0
    try {
3797
0
        return ConcatenatedOperation::create(buildProperties(node), operations,
3798
0
                                             accuracies);
3799
0
    } catch (const InvalidOperation &e) {
3800
0
        throw ParsingException(
3801
0
            std::string("Cannot build concatenated operation: ") + e.what());
3802
0
    }
3803
0
}
3804
3805
// ---------------------------------------------------------------------------
3806
3807
bool WKTParser::Private::hasWebMercPROJ4String(
3808
1.21k
    const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) {
3809
1.21k
    if (projectionNode->GP()->childrenSize() == 0) {
3810
3
        ThrowNotEnoughChildren(WKTConstants::PROJECTION);
3811
3
    }
3812
1.21k
    const std::string wkt1ProjectionName =
3813
1.21k
        stripQuotes(projectionNode->GP()->children()[0]);
3814
3815
1.21k
    auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
3816
3817
1.21k
    if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
3818
1.21k
                                               "Mercator_1SP") &&
3819
1.21k
        projCRSNode->countChildrenOfName("center_latitude") == 0) {
3820
3821
        // Hack to detect the hacky way of encodign webmerc in GDAL WKT1
3822
        // with a EXTENSION["PROJ4", "+proj=merc +a=6378137 +b=6378137
3823
        // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m
3824
        // +nadgrids=@null +wktext +no_defs"] node
3825
0
        if (extensionNode && extensionNode->GP()->childrenSize() == 2 &&
3826
0
            ci_equal(stripQuotes(extensionNode->GP()->children()[0]),
3827
0
                     "PROJ4")) {
3828
0
            std::string projString =
3829
0
                stripQuotes(extensionNode->GP()->children()[1]);
3830
0
            if (projString.find("+proj=merc") != std::string::npos &&
3831
0
                projString.find("+a=6378137") != std::string::npos &&
3832
0
                projString.find("+b=6378137") != std::string::npos &&
3833
0
                projString.find("+lon_0=0") != std::string::npos &&
3834
0
                projString.find("+x_0=0") != std::string::npos &&
3835
0
                projString.find("+y_0=0") != std::string::npos &&
3836
0
                projString.find("+nadgrids=@null") != std::string::npos &&
3837
0
                (projString.find("+lat_ts=") == std::string::npos ||
3838
0
                 projString.find("+lat_ts=0") != std::string::npos) &&
3839
0
                (projString.find("+k=") == std::string::npos ||
3840
0
                 projString.find("+k=1") != std::string::npos) &&
3841
0
                (projString.find("+units=") == std::string::npos ||
3842
0
                 projString.find("+units=m") != std::string::npos)) {
3843
0
                return true;
3844
0
            }
3845
0
        }
3846
0
    }
3847
1.21k
    return false;
3848
1.21k
}
3849
3850
// ---------------------------------------------------------------------------
3851
3852
static const MethodMapping *
3853
selectSphericalOrEllipsoidal(const MethodMapping *mapping,
3854
20.9k
                             const GeodeticCRSNNPtr &baseGeodCRS) {
3855
20.9k
    if (mapping->epsg_code ==
3856
20.9k
            EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL ||
3857
20.9k
        mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA) {
3858
1.03k
        mapping = getMapping(
3859
1.03k
            baseGeodCRS->ellipsoid()->isSphere()
3860
1.03k
                ? EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL
3861
1.03k
                : EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA);
3862
19.9k
    } else if (mapping->epsg_code ==
3863
19.9k
                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL ||
3864
19.9k
               mapping->epsg_code ==
3865
19.9k
                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
3866
333
        mapping = getMapping(
3867
333
            baseGeodCRS->ellipsoid()->isSphere()
3868
333
                ? EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL
3869
333
                : EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA);
3870
19.6k
    } else if (mapping->epsg_code ==
3871
19.6k
                   EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL ||
3872
19.6k
               mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL) {
3873
24
        mapping =
3874
24
            getMapping(baseGeodCRS->ellipsoid()->isSphere()
3875
24
                           ? EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL
3876
24
                           : EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL);
3877
24
    }
3878
20.9k
    return mapping;
3879
20.9k
}
3880
3881
// ---------------------------------------------------------------------------
3882
3883
const ESRIMethodMapping *WKTParser::Private::getESRIMapping(
3884
    const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode,
3885
1.17k
    std::map<std::string, std::string, ci_less_struct> &mapParamNameToValue) {
3886
1.17k
    const std::string esriProjectionName =
3887
1.17k
        stripQuotes(projectionNode->GP()->children()[0]);
3888
3889
    // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods
3890
    // depending
3891
    // on the parameters / their values
3892
1.17k
    const auto esriMappings = getMappingsFromESRI(esriProjectionName);
3893
1.17k
    if (esriMappings.empty()) {
3894
705
        return nullptr;
3895
705
    }
3896
3897
    // Build a map of present parameters
3898
6.04k
    for (const auto &childNode : projCRSNode->GP()->children()) {
3899
6.04k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3900
1.35k
            const auto &childNodeChildren = childNode->GP()->children();
3901
1.35k
            if (childNodeChildren.size() < 2) {
3902
5
                ThrowNotEnoughChildren(WKTConstants::PARAMETER);
3903
5
            }
3904
1.34k
            const std::string parameterName(stripQuotes(childNodeChildren[0]));
3905
1.34k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
3906
1.34k
            mapParamNameToValue[parameterName] = paramValue;
3907
1.34k
        }
3908
6.04k
    }
3909
3910
    // Compare parameters present with the ones expected in the mapping
3911
469
    const ESRIMethodMapping *esriMapping = nullptr;
3912
469
    int bestMatchCount = -1;
3913
786
    for (const auto &mapping : esriMappings) {
3914
786
        int matchCount = 0;
3915
786
        int unmatchCount = 0;
3916
7.63k
        for (const auto *param = mapping->params; param->esri_name; ++param) {
3917
6.84k
            auto iter = mapParamNameToValue.find(param->esri_name);
3918
6.84k
            if (iter != mapParamNameToValue.end()) {
3919
283
                if (param->wkt2_name == nullptr) {
3920
5
                    bool ok = true;
3921
5
                    try {
3922
5
                        if (io::asDouble(param->fixed_value) ==
3923
5
                            io::asDouble(iter->second)) {
3924
5
                            matchCount++;
3925
5
                        } else {
3926
0
                            ok = false;
3927
0
                        }
3928
5
                    } catch (const std::exception &) {
3929
0
                        ok = false;
3930
0
                    }
3931
5
                    if (!ok) {
3932
0
                        matchCount = -1;
3933
0
                        break;
3934
0
                    }
3935
278
                } else {
3936
278
                    matchCount++;
3937
278
                }
3938
6.56k
            } else if (param->is_fixed_value) {
3939
0
                mapParamNameToValue[param->esri_name] = param->fixed_value;
3940
6.56k
            } else {
3941
6.56k
                unmatchCount++;
3942
6.56k
            }
3943
6.84k
        }
3944
786
        if (matchCount > bestMatchCount &&
3945
786
            !(maybeEsriStyle_ && unmatchCount >= matchCount)) {
3946
469
            esriMapping = mapping;
3947
469
            bestMatchCount = matchCount;
3948
469
        }
3949
786
    }
3950
3951
469
    return esriMapping;
3952
469
}
3953
3954
// ---------------------------------------------------------------------------
3955
3956
ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
3957
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
3958
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
3959
    const UnitOfMeasure &defaultAngularUnit,
3960
    const ESRIMethodMapping *esriMapping,
3961
469
    std::map<std::string, std::string, ci_less_struct> &mapParamNameToValue) {
3962
469
    std::map<std::string, const char *> mapWKT2NameToESRIName;
3963
4.16k
    for (const auto *param = esriMapping->params; param->esri_name; ++param) {
3964
3.70k
        if (param->wkt2_name) {
3965
2.75k
            mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name;
3966
2.75k
        }
3967
3.70k
    }
3968
3969
469
    const std::string esriProjectionName =
3970
469
        stripQuotes(projectionNode->GP()->children()[0]);
3971
469
    const char *projectionMethodWkt2Name = esriMapping->wkt2_name;
3972
469
    if (ci_equal(esriProjectionName, "Krovak")) {
3973
312
        const std::string projCRSName =
3974
312
            stripQuotes(projCRSNode->GP()->children()[0]);
3975
312
        if (projCRSName.find("_East_North") != std::string::npos) {
3976
0
            projectionMethodWkt2Name = EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED;
3977
0
        }
3978
312
    }
3979
3980
469
    const auto *wkt2_mapping = getMapping(projectionMethodWkt2Name);
3981
469
    if (ci_equal(esriProjectionName, "Stereographic")) {
3982
4
        try {
3983
4
            const auto iterLatitudeOfOrigin =
3984
4
                mapParamNameToValue.find("Latitude_Of_Origin");
3985
4
            if (iterLatitudeOfOrigin != mapParamNameToValue.end() &&
3986
4
                std::fabs(io::asDouble(iterLatitudeOfOrigin->second)) == 90.0) {
3987
4
                wkt2_mapping =
3988
4
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
3989
4
            }
3990
4
        } catch (const std::exception &) {
3991
0
        }
3992
4
    }
3993
469
    assert(wkt2_mapping);
3994
3995
469
    wkt2_mapping = selectSphericalOrEllipsoidal(wkt2_mapping, baseGeodCRS);
3996
3997
469
    PropertyMap propertiesMethod;
3998
469
    propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name);
3999
469
    if (wkt2_mapping->epsg_code != 0) {
4000
369
        propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code);
4001
369
        propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4002
369
    }
4003
4004
469
    std::vector<OperationParameterNNPtr> parameters;
4005
469
    std::vector<ParameterValueNNPtr> values;
4006
4007
469
    if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
4008
469
        ci_equal(esriProjectionName, "Plate_Carree")) {
4009
        // Add a fixed  Latitude of 1st parallel = 0 so as to have all
4010
        // parameters expected by Equidistant Cylindrical.
4011
1
        mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] =
4012
1
            "Standard_Parallel_1";
4013
1
        mapParamNameToValue["Standard_Parallel_1"] = "0";
4014
468
    } else if ((wkt2_mapping->epsg_code ==
4015
468
                    EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4016
468
                wkt2_mapping->epsg_code ==
4017
466
                    EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) &&
4018
468
               !ci_equal(esriProjectionName,
4019
2
                         "Rectified_Skew_Orthomorphic_Natural_Origin") &&
4020
468
               !ci_equal(esriProjectionName,
4021
0
                         "Rectified_Skew_Orthomorphic_Center")) {
4022
        // ESRI WKT lacks the angle to skew grid
4023
        // Take it from the azimuth value
4024
0
        mapWKT2NameToESRIName
4025
0
            [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth";
4026
0
    }
4027
4028
3.21k
    for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) {
4029
2.74k
        const auto *paramMapping = wkt2_mapping->params[i];
4030
4031
2.74k
        auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name);
4032
2.74k
        if (iter == mapWKT2NameToESRIName.end()) {
4033
7
            continue;
4034
7
        }
4035
2.73k
        const auto &esriParamName = iter->second;
4036
2.73k
        auto iter2 = mapParamNameToValue.find(esriParamName);
4037
2.73k
        auto mapParamNameToValueEnd = mapParamNameToValue.end();
4038
2.73k
        if (iter2 == mapParamNameToValueEnd) {
4039
            // In case we don't find a direct match, try the aliases
4040
2.46k
            for (iter2 = mapParamNameToValue.begin();
4041
8.31k
                 iter2 != mapParamNameToValueEnd; ++iter2) {
4042
5.86k
                if (areEquivalentParameters(iter2->first, esriParamName)) {
4043
19
                    break;
4044
19
                }
4045
5.86k
            }
4046
2.46k
            if (iter2 == mapParamNameToValueEnd) {
4047
2.44k
                continue;
4048
2.44k
            }
4049
2.46k
        }
4050
4051
292
        PropertyMap propertiesParameter;
4052
292
        propertiesParameter.set(IdentifiedObject::NAME_KEY,
4053
292
                                paramMapping->wkt2_name);
4054
292
        if (paramMapping->epsg_code != 0) {
4055
275
            propertiesParameter.set(Identifier::CODE_KEY,
4056
275
                                    paramMapping->epsg_code);
4057
275
            propertiesParameter.set(Identifier::CODESPACE_KEY,
4058
275
                                    Identifier::EPSG);
4059
275
        }
4060
292
        parameters.push_back(OperationParameter::create(propertiesParameter));
4061
4062
292
        try {
4063
292
            double val = io::asDouble(iter2->second);
4064
292
            auto unit = guessUnitForParameter(
4065
292
                paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit);
4066
292
            values.push_back(ParameterValue::create(Measure(val, unit)));
4067
292
        } catch (const std::exception &) {
4068
4
            throw ParsingException(
4069
4
                concat("unhandled parameter value type : ", iter2->second));
4070
4
        }
4071
292
    }
4072
4073
465
    return Conversion::create(
4074
465
               PropertyMap().set(IdentifiedObject::NAME_KEY,
4075
465
                                 esriProjectionName == "Gauss_Kruger"
4076
465
                                     ? "unnnamed (Gauss Kruger)"
4077
465
                                     : "unnamed"),
4078
465
               propertiesMethod, parameters, values)
4079
465
        ->identify();
4080
469
}
4081
4082
// ---------------------------------------------------------------------------
4083
4084
ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
4085
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4086
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4087
715
    const UnitOfMeasure &defaultAngularUnit) {
4088
4089
715
    std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
4090
715
    const auto esriMapping =
4091
715
        getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue);
4092
715
    if (esriMapping == nullptr) {
4093
262
        return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
4094
262
                                       defaultLinearUnit, defaultAngularUnit);
4095
262
    }
4096
4097
453
    return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
4098
453
                                   defaultLinearUnit, defaultAngularUnit,
4099
453
                                   esriMapping, mapParamNameToValue);
4100
715
}
4101
4102
// ---------------------------------------------------------------------------
4103
4104
ConversionNNPtr WKTParser::Private::buildProjection(
4105
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4106
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4107
1.21k
    const UnitOfMeasure &defaultAngularUnit) {
4108
1.21k
    if (projectionNode->GP()->childrenSize() == 0) {
4109
0
        ThrowNotEnoughChildren(WKTConstants::PROJECTION);
4110
0
    }
4111
1.21k
    if (esriStyle_ || maybeEsriStyle_) {
4112
715
        return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
4113
715
                                       defaultLinearUnit, defaultAngularUnit);
4114
715
    }
4115
499
    return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
4116
499
                                   defaultLinearUnit, defaultAngularUnit);
4117
1.21k
}
4118
4119
// ---------------------------------------------------------------------------
4120
4121
std::string
4122
WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
4123
0
                                           const char *paramName) {
4124
0
    for (const auto &childNode : projCRSNode->GP()->children()) {
4125
0
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4126
0
            const auto &childNodeChildren = childNode->GP()->children();
4127
0
            if (childNodeChildren.size() == 2 &&
4128
0
                metadata::Identifier::isEquivalentName(
4129
0
                    stripQuotes(childNodeChildren[0]).c_str(), paramName)) {
4130
0
                return childNodeChildren[1]->GP()->value();
4131
0
            }
4132
0
        }
4133
0
    }
4134
0
    return std::string();
4135
0
}
4136
4137
// ---------------------------------------------------------------------------
4138
4139
ConversionNNPtr WKTParser::Private::buildProjectionStandard(
4140
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4141
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4142
761
    const UnitOfMeasure &defaultAngularUnit) {
4143
761
    std::string wkt1ProjectionName =
4144
761
        stripQuotes(projectionNode->GP()->children()[0]);
4145
4146
761
    std::vector<OperationParameterNNPtr> parameters;
4147
761
    std::vector<ParameterValueNNPtr> values;
4148
761
    bool tryToIdentifyWKT1Method = true;
4149
4150
761
    auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
4151
761
    const auto &extensionChildren = extensionNode->GP()->children();
4152
4153
761
    bool gdal_3026_hack = false;
4154
761
    if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
4155
761
                                               "Mercator_1SP") &&
4156
761
        projectionGetParameter(projCRSNode, "center_latitude").empty()) {
4157
4158
        // Hack for https://trac.osgeo.org/gdal/ticket/3026
4159
0
        std::string lat0(
4160
0
            projectionGetParameter(projCRSNode, "latitude_of_origin"));
4161
0
        if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") {
4162
0
            wkt1ProjectionName = "Mercator_2SP";
4163
0
            gdal_3026_hack = true;
4164
0
        } else {
4165
            // The latitude of origin, which should always be zero, is
4166
            // missing
4167
            // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP
4168
            // definition,
4169
            // so add it manually.
4170
0
            PropertyMap propertiesParameter;
4171
0
            propertiesParameter.set(IdentifiedObject::NAME_KEY,
4172
0
                                    "Latitude of natural origin");
4173
0
            propertiesParameter.set(Identifier::CODE_KEY, 8801);
4174
0
            propertiesParameter.set(Identifier::CODESPACE_KEY,
4175
0
                                    Identifier::EPSG);
4176
0
            parameters.push_back(
4177
0
                OperationParameter::create(propertiesParameter));
4178
0
            values.push_back(
4179
0
                ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE)));
4180
0
        }
4181
4182
761
    } else if (metadata::Identifier::isEquivalentName(
4183
761
                   wkt1ProjectionName.c_str(), "Polar_Stereographic")) {
4184
142
        std::map<std::string, Measure> mapParameters;
4185
8.05k
        for (const auto &childNode : projCRSNode->GP()->children()) {
4186
8.05k
            const auto &childNodeChildren = childNode->GP()->children();
4187
8.05k
            if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
4188
8.05k
                childNodeChildren.size() == 2) {
4189
5.39k
                const std::string wkt1ParameterName(
4190
5.39k
                    stripQuotes(childNodeChildren[0]));
4191
5.39k
                try {
4192
5.39k
                    double val = asDouble(childNodeChildren[1]);
4193
5.39k
                    auto unit = guessUnitForParameter(wkt1ParameterName,
4194
5.39k
                                                      defaultLinearUnit,
4195
5.39k
                                                      defaultAngularUnit);
4196
5.39k
                    mapParameters.insert(std::pair<std::string, Measure>(
4197
5.39k
                        tolower(wkt1ParameterName), Measure(val, unit)));
4198
5.39k
                } catch (const std::exception &) {
4199
62
                }
4200
5.39k
            }
4201
8.05k
        }
4202
4203
142
        Measure latitudeOfOrigin = mapParameters["latitude_of_origin"];
4204
142
        Measure centralMeridian = mapParameters["central_meridian"];
4205
142
        Measure scaleFactorFromMap = mapParameters["scale_factor"];
4206
142
        Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE)
4207
142
                                ? Measure(1.0, UnitOfMeasure::SCALE_UNITY)
4208
142
                                : scaleFactorFromMap);
4209
142
        Measure falseEasting = mapParameters["false_easting"];
4210
142
        Measure falseNorthing = mapParameters["false_northing"];
4211
142
        if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
4212
142
            scaleFactor.getSIValue() == 1.0) {
4213
0
            return Conversion::createPolarStereographicVariantB(
4214
0
                PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4215
0
                Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
4216
0
                Angle(centralMeridian.value(), centralMeridian.unit()),
4217
0
                Length(falseEasting.value(), falseEasting.unit()),
4218
0
                Length(falseNorthing.value(), falseNorthing.unit()));
4219
0
        }
4220
4221
142
        if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
4222
142
            std::fabs(std::fabs(latitudeOfOrigin.convertToUnit(
4223
0
                          UnitOfMeasure::DEGREE)) -
4224
0
                      90.0) < 1e-10) {
4225
0
            return Conversion::createPolarStereographicVariantA(
4226
0
                PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4227
0
                Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
4228
0
                Angle(centralMeridian.value(), centralMeridian.unit()),
4229
0
                Scale(scaleFactor.value(), scaleFactor.unit()),
4230
0
                Length(falseEasting.value(), falseEasting.unit()),
4231
0
                Length(falseNorthing.value(), falseNorthing.unit()));
4232
0
        }
4233
4234
142
        tryToIdentifyWKT1Method = false;
4235
        // Import GDAL PROJ4 extension nodes
4236
619
    } else if (extensionChildren.size() == 2 &&
4237
619
               ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
4238
0
        std::string projString = stripQuotes(extensionChildren[1]);
4239
0
        if (starts_with(projString, "+proj=")) {
4240
0
            if (projString.find(" +type=crs") == std::string::npos) {
4241
0
                projString += " +type=crs";
4242
0
            }
4243
0
            try {
4244
0
                auto projObj =
4245
0
                    PROJStringParser().createFromPROJString(projString);
4246
0
                auto projObjCrs =
4247
0
                    nn_dynamic_pointer_cast<ProjectedCRS>(projObj);
4248
0
                if (projObjCrs) {
4249
0
                    return projObjCrs->derivingConversion();
4250
0
                }
4251
0
            } catch (const io::ParsingException &) {
4252
0
            }
4253
0
        }
4254
0
    }
4255
4256
761
    std::string projectionName(std::move(wkt1ProjectionName));
4257
761
    const MethodMapping *mapping =
4258
761
        tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr;
4259
4260
761
    if (!mapping) {
4261
        // Sometimes non-WKT1:ESRI looking WKT can actually use WKT1:ESRI
4262
        // projection definitions
4263
464
        std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
4264
464
        const auto esriMapping =
4265
464
            getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue);
4266
464
        if (esriMapping != nullptr) {
4267
19
            return buildProjectionFromESRI(
4268
19
                baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit,
4269
19
                defaultAngularUnit, esriMapping, mapParamNameToValue);
4270
19
        }
4271
464
    }
4272
4273
742
    if (mapping) {
4274
297
        mapping = selectSphericalOrEllipsoidal(mapping, baseGeodCRS);
4275
445
    } else if (metadata::Identifier::isEquivalentName(
4276
445
                   projectionName.c_str(), "Lambert Conformal Conic")) {
4277
        // Lambert Conformal Conic or Lambert_Conformal_Conic are respectively
4278
        // used by Oracle WKT and Trimble for either LCC 1SP or 2SP, so we
4279
        // have to look at parameters to figure out the variant.
4280
9
        bool found2ndStdParallel = false;
4281
9
        bool foundScaleFactor = false;
4282
106
        for (const auto &childNode : projCRSNode->GP()->children()) {
4283
106
            if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4284
41
                const auto &childNodeChildren = childNode->GP()->children();
4285
41
                if (childNodeChildren.size() < 2) {
4286
1
                    ThrowNotEnoughChildren(WKTConstants::PARAMETER);
4287
1
                }
4288
40
                const std::string wkt1ParameterName(
4289
40
                    stripQuotes(childNodeChildren[0]));
4290
40
                if (metadata::Identifier::isEquivalentName(
4291
40
                        wkt1ParameterName.c_str(), WKT1_STANDARD_PARALLEL_2)) {
4292
0
                    found2ndStdParallel = true;
4293
40
                } else if (metadata::Identifier::isEquivalentName(
4294
40
                               wkt1ParameterName.c_str(), WKT1_SCALE_FACTOR)) {
4295
0
                    foundScaleFactor = true;
4296
0
                }
4297
40
            }
4298
106
        }
4299
8
        if (found2ndStdParallel && !foundScaleFactor) {
4300
0
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
4301
8
        } else if (!found2ndStdParallel && foundScaleFactor) {
4302
0
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
4303
8
        } else if (found2ndStdParallel && foundScaleFactor) {
4304
            // Not sure if that happens
4305
0
            mapping = getMapping(
4306
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
4307
0
        }
4308
8
    }
4309
4310
    // For Krovak, we need to look at axis to decide between the Krovak and
4311
    // Krovak East-North Oriented methods
4312
741
    if (ci_equal(projectionName, "Krovak") &&
4313
741
        projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 &&
4314
741
        &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0),
4315
0
                   defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 1)
4316
0
                ->direction() == &AxisDirection::SOUTH &&
4317
741
        &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1),
4318
0
                   defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 2)
4319
0
                ->direction() == &AxisDirection::WEST) {
4320
0
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
4321
0
    }
4322
4323
741
    PropertyMap propertiesMethod;
4324
741
    if (mapping) {
4325
297
        projectionName = mapping->wkt2_name;
4326
297
        if (mapping->epsg_code != 0) {
4327
290
            propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code);
4328
290
            propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4329
290
        }
4330
297
    }
4331
741
    propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName);
4332
4333
741
    std::vector<bool> foundParameters;
4334
741
    if (mapping) {
4335
297
        size_t countParams = 0;
4336
2.34k
        while (mapping->params[countParams] != nullptr) {
4337
2.05k
            ++countParams;
4338
2.05k
        }
4339
297
        foundParameters.resize(countParams);
4340
297
    }
4341
4342
14.2k
    for (const auto &childNode : projCRSNode->GP()->children()) {
4343
14.2k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4344
5.63k
            const auto &childNodeChildren = childNode->GP()->children();
4345
5.63k
            if (childNodeChildren.size() < 2) {
4346
81
                ThrowNotEnoughChildren(WKTConstants::PARAMETER);
4347
81
            }
4348
5.55k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
4349
4350
5.55k
            PropertyMap propertiesParameter;
4351
5.55k
            const std::string wkt1ParameterName(
4352
5.55k
                stripQuotes(childNodeChildren[0]));
4353
5.55k
            std::string parameterName(wkt1ParameterName);
4354
5.55k
            if (gdal_3026_hack) {
4355
0
                if (ci_equal(parameterName, "latitude_of_origin")) {
4356
0
                    parameterName = "standard_parallel_1";
4357
0
                } else if (ci_equal(parameterName, "scale_factor") &&
4358
0
                           paramValue == "1") {
4359
0
                    continue;
4360
0
                }
4361
0
            }
4362
5.55k
            auto *paramMapping =
4363
5.55k
                mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
4364
5.55k
            if (mapping &&
4365
5.55k
                mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
4366
5.55k
                ci_equal(parameterName, "latitude_of_origin")) {
4367
                // Some illegal formulations of Mercator_2SP have a unexpected
4368
                // latitude_of_origin parameter. We accept it on import, but
4369
                // do not accept it when exporting to PROJ string, unless it is
4370
                // zero.
4371
                // No need to try to update foundParameters[] as this is a
4372
                // unexpected one.
4373
0
                parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
4374
0
                propertiesParameter.set(
4375
0
                    Identifier::CODE_KEY,
4376
0
                    EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
4377
0
                propertiesParameter.set(Identifier::CODESPACE_KEY,
4378
0
                                        Identifier::EPSG);
4379
5.55k
            } else if (mapping && paramMapping) {
4380
10
                for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4381
10
                    if (mapping->params[idx] == paramMapping) {
4382
5
                        foundParameters[idx] = true;
4383
5
                        break;
4384
5
                    }
4385
10
                }
4386
5
                parameterName = paramMapping->wkt2_name;
4387
5
                if (paramMapping->epsg_code != 0) {
4388
5
                    propertiesParameter.set(Identifier::CODE_KEY,
4389
5
                                            paramMapping->epsg_code);
4390
5
                    propertiesParameter.set(Identifier::CODESPACE_KEY,
4391
5
                                            Identifier::EPSG);
4392
5
                }
4393
5
            }
4394
5.55k
            propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName);
4395
5.55k
            parameters.push_back(
4396
5.55k
                OperationParameter::create(propertiesParameter));
4397
5.55k
            try {
4398
5.55k
                double val = io::asDouble(paramValue);
4399
5.55k
                auto unit = guessUnitForParameter(
4400
5.55k
                    wkt1ParameterName, defaultLinearUnit, defaultAngularUnit);
4401
5.55k
                values.push_back(ParameterValue::create(Measure(val, unit)));
4402
5.55k
            } catch (const std::exception &) {
4403
48
                throw ParsingException(
4404
48
                    concat("unhandled parameter value type : ", paramValue));
4405
48
            }
4406
5.55k
        }
4407
14.2k
    }
4408
4409
    // Add back important parameters that should normally be present, but
4410
    // are sometimes missing. Currently we only deal with Scale factor at
4411
    // natural origin. This is to avoid a default value of 0 to slip in later.
4412
    // But such WKT should be considered invalid.
4413
612
    if (mapping) {
4414
2.14k
        for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4415
1.86k
            if (!foundParameters[idx] &&
4416
1.86k
                mapping->params[idx]->epsg_code ==
4417
1.86k
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
4418
4419
0
                emitRecoverableWarning(
4420
0
                    "The WKT string lacks a value "
4421
0
                    "for " EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN
4422
0
                    ". Default it to 1.");
4423
4424
0
                PropertyMap propertiesParameter;
4425
0
                propertiesParameter.set(
4426
0
                    Identifier::CODE_KEY,
4427
0
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4428
0
                propertiesParameter.set(Identifier::CODESPACE_KEY,
4429
0
                                        Identifier::EPSG);
4430
0
                propertiesParameter.set(
4431
0
                    IdentifiedObject::NAME_KEY,
4432
0
                    EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4433
0
                parameters.push_back(
4434
0
                    OperationParameter::create(propertiesParameter));
4435
0
                values.push_back(ParameterValue::create(
4436
0
                    Measure(1.0, UnitOfMeasure::SCALE_UNITY)));
4437
0
            }
4438
1.86k
        }
4439
271
    }
4440
4441
612
    if (mapping && (mapping->epsg_code ==
4442
271
                        EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4443
271
                    mapping->epsg_code ==
4444
271
                        EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B)) {
4445
        // Special case when importing some GDAL WKT of Hotine Oblique Mercator
4446
        // that have a Azimuth parameter but lacks the Rectified Grid Angle.
4447
        // We have code in the exportToPROJString() to deal with that situation,
4448
        // but also adds the rectified grid angle from the azimuth on import.
4449
0
        bool foundAngleRecifiedToSkewGrid = false;
4450
0
        bool foundAzimuth = false;
4451
0
        for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4452
0
            if (foundParameters[idx] &&
4453
0
                mapping->params[idx]->epsg_code ==
4454
0
                    EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
4455
0
                foundAngleRecifiedToSkewGrid = true;
4456
0
            } else if (foundParameters[idx] &&
4457
0
                       mapping->params[idx]->epsg_code ==
4458
0
                           EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) {
4459
0
                foundAzimuth = true;
4460
0
            }
4461
0
        }
4462
0
        if (!foundAngleRecifiedToSkewGrid && foundAzimuth) {
4463
0
            for (size_t idx = 0; idx < parameters.size(); ++idx) {
4464
0
                if (parameters[idx]->getEPSGCode() ==
4465
0
                    EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) {
4466
0
                    PropertyMap propertiesParameter;
4467
0
                    propertiesParameter.set(
4468
0
                        Identifier::CODE_KEY,
4469
0
                        EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID);
4470
0
                    propertiesParameter.set(Identifier::CODESPACE_KEY,
4471
0
                                            Identifier::EPSG);
4472
0
                    propertiesParameter.set(
4473
0
                        IdentifiedObject::NAME_KEY,
4474
0
                        EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID);
4475
0
                    parameters.push_back(
4476
0
                        OperationParameter::create(propertiesParameter));
4477
0
                    values.push_back(values[idx]);
4478
0
                }
4479
0
            }
4480
0
        }
4481
0
    }
4482
4483
612
    return Conversion::create(
4484
612
               PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4485
612
               propertiesMethod, parameters, values)
4486
612
        ->identify();
4487
741
}
4488
4489
// ---------------------------------------------------------------------------
4490
4491
static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props,
4492
676
                                              const cs::CartesianCSNNPtr &cs) {
4493
676
    auto conversion = Conversion::createPopularVisualisationPseudoMercator(
4494
676
        PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0),
4495
676
        Angle(0), Length(0), Length(0));
4496
676
    return ProjectedCRS::create(props, GeographicCRS::EPSG_4326, conversion,
4497
676
                                cs);
4498
676
}
4499
4500
// ---------------------------------------------------------------------------
4501
4502
ProjectedCRSNNPtr
4503
1.29k
WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) {
4504
4505
1.29k
    const auto *nodeP = node->GP();
4506
1.29k
    auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION);
4507
1.29k
    auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION);
4508
1.29k
    if (isNull(conversionNode) && isNull(projectionNode)) {
4509
42
        ThrowMissing(WKTConstants::CONVERSION);
4510
42
    }
4511
4512
1.25k
    auto &baseGeodCRSNode =
4513
1.25k
        nodeP->lookForChild(WKTConstants::BASEGEODCRS,
4514
1.25k
                            WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS);
4515
1.25k
    if (isNull(baseGeodCRSNode)) {
4516
7
        throw ParsingException(
4517
7
            "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node");
4518
7
    }
4519
1.24k
    auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
4520
4521
1.24k
    auto props = buildProperties(node);
4522
4523
1.24k
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4524
1.24k
    const auto &nodeValue = nodeP->value();
4525
1.24k
    if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) &&
4526
1.24k
        !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) {
4527
3
        ThrowMissing(WKTConstants::CS_);
4528
3
    }
4529
4530
1.24k
    const std::string projCRSName = stripQuotes(nodeP->children()[0]);
4531
4532
1.24k
    auto cs = [this, &projCRSName, &nodeP, &csNode, &node, &nodeValue,
4533
1.24k
               &conversionNode]() -> CoordinateSystemNNPtr {
4534
1.22k
        if (isNull(csNode) && ci_equal(nodeValue, WKTConstants::BASEPROJCRS) &&
4535
1.22k
            !isNull(conversionNode)) {
4536
            // A BASEPROJCRS (as of WKT2 18-010r11) normally lacks an explicit
4537
            // CS[] which cause issues to properly instantiate it. So we first
4538
            // start by trying to identify the BASEPROJCRS by its id or name.
4539
            // And fallback to exploring the conversion parameters to infer the
4540
            // CS AXIS unit from the linear parameter unit... Not fully bullet
4541
            // proof.
4542
0
            if (dbContext_) {
4543
                // Get official name from database if ID is present
4544
0
                auto &idNode = nodeP->lookForChild(WKTConstants::ID);
4545
0
                if (!isNull(idNode)) {
4546
0
                    try {
4547
0
                        auto id = buildId(node, idNode, false, false);
4548
0
                        auto authFactory = AuthorityFactory::create(
4549
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
4550
0
                        auto projCRS =
4551
0
                            authFactory->createProjectedCRS(id->code());
4552
0
                        return projCRS->coordinateSystem();
4553
0
                    } catch (const std::exception &) {
4554
0
                    }
4555
0
                }
4556
4557
0
                auto authFactory = AuthorityFactory::create(
4558
0
                    NN_NO_CHECK(dbContext_), std::string());
4559
0
                auto res = authFactory->createObjectsFromName(
4560
0
                    projCRSName, {AuthorityFactory::ObjectType::PROJECTED_CRS},
4561
0
                    false, 2);
4562
0
                if (res.size() == 1) {
4563
0
                    auto projCRS =
4564
0
                        dynamic_cast<const ProjectedCRS *>(res.front().get());
4565
0
                    if (projCRS) {
4566
0
                        return projCRS->coordinateSystem();
4567
0
                    }
4568
0
                }
4569
0
            }
4570
4571
0
            auto conv = buildConversion(conversionNode, UnitOfMeasure::METRE,
4572
0
                                        UnitOfMeasure::DEGREE);
4573
0
            UnitOfMeasure linearUOM = UnitOfMeasure::NONE;
4574
0
            for (const auto &genOpParamvalue : conv->parameterValues()) {
4575
0
                auto opParamvalue =
4576
0
                    dynamic_cast<const operation::OperationParameterValue *>(
4577
0
                        genOpParamvalue.get());
4578
0
                if (opParamvalue) {
4579
0
                    const auto &parameterValue = opParamvalue->parameterValue();
4580
0
                    if (parameterValue->type() ==
4581
0
                        operation::ParameterValue::Type::MEASURE) {
4582
0
                        const auto &measure = parameterValue->value();
4583
0
                        const auto &unit = measure.unit();
4584
0
                        if (unit.type() == UnitOfMeasure::Type::LINEAR) {
4585
0
                            if (linearUOM == UnitOfMeasure::NONE) {
4586
0
                                linearUOM = unit;
4587
0
                            } else if (linearUOM != unit) {
4588
0
                                linearUOM = UnitOfMeasure::NONE;
4589
0
                                break;
4590
0
                            }
4591
0
                        }
4592
0
                    }
4593
0
                }
4594
0
            }
4595
0
            if (linearUOM != UnitOfMeasure::NONE) {
4596
0
                return CartesianCS::createEastingNorthing(linearUOM);
4597
0
            }
4598
0
        }
4599
1.22k
        return buildCS(csNode, node, UnitOfMeasure::NONE);
4600
1.22k
    }();
4601
1.24k
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
4602
4603
1.24k
    if (esriStyle_ && dbContext_) {
4604
563
        if (cartesianCS) {
4605
563
            std::string outTableName;
4606
563
            std::string authNameFromAlias;
4607
563
            std::string codeFromAlias;
4608
563
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
4609
563
                                                        std::string());
4610
563
            auto officialName = authFactory->getOfficialNameFromAlias(
4611
563
                projCRSName, "projected_crs", "ESRI", false, outTableName,
4612
563
                authNameFromAlias, codeFromAlias);
4613
563
            if (!officialName.empty()) {
4614
                // Special case for https://github.com/OSGeo/PROJ/issues/2086
4615
                // The name of the CRS to identify is
4616
                // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501
4617
                // whereas it should be
4618
                // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501_Feet
4619
4
                constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
4620
4
                if (projCRSName.find("_FIPS_") != std::string::npos &&
4621
4
                    projCRSName.find("_Feet") == std::string::npos &&
4622
4
                    std::fabs(
4623
0
                        cartesianCS->axisList()[0]->unit().conversionToSI() -
4624
0
                        US_FOOT_CONV_FACTOR) < 1e-10 * US_FOOT_CONV_FACTOR) {
4625
0
                    auto officialNameFromFeet =
4626
0
                        authFactory->getOfficialNameFromAlias(
4627
0
                            projCRSName + "_Feet", "projected_crs", "ESRI",
4628
0
                            false, outTableName, authNameFromAlias,
4629
0
                            codeFromAlias);
4630
0
                    if (!officialNameFromFeet.empty()) {
4631
0
                        officialName = std::move(officialNameFromFeet);
4632
0
                    }
4633
0
                }
4634
4635
4
                props.set(IdentifiedObject::NAME_KEY, officialName);
4636
4
            }
4637
563
        }
4638
563
    }
4639
4640
1.24k
    if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode) &&
4641
1.24k
        cartesianCS) {
4642
0
        toWGS84Parameters_.clear();
4643
0
        return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4644
0
    }
4645
4646
    // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated
4647
    // by older GDAL versions
4648
    // https://trac.osgeo.org/gdal/changeset/30732
4649
    // WGS_1984_Web_Mercator: deprecated ESRI:102113
4650
1.24k
    if (cartesianCS && (metadata::Identifier::isEquivalentName(
4651
1.21k
                            projCRSName.c_str(), "WGS_84_Pseudo_Mercator") ||
4652
1.21k
                        metadata::Identifier::isEquivalentName(
4653
1.21k
                            projCRSName.c_str(), "WGS_1984_Web_Mercator"))) {
4654
0
        toWGS84Parameters_.clear();
4655
0
        return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4656
0
    }
4657
4658
    // For WKT2, if there is no explicit parameter unit, use metre for linear
4659
    // units and degree for angular units
4660
1.24k
    const UnitOfMeasure linearUnit(
4661
1.24k
        !isNull(conversionNode)
4662
1.24k
            ? UnitOfMeasure::METRE
4663
1.24k
            : buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR));
4664
1.24k
    const auto &angularUnit =
4665
1.24k
        !isNull(conversionNode)
4666
1.24k
            ? UnitOfMeasure::DEGREE
4667
1.24k
            : baseGeodCRS->coordinateSystem()->axisList()[0]->unit();
4668
4669
1.24k
    auto conversion =
4670
1.24k
        !isNull(conversionNode)
4671
1.24k
            ? buildConversion(conversionNode, linearUnit, angularUnit)
4672
1.24k
            : buildProjection(baseGeodCRS, node, projectionNode, linearUnit,
4673
1.24k
                              angularUnit);
4674
4675
    // No explicit AXIS node ? (WKT1)
4676
1.24k
    if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
4677
1.07k
        props.set("IMPLICIT_CS", true);
4678
1.07k
    }
4679
4680
1.24k
    if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) {
4681
4682
1.07k
        const auto methodCode = conversion->method()->getEPSGCode();
4683
        // Krovak south oriented ?
4684
1.07k
        if (methodCode == EPSG_CODE_METHOD_KROVAK) {
4685
308
            cartesianCS =
4686
308
                CartesianCS::create(
4687
308
                    PropertyMap(),
4688
308
                    CoordinateSystemAxis::create(
4689
308
                        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4690
308
                                                AxisName::Southing),
4691
308
                        emptyString, AxisDirection::SOUTH, linearUnit),
4692
308
                    CoordinateSystemAxis::create(
4693
308
                        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4694
308
                                                AxisName::Westing),
4695
308
                        emptyString, AxisDirection::WEST, linearUnit))
4696
308
                    .as_nullable();
4697
766
        } else if (methodCode ==
4698
766
                       EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A ||
4699
766
                   methodCode ==
4700
762
                       EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
4701
            // It is likely that the ESRI definition of EPSG:32661 (UPS North) &
4702
            // EPSG:32761 (UPS South) uses the easting-northing order, instead
4703
            // of the EPSG northing-easting order.
4704
            // Same for WKT1_GDAL
4705
4
            const double lat0 = conversion->parameterValueNumeric(
4706
4
                EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4707
4
                common::UnitOfMeasure::DEGREE);
4708
4
            if (std::fabs(lat0 - 90) < 1e-10) {
4709
4
                cartesianCS =
4710
4
                    CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4711
4
                        linearUnit)
4712
4
                        .as_nullable();
4713
4
            } else if (std::fabs(lat0 - -90) < 1e-10) {
4714
0
                cartesianCS =
4715
0
                    CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4716
0
                        linearUnit)
4717
0
                        .as_nullable();
4718
0
            }
4719
762
        } else if (methodCode ==
4720
762
                   EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
4721
21
            const double lat_ts = conversion->parameterValueNumeric(
4722
21
                EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
4723
21
                common::UnitOfMeasure::DEGREE);
4724
21
            if (lat_ts > 0) {
4725
3
                cartesianCS =
4726
3
                    CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4727
3
                        linearUnit)
4728
3
                        .as_nullable();
4729
18
            } else if (lat_ts < 0) {
4730
0
                cartesianCS =
4731
0
                    CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4732
0
                        linearUnit)
4733
0
                        .as_nullable();
4734
0
            }
4735
741
        } else if (methodCode ==
4736
741
                   EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
4737
0
            cartesianCS =
4738
0
                CartesianCS::createWestingSouthing(linearUnit).as_nullable();
4739
0
        }
4740
1.07k
    }
4741
1.24k
    if (!cartesianCS) {
4742
0
        ThrowNotExpectedCSType(CartesianCS::WKT2_TYPE);
4743
0
    }
4744
4745
1.24k
    if (cartesianCS->axisList().size() == 3 &&
4746
1.24k
        baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
4747
0
        baseGeodCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
4748
0
            baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
4749
0
    }
4750
4751
1.24k
    addExtensionProj4ToProp(nodeP, props);
4752
4753
1.24k
    return ProjectedCRS::create(props, baseGeodCRS, conversion,
4754
1.24k
                                NN_NO_CHECK(cartesianCS));
4755
1.24k
}
4756
4757
// ---------------------------------------------------------------------------
4758
4759
void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode,
4760
                                      double &frameReferenceEpoch,
4761
50
                                      util::optional<std::string> &modelName) {
4762
50
    auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH);
4763
50
    const auto &frameEpochChildren = frameEpochNode->GP()->children();
4764
50
    if (frameEpochChildren.empty()) {
4765
6
        ThrowMissing(WKTConstants::FRAMEEPOCH);
4766
6
    }
4767
44
    try {
4768
44
        frameReferenceEpoch = asDouble(frameEpochChildren[0]);
4769
44
    } catch (const std::exception &) {
4770
2
        throw ParsingException("Invalid FRAMEEPOCH node");
4771
2
    }
4772
42
    auto &modelNode = dynamicNode->GP()->lookForChild(
4773
42
        WKTConstants::MODEL, WKTConstants::VELOCITYGRID);
4774
42
    const auto &modelChildren = modelNode->GP()->children();
4775
42
    if (modelChildren.size() == 1) {
4776
1
        modelName = stripQuotes(modelChildren[0]);
4777
1
    }
4778
42
}
4779
4780
// ---------------------------------------------------------------------------
4781
4782
VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame(
4783
2.27k
    const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) {
4784
4785
2.27k
    if (!isNull(dynamicNode)) {
4786
20
        double frameReferenceEpoch = 0.0;
4787
20
        util::optional<std::string> modelName;
4788
20
        parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
4789
20
        return DynamicVerticalReferenceFrame::create(
4790
20
            buildProperties(node), getAnchor(node),
4791
20
            optional<RealizationMethod>(),
4792
20
            common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
4793
20
            modelName);
4794
20
    }
4795
4796
    // WKT1 VERT_DATUM has a datum type after the datum name
4797
2.25k
    const auto *nodeP = node->GP();
4798
2.25k
    const std::string &name(nodeP->value());
4799
2.25k
    auto &props = buildProperties(node);
4800
2.25k
    const auto &children = nodeP->children();
4801
4802
2.25k
    if (esriStyle_ && dbContext_ && !children.empty()) {
4803
146
        std::string outTableName;
4804
146
        std::string authNameFromAlias;
4805
146
        std::string codeFromAlias;
4806
146
        auto authFactory =
4807
146
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
4808
146
        const std::string datumName = stripQuotes(children[0]);
4809
146
        auto officialName = authFactory->getOfficialNameFromAlias(
4810
146
            datumName, "vertical_datum", "ESRI", false, outTableName,
4811
146
            authNameFromAlias, codeFromAlias);
4812
146
        if (!officialName.empty()) {
4813
0
            props.set(IdentifiedObject::NAME_KEY, officialName);
4814
0
        }
4815
146
    }
4816
4817
2.25k
    if (ci_equal(name, WKTConstants::VERT_DATUM)) {
4818
774
        if (children.size() >= 2) {
4819
582
            props.set("VERT_DATUM_TYPE", children[1]->GP()->value());
4820
582
        }
4821
774
    }
4822
4823
2.25k
    return VerticalReferenceFrame::create(props, getAnchor(node),
4824
2.25k
                                          getAnchorEpoch(node));
4825
2.27k
}
4826
4827
// ---------------------------------------------------------------------------
4828
4829
TemporalDatumNNPtr
4830
300
WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) {
4831
300
    const auto *nodeP = node->GP();
4832
300
    auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR);
4833
300
    std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN;
4834
300
    const auto &calendarChildren = calendarNode->GP()->children();
4835
300
    if (calendarChildren.size() == 1) {
4836
48
        calendar = stripQuotes(calendarChildren[0]);
4837
48
    }
4838
4839
300
    auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN);
4840
300
    std::string originStr;
4841
300
    const auto &timeOriginNodeChildren = timeOriginNode->GP()->children();
4842
300
    if (timeOriginNodeChildren.size() == 1) {
4843
0
        originStr = stripQuotes(timeOriginNodeChildren[0]);
4844
0
    }
4845
300
    auto origin = DateTime::create(originStr);
4846
300
    return TemporalDatum::create(buildProperties(node), origin, calendar);
4847
300
}
4848
4849
// ---------------------------------------------------------------------------
4850
4851
EngineeringDatumNNPtr
4852
109
WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) {
4853
109
    return EngineeringDatum::create(buildProperties(node), getAnchor(node));
4854
109
}
4855
4856
// ---------------------------------------------------------------------------
4857
4858
ParametricDatumNNPtr
4859
284
WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) {
4860
284
    return ParametricDatum::create(buildProperties(node), getAnchor(node));
4861
284
}
4862
4863
// ---------------------------------------------------------------------------
4864
4865
static CRSNNPtr
4866
createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS,
4867
0
                                      const crs::CRSPtr &targetCRS) {
4868
0
    CRSPtr sourceTransformationCRS;
4869
0
    if (dynamic_cast<GeographicCRS *>(targetCRS.get())) {
4870
0
        GeographicCRSPtr sourceGeographicCRS =
4871
0
            sourceCRS->extractGeographicCRS();
4872
0
        sourceTransformationCRS = sourceGeographicCRS;
4873
0
        if (sourceGeographicCRS) {
4874
0
            const auto &sourceDatum = sourceGeographicCRS->datum();
4875
0
            if (sourceDatum != nullptr && sourceGeographicCRS->primeMeridian()
4876
0
                                                  ->longitude()
4877
0
                                                  .getSIValue() != 0.0) {
4878
0
                sourceTransformationCRS =
4879
0
                    GeographicCRS::create(
4880
0
                        util::PropertyMap().set(
4881
0
                            common::IdentifiedObject::NAME_KEY,
4882
0
                            sourceGeographicCRS->nameStr() +
4883
0
                                " (with Greenwich prime meridian)"),
4884
0
                        datum::GeodeticReferenceFrame::create(
4885
0
                            util::PropertyMap().set(
4886
0
                                common::IdentifiedObject::NAME_KEY,
4887
0
                                sourceDatum->nameStr() +
4888
0
                                    " (with Greenwich prime meridian)"),
4889
0
                            sourceDatum->ellipsoid(),
4890
0
                            util::optional<std::string>(),
4891
0
                            datum::PrimeMeridian::GREENWICH),
4892
0
                        sourceGeographicCRS->coordinateSystem())
4893
0
                        .as_nullable();
4894
0
            }
4895
0
        } else {
4896
0
            auto vertSourceCRS =
4897
0
                std::dynamic_pointer_cast<VerticalCRS>(sourceCRS);
4898
0
            if (!vertSourceCRS) {
4899
0
                throw ParsingException(
4900
0
                    "Cannot find GeographicCRS or VerticalCRS in sourceCRS");
4901
0
            }
4902
0
            const auto &axis = vertSourceCRS->coordinateSystem()->axisList()[0];
4903
0
            if (axis->unit() == common::UnitOfMeasure::METRE &&
4904
0
                &(axis->direction()) == &AxisDirection::UP) {
4905
0
                sourceTransformationCRS = sourceCRS;
4906
0
            } else {
4907
0
                std::string sourceTransformationCRSName(
4908
0
                    vertSourceCRS->nameStr());
4909
0
                if (ends_with(sourceTransformationCRSName, " (ftUS)")) {
4910
0
                    sourceTransformationCRSName.resize(
4911
0
                        sourceTransformationCRSName.size() - strlen(" (ftUS)"));
4912
0
                }
4913
0
                if (ends_with(sourceTransformationCRSName, " depth")) {
4914
0
                    sourceTransformationCRSName.resize(
4915
0
                        sourceTransformationCRSName.size() - strlen(" depth"));
4916
0
                }
4917
0
                if (!ends_with(sourceTransformationCRSName, " height")) {
4918
0
                    sourceTransformationCRSName += " height";
4919
0
                }
4920
0
                sourceTransformationCRS =
4921
0
                    VerticalCRS::create(
4922
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
4923
0
                                          sourceTransformationCRSName),
4924
0
                        vertSourceCRS->datum(), vertSourceCRS->datumEnsemble(),
4925
0
                        VerticalCS::createGravityRelatedHeight(
4926
0
                            common::UnitOfMeasure::METRE))
4927
0
                        .as_nullable();
4928
0
            }
4929
0
        }
4930
0
    } else {
4931
0
        sourceTransformationCRS = sourceCRS;
4932
0
    }
4933
0
    return NN_NO_CHECK(sourceTransformationCRS);
4934
0
}
4935
4936
// ---------------------------------------------------------------------------
4937
4938
2.36k
CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
4939
2.36k
    const auto *nodeP = node->GP();
4940
2.36k
    const auto &nodeValue = nodeP->value();
4941
2.36k
    auto &vdatumNode =
4942
2.36k
        nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM,
4943
2.36k
                            WKTConstants::VERTICALDATUM, WKTConstants::VRF);
4944
2.36k
    auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
4945
    // like in ESRI  VERTCS["WGS_1984",DATUM["D_WGS_1984",
4946
    //               SPHEROID["WGS_1984",6378137.0,298.257223563]],
4947
    //               PARAMETER["Vertical_Shift",0.0],
4948
    //               PARAMETER["Direction",1.0],UNIT["Meter",1.0]
4949
2.36k
    auto &geogDatumNode = ci_equal(nodeValue, WKTConstants::VERTCS)
4950
2.36k
                              ? nodeP->lookForChild(WKTConstants::DATUM)
4951
2.36k
                              : null_node;
4952
2.36k
    if (isNull(vdatumNode) && isNull(geogDatumNode) && isNull(ensembleNode)) {
4953
43
        throw ParsingException("Missing VDATUM or ENSEMBLE node");
4954
43
    }
4955
4956
12.0k
    for (const auto &childNode : nodeP->children()) {
4957
12.0k
        const auto &childNodeChildren = childNode->GP()->children();
4958
12.0k
        if (childNodeChildren.size() == 2 &&
4959
12.0k
            ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
4960
12.0k
            childNodeChildren[0]->GP()->value() == "\"Vertical_Shift\"") {
4961
0
            esriStyle_ = true;
4962
0
            break;
4963
0
        }
4964
12.0k
    }
4965
4966
2.32k
    auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
4967
2.32k
    auto vdatum =
4968
2.32k
        !isNull(geogDatumNode)
4969
2.32k
            ? VerticalReferenceFrame::create(
4970
482
                  PropertyMap()
4971
482
                      .set(IdentifiedObject::NAME_KEY,
4972
482
                           buildGeodeticReferenceFrame(geogDatumNode,
4973
482
                                                       PrimeMeridian::GREENWICH,
4974
482
                                                       null_node)
4975
482
                               ->nameStr())
4976
482
                      .set("VERT_DATUM_TYPE", "2002"))
4977
482
                  .as_nullable()
4978
2.32k
        : !isNull(vdatumNode)
4979
1.84k
            ? buildVerticalReferenceFrame(vdatumNode, dynamicNode).as_nullable()
4980
1.84k
            : nullptr;
4981
2.32k
    auto datumEnsemble =
4982
2.32k
        !isNull(ensembleNode)
4983
2.32k
            ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable()
4984
2.32k
            : nullptr;
4985
4986
2.32k
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4987
2.32k
    if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) &&
4988
2.32k
        !ci_equal(nodeValue, WKTConstants::VERTCS) &&
4989
2.32k
        !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) {
4990
0
        ThrowMissing(WKTConstants::CS_);
4991
0
    }
4992
2.32k
    auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(
4993
2.32k
        buildCS(csNode, node, UnitOfMeasure::NONE));
4994
2.32k
    if (!verticalCS) {
4995
0
        ThrowNotExpectedCSType(VerticalCS::WKT2_TYPE);
4996
0
    }
4997
4998
2.32k
    if (vdatum && vdatum->getWKT1DatumType() == "2002" &&
4999
2.32k
        &(verticalCS->axisList()[0]->direction()) == &(AxisDirection::UP)) {
5000
491
        verticalCS =
5001
491
            VerticalCS::create(
5002
491
                util::PropertyMap(),
5003
491
                CoordinateSystemAxis::create(
5004
491
                    util::PropertyMap().set(IdentifiedObject::NAME_KEY,
5005
491
                                            "ellipsoidal height"),
5006
491
                    "h", AxisDirection::UP, verticalCS->axisList()[0]->unit()))
5007
491
                .as_nullable();
5008
491
    }
5009
5010
2.32k
    auto &props = buildProperties(node);
5011
5012
2.32k
    if (esriStyle_ && dbContext_) {
5013
537
        std::string outTableName;
5014
537
        std::string authNameFromAlias;
5015
537
        std::string codeFromAlias;
5016
537
        auto authFactory =
5017
537
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
5018
537
        const std::string vertCRSName = stripQuotes(nodeP->children()[0]);
5019
537
        auto officialName = authFactory->getOfficialNameFromAlias(
5020
537
            vertCRSName, "vertical_crs", "ESRI", false, outTableName,
5021
537
            authNameFromAlias, codeFromAlias);
5022
537
        if (!officialName.empty()) {
5023
0
            props.set(IdentifiedObject::NAME_KEY, officialName);
5024
0
        }
5025
537
    }
5026
5027
    // Deal with Lidar WKT1 VertCRS that embeds geoid model in CRS name,
5028
    // following conventions from
5029
    // https://pubs.usgs.gov/tm/11b4/pdf/tm11-B4.pdf
5030
    // page 9
5031
2.32k
    if (ci_equal(nodeValue, WKTConstants::VERT_CS) ||
5032
2.32k
        ci_equal(nodeValue, WKTConstants::VERTCS)) {
5033
2.24k
        std::string name;
5034
2.24k
        if (props.getStringValue(IdentifiedObject::NAME_KEY, name)) {
5035
2.24k
            std::string geoidName;
5036
2.24k
            for (const char *prefix :
5037
2.24k
                 {"NAVD88 - ", "NAVD88 via ", "NAVD88 height - ",
5038
8.97k
                  "NAVD88 height (ftUS) - "}) {
5039
8.97k
                if (starts_with(name, prefix)) {
5040
20
                    geoidName = name.substr(strlen(prefix));
5041
20
                    auto pos = geoidName.find_first_of(" (");
5042
20
                    if (pos != std::string::npos) {
5043
20
                        geoidName.resize(pos);
5044
20
                    }
5045
20
                    break;
5046
20
                }
5047
8.97k
            }
5048
2.24k
            if (!geoidName.empty()) {
5049
20
                const auto &axis = verticalCS->axisList()[0];
5050
20
                const auto &dir = axis->direction();
5051
20
                if (dir == cs::AxisDirection::UP) {
5052
20
                    if (axis->unit() == common::UnitOfMeasure::METRE) {
5053
20
                        props.set(IdentifiedObject::NAME_KEY, "NAVD88 height");
5054
20
                        props.set(Identifier::CODE_KEY, 5703);
5055
20
                        props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5056
20
                    } else if (axis->unit().name() == "US survey foot") {
5057
0
                        props.set(IdentifiedObject::NAME_KEY,
5058
0
                                  "NAVD88 height (ftUS)");
5059
0
                        props.set(Identifier::CODE_KEY, 6360);
5060
0
                        props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5061
0
                    }
5062
20
                }
5063
20
                PropertyMap propsModel;
5064
20
                propsModel.set(IdentifiedObject::NAME_KEY, toupper(geoidName));
5065
20
                PropertyMap propsDatum;
5066
20
                propsDatum.set(IdentifiedObject::NAME_KEY,
5067
20
                               "North American Vertical Datum 1988");
5068
20
                propsDatum.set(Identifier::CODE_KEY, 5103);
5069
20
                propsDatum.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5070
20
                vdatum =
5071
20
                    VerticalReferenceFrame::create(propsDatum).as_nullable();
5072
20
                const auto dummyCRS =
5073
20
                    VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
5074
20
                                        NN_NO_CHECK(verticalCS));
5075
20
                const auto model(Transformation::create(
5076
20
                    propsModel, dummyCRS, dummyCRS, nullptr,
5077
20
                    OperationMethod::create(
5078
20
                        PropertyMap(), std::vector<OperationParameterNNPtr>()),
5079
20
                    {}, {}));
5080
20
                props.set("GEOID_MODEL", model);
5081
20
            }
5082
2.24k
        }
5083
2.24k
    }
5084
5085
2.32k
    auto &geoidModelNode = nodeP->lookForChild(WKTConstants::GEOIDMODEL);
5086
2.32k
    if (!isNull(geoidModelNode)) {
5087
120
        ArrayOfBaseObjectNNPtr arrayModels = ArrayOfBaseObject::create();
5088
2.30k
        for (const auto &childNode : nodeP->children()) {
5089
2.30k
            const auto &childNodeChildren = childNode->GP()->children();
5090
2.30k
            if (childNodeChildren.size() >= 1 &&
5091
2.30k
                ci_equal(childNode->GP()->value(), WKTConstants::GEOIDMODEL)) {
5092
234
                auto &propsModel = buildProperties(childNode);
5093
234
                const auto dummyCRS =
5094
234
                    VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
5095
234
                                        NN_NO_CHECK(verticalCS));
5096
234
                const auto model(Transformation::create(
5097
234
                    propsModel, dummyCRS, dummyCRS, nullptr,
5098
234
                    OperationMethod::create(
5099
234
                        PropertyMap(), std::vector<OperationParameterNNPtr>()),
5100
234
                    {}, {}));
5101
234
                arrayModels->add(model);
5102
234
            }
5103
2.30k
        }
5104
120
        props.set("GEOID_MODEL", arrayModels);
5105
120
    }
5106
5107
2.32k
    auto crs = nn_static_pointer_cast<CRS>(VerticalCRS::create(
5108
2.32k
        props, vdatum, datumEnsemble, NN_NO_CHECK(verticalCS)));
5109
5110
2.32k
    if (!isNull(vdatumNode)) {
5111
1.92k
        auto &extensionNode = vdatumNode->lookForChild(WKTConstants::EXTENSION);
5112
1.92k
        const auto &extensionChildren = extensionNode->GP()->children();
5113
1.92k
        if (extensionChildren.size() == 2) {
5114
87
            if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
5115
0
                const auto gridName(stripQuotes(extensionChildren[1]));
5116
                // This is the expansion of EPSG:5703 by old GDAL versions.
5117
                // See
5118
                // 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
5119
                // It is unlikely that the user really explicitly wants this.
5120
0
                if (gridName != "g2003conus.gtx,g2003alaska.gtx,"
5121
0
                                "g2003h01.gtx,g2003p01.gtx" &&
5122
0
                    gridName != "g2012a_conus.gtx,g2012a_alaska.gtx,"
5123
0
                                "g2012a_guam.gtx,g2012a_hawaii.gtx,"
5124
0
                                "g2012a_puertorico.gtx,g2012a_samoa.gtx") {
5125
0
                    auto geogCRS =
5126
0
                        geogCRSOfCompoundCRS_ &&
5127
0
                                geogCRSOfCompoundCRS_->primeMeridian()
5128
0
                                        ->longitude()
5129
0
                                        .getSIValue() == 0 &&
5130
0
                                geogCRSOfCompoundCRS_->coordinateSystem()
5131
0
                                        ->axisList()[0]
5132
0
                                        ->unit() == UnitOfMeasure::DEGREE
5133
0
                            ? geogCRSOfCompoundCRS_->promoteTo3D(std::string(),
5134
0
                                                                 dbContext_)
5135
0
                            : GeographicCRS::EPSG_4979;
5136
5137
0
                    auto sourceTransformationCRS =
5138
0
                        createBoundCRSSourceTransformationCRS(
5139
0
                            crs.as_nullable(), geogCRS.as_nullable());
5140
0
                    auto transformation = Transformation::
5141
0
                        createGravityRelatedHeightToGeographic3D(
5142
0
                            PropertyMap().set(
5143
0
                                IdentifiedObject::NAME_KEY,
5144
0
                                sourceTransformationCRS->nameStr() + " to " +
5145
0
                                    geogCRS->nameStr() + " ellipsoidal height"),
5146
0
                            sourceTransformationCRS, geogCRS, nullptr, gridName,
5147
0
                            std::vector<PositionalAccuracyNNPtr>());
5148
0
                    return nn_static_pointer_cast<CRS>(
5149
0
                        BoundCRS::create(crs, geogCRS, transformation));
5150
0
                }
5151
0
            }
5152
87
        }
5153
1.92k
    }
5154
5155
2.32k
    return crs;
5156
2.32k
}
5157
5158
// ---------------------------------------------------------------------------
5159
5160
DerivedVerticalCRSNNPtr
5161
0
WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) {
5162
0
    const auto *nodeP = node->GP();
5163
0
    auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS);
5164
    // given the constraints enforced on calling code path
5165
0
    assert(!isNull(baseVertCRSNode));
5166
5167
0
    auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode);
5168
0
    auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS());
5169
5170
0
    auto &derivingConversionNode =
5171
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5172
0
    if (isNull(derivingConversionNode)) {
5173
0
        ThrowMissing(WKTConstants::DERIVINGCONVERSION);
5174
0
    }
5175
0
    auto derivingConversion = buildConversion(
5176
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
5177
5178
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5179
0
    if (isNull(csNode)) {
5180
0
        ThrowMissing(WKTConstants::CS_);
5181
0
    }
5182
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5183
5184
0
    auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs);
5185
0
    if (!verticalCS) {
5186
0
        throw ParsingException(
5187
0
            concat("vertical CS expected, but found ", cs->getWKT2Type(true)));
5188
0
    }
5189
5190
0
    return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS,
5191
0
                                      derivingConversion,
5192
0
                                      NN_NO_CHECK(verticalCS));
5193
0
}
5194
5195
// ---------------------------------------------------------------------------
5196
5197
1.95k
CRSNNPtr WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
5198
1.95k
    std::vector<CRSNNPtr> components;
5199
1.95k
    bool bFirstNode = true;
5200
17.6k
    for (const auto &child : node->GP()->children()) {
5201
17.6k
        auto crs = buildCRS(child);
5202
17.6k
        if (crs) {
5203
6.10k
            if (bFirstNode) {
5204
1.49k
                geogCRSOfCompoundCRS_ = crs->extractGeographicCRS();
5205
1.49k
                bFirstNode = false;
5206
1.49k
            }
5207
6.10k
            components.push_back(NN_NO_CHECK(crs));
5208
6.10k
        }
5209
17.6k
    }
5210
5211
1.95k
    if (ci_equal(node->GP()->value(), WKTConstants::COMPD_CS)) {
5212
701
        return CompoundCRS::createLax(buildProperties(node), components,
5213
701
                                      dbContext_);
5214
1.24k
    } else {
5215
1.24k
        return CompoundCRS::create(buildProperties(node), components);
5216
1.24k
    }
5217
1.95k
}
5218
5219
// ---------------------------------------------------------------------------
5220
5221
static TransformationNNPtr buildTransformationForBoundCRS(
5222
    DatabaseContextPtr &dbContext,
5223
    const util::PropertyMap &abridgedNodeProperties,
5224
    const util::PropertyMap &methodNodeProperties, const CRSNNPtr &sourceCRS,
5225
    const CRSNNPtr &targetCRS, std::vector<OperationParameterNNPtr> &parameters,
5226
0
    std::vector<ParameterValueNNPtr> &values) {
5227
5228
0
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
5229
0
        dbContext, parameters, values);
5230
5231
0
    const auto sourceTransformationCRS(
5232
0
        createBoundCRSSourceTransformationCRS(sourceCRS, targetCRS));
5233
0
    auto transformation = Transformation::create(
5234
0
        abridgedNodeProperties, sourceTransformationCRS, targetCRS,
5235
0
        interpolationCRS, methodNodeProperties, parameters, values,
5236
0
        std::vector<PositionalAccuracyNNPtr>());
5237
5238
    // If the transformation is a "Geographic3D to GravityRelatedHeight" one,
5239
    // then the sourceCRS is expected to be a GeographicCRS and the target a
5240
    // VerticalCRS. Due to how things work in a BoundCRS, we have the opposite,
5241
    // so use our "GravityRelatedHeight to Geographic3D" method instead.
5242
0
    if (Transformation::isGeographic3DToGravityRelatedHeight(
5243
0
            transformation->method(), true) &&
5244
0
        dynamic_cast<VerticalCRS *>(sourceTransformationCRS.get()) &&
5245
0
        dynamic_cast<GeographicCRS *>(targetCRS.get())) {
5246
0
        auto fileParameter = transformation->parameterValue(
5247
0
            EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
5248
0
            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
5249
0
        if (fileParameter &&
5250
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
5251
0
            const auto &filename = fileParameter->valueFile();
5252
5253
0
            transformation =
5254
0
                Transformation::createGravityRelatedHeightToGeographic3D(
5255
0
                    abridgedNodeProperties, sourceTransformationCRS, targetCRS,
5256
0
                    interpolationCRS, filename,
5257
0
                    std::vector<PositionalAccuracyNNPtr>());
5258
0
        }
5259
0
    }
5260
0
    return transformation;
5261
0
}
5262
5263
// ---------------------------------------------------------------------------
5264
5265
5
BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) {
5266
5
    const auto *nodeP = node->GP();
5267
5
    auto &abridgedNode =
5268
5
        nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION);
5269
5
    if (isNull(abridgedNode)) {
5270
5
        ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION);
5271
5
    }
5272
5273
0
    auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD);
5274
0
    if (isNull(methodNode)) {
5275
0
        ThrowMissing(WKTConstants::METHOD);
5276
0
    }
5277
0
    if (methodNode->GP()->childrenSize() == 0) {
5278
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
5279
0
    }
5280
5281
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
5282
0
    const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children();
5283
0
    if (sourceCRSNodeChildren.size() != 1) {
5284
0
        ThrowNotEnoughChildren(WKTConstants::SOURCECRS);
5285
0
    }
5286
0
    auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]);
5287
0
    if (!sourceCRS) {
5288
0
        throw ParsingException("Invalid content in SOURCECRS node");
5289
0
    }
5290
5291
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
5292
0
    const auto &targetCRSNodeChildren = targetCRSNode->GP()->children();
5293
0
    if (targetCRSNodeChildren.size() != 1) {
5294
0
        ThrowNotEnoughChildren(WKTConstants::TARGETCRS);
5295
0
    }
5296
0
    auto targetCRS = buildCRS(targetCRSNodeChildren[0]);
5297
0
    if (!targetCRS) {
5298
0
        throw ParsingException("Invalid content in TARGETCRS node");
5299
0
    }
5300
5301
0
    std::vector<OperationParameterNNPtr> parameters;
5302
0
    std::vector<ParameterValueNNPtr> values;
5303
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
5304
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
5305
0
    consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit,
5306
0
                      defaultAngularUnit);
5307
5308
0
    const auto nnSourceCRS = NN_NO_CHECK(sourceCRS);
5309
0
    const auto nnTargetCRS = NN_NO_CHECK(targetCRS);
5310
0
    const auto transformation = buildTransformationForBoundCRS(
5311
0
        dbContext_, buildProperties(abridgedNode), buildProperties(methodNode),
5312
0
        nnSourceCRS, nnTargetCRS, parameters, values);
5313
5314
0
    return BoundCRS::create(buildProperties(node, false, false),
5315
0
                            NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS),
5316
0
                            transformation);
5317
0
}
5318
5319
// ---------------------------------------------------------------------------
5320
5321
TemporalCSNNPtr
5322
0
WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) {
5323
5324
0
    auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
5325
0
    if (isNull(csNode) &&
5326
0
        !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) {
5327
0
        ThrowMissing(WKTConstants::CS_);
5328
0
    }
5329
0
    auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
5330
0
    auto temporalCS = nn_dynamic_pointer_cast<TemporalCS>(cs);
5331
0
    if (!temporalCS) {
5332
0
        ThrowNotExpectedCSType(TemporalCS::WKT2_2015_TYPE);
5333
0
    }
5334
0
    return NN_NO_CHECK(temporalCS);
5335
0
}
5336
5337
// ---------------------------------------------------------------------------
5338
5339
TemporalCRSNNPtr
5340
5
WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) {
5341
5
    auto &datumNode =
5342
5
        node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM);
5343
5
    if (isNull(datumNode)) {
5344
5
        throw ParsingException("Missing TDATUM / TIMEDATUM node");
5345
5
    }
5346
5347
0
    return TemporalCRS::create(buildProperties(node),
5348
0
                               buildTemporalDatum(datumNode),
5349
0
                               buildTemporalCS(node));
5350
5
}
5351
5352
// ---------------------------------------------------------------------------
5353
5354
DerivedTemporalCRSNNPtr
5355
0
WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) {
5356
0
    const auto *nodeP = node->GP();
5357
0
    auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS);
5358
    // given the constraints enforced on calling code path
5359
0
    assert(!isNull(baseCRSNode));
5360
5361
0
    auto &derivingConversionNode =
5362
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5363
0
    if (isNull(derivingConversionNode)) {
5364
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5365
0
    }
5366
5367
0
    return DerivedTemporalCRS::create(
5368
0
        buildProperties(node), buildTemporalCRS(baseCRSNode),
5369
0
        buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
5370
0
                        UnitOfMeasure::NONE),
5371
0
        buildTemporalCS(node));
5372
0
}
5373
5374
// ---------------------------------------------------------------------------
5375
5376
EngineeringCRSNNPtr
5377
27
WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) {
5378
27
    const auto *nodeP = node->GP();
5379
27
    auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM,
5380
27
                                          WKTConstants::ENGINEERINGDATUM);
5381
27
    if (isNull(datumNode)) {
5382
19
        throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node");
5383
19
    }
5384
5385
8
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5386
8
    if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) {
5387
2
        ThrowMissing(WKTConstants::CS_);
5388
2
    }
5389
5390
6
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5391
6
    return EngineeringCRS::create(buildProperties(node),
5392
6
                                  buildEngineeringDatum(datumNode), cs);
5393
8
}
5394
5395
// ---------------------------------------------------------------------------
5396
5397
EngineeringCRSNNPtr
5398
3.05k
WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) {
5399
3.05k
    auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM);
5400
3.05k
    auto cs = buildCS(null_node, node, UnitOfMeasure::NONE);
5401
3.05k
    auto datum = EngineeringDatum::create(
5402
3.05k
        !isNull(datumNode)
5403
3.05k
            ? buildProperties(datumNode)
5404
3.05k
            :
5405
            // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL
5406
            // has a tradition of emitting just LOCAL_CS["foo"]
5407
3.05k
            []() {
5408
2.93k
                PropertyMap map;
5409
2.93k
                map.set(IdentifiedObject::NAME_KEY, UNKNOWN_ENGINEERING_DATUM);
5410
2.93k
                return map;
5411
2.93k
            }());
5412
3.05k
    return EngineeringCRS::create(buildProperties(node), datum, cs);
5413
3.05k
}
5414
5415
// ---------------------------------------------------------------------------
5416
5417
DerivedEngineeringCRSNNPtr
5418
13
WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) {
5419
13
    const auto *nodeP = node->GP();
5420
13
    auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS);
5421
    // given the constraints enforced on calling code path
5422
13
    assert(!isNull(baseEngCRSNode));
5423
5424
13
    auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode);
5425
5426
13
    auto &derivingConversionNode =
5427
13
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5428
13
    if (isNull(derivingConversionNode)) {
5429
6
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5430
6
    }
5431
7
    auto derivingConversion = buildConversion(
5432
7
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
5433
5434
7
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5435
7
    if (isNull(csNode)) {
5436
0
        ThrowMissing(WKTConstants::CS_);
5437
0
    }
5438
7
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5439
5440
7
    return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS,
5441
7
                                         derivingConversion, cs);
5442
7
}
5443
5444
// ---------------------------------------------------------------------------
5445
5446
ParametricCSNNPtr
5447
0
WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) {
5448
5449
0
    auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
5450
0
    if (isNull(csNode) &&
5451
0
        !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) {
5452
0
        ThrowMissing(WKTConstants::CS_);
5453
0
    }
5454
0
    auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
5455
0
    auto parametricCS = nn_dynamic_pointer_cast<ParametricCS>(cs);
5456
0
    if (!parametricCS) {
5457
0
        ThrowNotExpectedCSType(ParametricCS::WKT2_TYPE);
5458
0
    }
5459
0
    return NN_NO_CHECK(parametricCS);
5460
0
}
5461
5462
// ---------------------------------------------------------------------------
5463
5464
ParametricCRSNNPtr
5465
0
WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) {
5466
0
    auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM,
5467
0
                                               WKTConstants::PARAMETRICDATUM);
5468
0
    if (isNull(datumNode)) {
5469
0
        throw ParsingException("Missing PDATUM / PARAMETRICDATUM node");
5470
0
    }
5471
5472
0
    return ParametricCRS::create(buildProperties(node),
5473
0
                                 buildParametricDatum(datumNode),
5474
0
                                 buildParametricCS(node));
5475
0
}
5476
5477
// ---------------------------------------------------------------------------
5478
5479
DerivedParametricCRSNNPtr
5480
0
WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) {
5481
0
    const auto *nodeP = node->GP();
5482
0
    auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS);
5483
    // given the constraints enforced on calling code path
5484
0
    assert(!isNull(baseParamCRSNode));
5485
5486
0
    auto &derivingConversionNode =
5487
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5488
0
    if (isNull(derivingConversionNode)) {
5489
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5490
0
    }
5491
5492
0
    return DerivedParametricCRS::create(
5493
0
        buildProperties(node), buildParametricCRS(baseParamCRSNode),
5494
0
        buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
5495
0
                        UnitOfMeasure::NONE),
5496
0
        buildParametricCS(node));
5497
0
}
5498
5499
// ---------------------------------------------------------------------------
5500
5501
DerivedProjectedCRSNNPtr
5502
3
WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) {
5503
3
    const auto *nodeP = node->GP();
5504
3
    auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS);
5505
3
    if (isNull(baseProjCRSNode)) {
5506
3
        ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS);
5507
3
    }
5508
0
    auto baseProjCRS = buildProjectedCRS(baseProjCRSNode);
5509
5510
0
    auto &conversionNode =
5511
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5512
0
    if (isNull(conversionNode)) {
5513
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5514
0
    }
5515
5516
0
    auto linearUnit = buildUnitInSubNode(node);
5517
0
    const auto &angularUnit =
5518
0
        baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit();
5519
5520
0
    auto conversion = buildConversion(conversionNode, linearUnit, angularUnit);
5521
5522
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5523
0
    if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) {
5524
0
        ThrowMissing(WKTConstants::CS_);
5525
0
    }
5526
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5527
5528
0
    if (cs->axisList().size() == 3 &&
5529
0
        baseProjCRS->coordinateSystem()->axisList().size() == 2) {
5530
0
        baseProjCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<ProjectedCRS>(
5531
0
            baseProjCRS->promoteTo3D(std::string(), dbContext_)));
5532
0
    }
5533
5534
0
    return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS,
5535
0
                                       conversion, cs);
5536
0
}
5537
5538
// ---------------------------------------------------------------------------
5539
5540
CoordinateMetadataNNPtr
5541
0
WKTParser::Private::buildCoordinateMetadata(const WKTNodeNNPtr &node) {
5542
0
    const auto *nodeP = node->GP();
5543
5544
0
    const auto &l_children = nodeP->children();
5545
0
    if (l_children.empty()) {
5546
0
        ThrowNotEnoughChildren(WKTConstants::COORDINATEMETADATA);
5547
0
    }
5548
5549
0
    auto crs = buildCRS(l_children[0]);
5550
0
    if (!crs) {
5551
0
        throw ParsingException("Invalid content in CRS node");
5552
0
    }
5553
5554
0
    auto &epochNode = nodeP->lookForChild(WKTConstants::EPOCH);
5555
0
    if (!isNull(epochNode)) {
5556
0
        const auto &epochChildren = epochNode->GP()->children();
5557
0
        if (epochChildren.empty()) {
5558
0
            ThrowMissing(WKTConstants::EPOCH);
5559
0
        }
5560
0
        double coordinateEpoch;
5561
0
        try {
5562
0
            coordinateEpoch = asDouble(epochChildren[0]);
5563
0
        } catch (const std::exception &) {
5564
0
            throw ParsingException("Invalid EPOCH node");
5565
0
        }
5566
0
        return CoordinateMetadata::create(NN_NO_CHECK(crs), coordinateEpoch,
5567
0
                                          dbContext_);
5568
0
    }
5569
5570
0
    return CoordinateMetadata::create(NN_NO_CHECK(crs));
5571
0
}
5572
5573
// ---------------------------------------------------------------------------
5574
5575
27.1k
static bool isGeodeticCRS(const std::string &name) {
5576
27.1k
    return ci_equal(name, WKTConstants::GEODCRS) ||       // WKT2
5577
27.1k
           ci_equal(name, WKTConstants::GEODETICCRS) ||   // WKT2
5578
27.1k
           ci_equal(name, WKTConstants::GEOGCRS) ||       // WKT2 2019
5579
27.1k
           ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2019
5580
27.1k
           ci_equal(name, WKTConstants::GEOGCS) ||        // WKT1
5581
27.1k
           ci_equal(name, WKTConstants::GEOCCS);          // WKT1
5582
27.1k
}
5583
5584
// ---------------------------------------------------------------------------
5585
5586
27.1k
CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) {
5587
27.1k
    const auto *nodeP = node->GP();
5588
27.1k
    const std::string &name(nodeP->value());
5589
5590
27.1k
    const auto applyHorizontalBoundCRSParams = [&](const CRSNNPtr &crs) {
5591
3.15k
        if (!toWGS84Parameters_.empty()) {
5592
0
            auto ret = BoundCRS::createFromTOWGS84(crs, toWGS84Parameters_);
5593
0
            toWGS84Parameters_.clear();
5594
0
            return util::nn_static_pointer_cast<CRS>(ret);
5595
3.15k
        } else if (!datumPROJ4Grids_.empty()) {
5596
0
            auto ret = BoundCRS::createFromNadgrids(crs, datumPROJ4Grids_);
5597
0
            datumPROJ4Grids_.clear();
5598
0
            return util::nn_static_pointer_cast<CRS>(ret);
5599
0
        }
5600
3.15k
        return crs;
5601
3.15k
    };
5602
5603
27.1k
    if (isGeodeticCRS(name)) {
5604
2.26k
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS,
5605
2.26k
                                        WKTConstants::BASEGEODCRS))) {
5606
4
            return util::nn_static_pointer_cast<CRS>(
5607
4
                applyHorizontalBoundCRSParams(buildDerivedGeodeticCRS(node)));
5608
2.26k
        } else {
5609
2.26k
            return util::nn_static_pointer_cast<CRS>(
5610
2.26k
                applyHorizontalBoundCRSParams(buildGeodeticCRS(node)));
5611
2.26k
        }
5612
2.26k
    }
5613
5614
24.9k
    if (ci_equal(name, WKTConstants::PROJCS) ||
5615
24.9k
        ci_equal(name, WKTConstants::PROJCRS) ||
5616
24.9k
        ci_equal(name, WKTConstants::PROJECTEDCRS)) {
5617
        // Get the EXTENSION "PROJ4" node before attempting to call
5618
        // buildProjectedCRS() since formulations of WKT1_GDAL from GDAL 2.x
5619
        // with the netCDF driver and the lack the required UNIT[] node
5620
1.29k
        std::string projString = getExtensionProj4(nodeP);
5621
1.29k
        if (!projString.empty() &&
5622
1.29k
            (starts_with(projString, "+proj=ob_tran +o_proj=longlat") ||
5623
0
             starts_with(projString, "+proj=ob_tran +o_proj=lonlat") ||
5624
0
             starts_with(projString, "+proj=ob_tran +o_proj=latlong") ||
5625
0
             starts_with(projString, "+proj=ob_tran +o_proj=latlon"))) {
5626
            // Those are not a projected CRS, but a DerivedGeographic one...
5627
0
            if (projString.find(" +type=crs") == std::string::npos) {
5628
0
                projString += " +type=crs";
5629
0
            }
5630
0
            try {
5631
0
                auto projObj =
5632
0
                    PROJStringParser().createFromPROJString(projString);
5633
0
                auto crs = nn_dynamic_pointer_cast<CRS>(projObj);
5634
0
                if (crs) {
5635
0
                    return util::nn_static_pointer_cast<CRS>(
5636
0
                        applyHorizontalBoundCRSParams(NN_NO_CHECK(crs)));
5637
0
                }
5638
0
            } catch (const io::ParsingException &) {
5639
0
            }
5640
0
        }
5641
1.29k
        return util::nn_static_pointer_cast<CRS>(
5642
1.29k
            applyHorizontalBoundCRSParams(buildProjectedCRS(node)));
5643
1.29k
    }
5644
5645
23.6k
    if (ci_equal(name, WKTConstants::VERT_CS) ||
5646
23.6k
        ci_equal(name, WKTConstants::VERTCS) ||
5647
23.6k
        ci_equal(name, WKTConstants::VERTCRS) ||
5648
23.6k
        ci_equal(name, WKTConstants::VERTICALCRS)) {
5649
2.36k
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) {
5650
0
            return util::nn_static_pointer_cast<CRS>(
5651
0
                buildDerivedVerticalCRS(node));
5652
2.36k
        } else {
5653
2.36k
            return util::nn_static_pointer_cast<CRS>(buildVerticalCRS(node));
5654
2.36k
        }
5655
2.36k
    }
5656
5657
21.2k
    if (ci_equal(name, WKTConstants::COMPD_CS) ||
5658
21.2k
        ci_equal(name, WKTConstants::COMPOUNDCRS)) {
5659
1.95k
        return util::nn_static_pointer_cast<CRS>(buildCompoundCRS(node));
5660
1.95k
    }
5661
5662
19.3k
    if (ci_equal(name, WKTConstants::BOUNDCRS)) {
5663
5
        return util::nn_static_pointer_cast<CRS>(buildBoundCRS(node));
5664
5
    }
5665
5666
19.3k
    if (ci_equal(name, WKTConstants::TIMECRS)) {
5667
5
        if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) {
5668
0
            return util::nn_static_pointer_cast<CRS>(
5669
0
                buildDerivedTemporalCRS(node));
5670
5
        } else {
5671
5
            return util::nn_static_pointer_cast<CRS>(buildTemporalCRS(node));
5672
5
        }
5673
5
    }
5674
5675
19.3k
    if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) {
5676
3
        return util::nn_static_pointer_cast<CRS>(
5677
3
            buildDerivedProjectedCRS(node));
5678
3
    }
5679
5680
19.3k
    if (ci_equal(name, WKTConstants::ENGCRS) ||
5681
19.3k
        ci_equal(name, WKTConstants::ENGINEERINGCRS)) {
5682
27
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) {
5683
13
            return util::nn_static_pointer_cast<CRS>(
5684
13
                buildDerivedEngineeringCRS(node));
5685
14
        } else {
5686
14
            return util::nn_static_pointer_cast<CRS>(buildEngineeringCRS(node));
5687
14
        }
5688
27
    }
5689
5690
19.2k
    if (ci_equal(name, WKTConstants::LOCAL_CS)) {
5691
3.05k
        return util::nn_static_pointer_cast<CRS>(
5692
3.05k
            buildEngineeringCRSFromLocalCS(node));
5693
3.05k
    }
5694
5695
16.2k
    if (ci_equal(name, WKTConstants::PARAMETRICCRS)) {
5696
0
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) {
5697
0
            return util::nn_static_pointer_cast<CRS>(
5698
0
                buildDerivedParametricCRS(node));
5699
0
        } else {
5700
0
            return util::nn_static_pointer_cast<CRS>(buildParametricCRS(node));
5701
0
        }
5702
0
    }
5703
5704
16.2k
    return nullptr;
5705
16.2k
}
5706
5707
// ---------------------------------------------------------------------------
5708
5709
9.50k
BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
5710
9.50k
    const auto *nodeP = node->GP();
5711
9.50k
    const std::string &name(nodeP->value());
5712
5713
9.50k
    auto crs = buildCRS(node);
5714
9.50k
    if (crs) {
5715
2.89k
        return util::nn_static_pointer_cast<BaseObject>(NN_NO_CHECK(crs));
5716
2.89k
    }
5717
5718
    // Datum handled by caller code WKTParser::createFromWKT()
5719
5720
6.61k
    if (ci_equal(name, WKTConstants::ENSEMBLE)) {
5721
118
        return util::nn_static_pointer_cast<BaseObject>(buildDatumEnsemble(
5722
118
            node, PrimeMeridian::GREENWICH,
5723
118
            !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID))));
5724
118
    }
5725
5726
6.49k
    if (ci_equal(name, WKTConstants::VDATUM) ||
5727
6.49k
        ci_equal(name, WKTConstants::VERT_DATUM) ||
5728
6.49k
        ci_equal(name, WKTConstants::VERTICALDATUM) ||
5729
6.49k
        ci_equal(name, WKTConstants::VRF)) {
5730
444
        return util::nn_static_pointer_cast<BaseObject>(
5731
444
            buildVerticalReferenceFrame(node, null_node));
5732
444
    }
5733
5734
6.05k
    if (ci_equal(name, WKTConstants::TDATUM) ||
5735
6.05k
        ci_equal(name, WKTConstants::TIMEDATUM)) {
5736
300
        return util::nn_static_pointer_cast<BaseObject>(
5737
300
            buildTemporalDatum(node));
5738
300
    }
5739
5740
5.75k
    if (ci_equal(name, WKTConstants::EDATUM) ||
5741
5.75k
        ci_equal(name, WKTConstants::ENGINEERINGDATUM)) {
5742
103
        return util::nn_static_pointer_cast<BaseObject>(
5743
103
            buildEngineeringDatum(node));
5744
103
    }
5745
5746
5.64k
    if (ci_equal(name, WKTConstants::PDATUM) ||
5747
5.64k
        ci_equal(name, WKTConstants::PARAMETRICDATUM)) {
5748
284
        return util::nn_static_pointer_cast<BaseObject>(
5749
284
            buildParametricDatum(node));
5750
284
    }
5751
5752
5.36k
    if (ci_equal(name, WKTConstants::ELLIPSOID) ||
5753
5.36k
        ci_equal(name, WKTConstants::SPHEROID)) {
5754
1
        return util::nn_static_pointer_cast<BaseObject>(buildEllipsoid(node));
5755
1
    }
5756
5757
5.36k
    if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) {
5758
0
        auto transf = buildCoordinateOperation(node);
5759
5760
0
        const char *prefixes[] = {
5761
0
            "PROJ-based operation method: ",
5762
0
            "PROJ-based operation method (approximate): "};
5763
0
        for (const char *prefix : prefixes) {
5764
0
            if (starts_with(transf->method()->nameStr(), prefix)) {
5765
0
                auto projString =
5766
0
                    transf->method()->nameStr().substr(strlen(prefix));
5767
0
                return util::nn_static_pointer_cast<BaseObject>(
5768
0
                    PROJBasedOperation::create(
5769
0
                        PropertyMap(), projString, transf->sourceCRS(),
5770
0
                        transf->targetCRS(),
5771
0
                        transf->coordinateOperationAccuracies()));
5772
0
            }
5773
0
        }
5774
5775
0
        return util::nn_static_pointer_cast<BaseObject>(transf);
5776
0
    }
5777
5778
5.36k
    if (ci_equal(name, WKTConstants::CONVERSION)) {
5779
2.58k
        auto conv =
5780
2.58k
            buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE);
5781
5782
2.58k
        if (starts_with(conv->method()->nameStr(),
5783
2.58k
                        "PROJ-based operation method: ")) {
5784
0
            auto projString = conv->method()->nameStr().substr(
5785
0
                strlen("PROJ-based operation method: "));
5786
0
            return util::nn_static_pointer_cast<BaseObject>(
5787
0
                PROJBasedOperation::create(PropertyMap(), projString, nullptr,
5788
0
                                           nullptr, {}));
5789
0
        }
5790
5791
2.58k
        return util::nn_static_pointer_cast<BaseObject>(conv);
5792
2.58k
    }
5793
5794
2.77k
    if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) {
5795
0
        return util::nn_static_pointer_cast<BaseObject>(
5796
0
            buildConcatenatedOperation(node));
5797
0
    }
5798
5799
2.77k
    if (ci_equal(name, WKTConstants::POINTMOTIONOPERATION)) {
5800
0
        return util::nn_static_pointer_cast<BaseObject>(
5801
0
            buildPointMotionOperation(node));
5802
0
    }
5803
5804
2.77k
    if (ci_equal(name, WKTConstants::ID) ||
5805
2.77k
        ci_equal(name, WKTConstants::AUTHORITY)) {
5806
1.49k
        return util::nn_static_pointer_cast<BaseObject>(
5807
1.49k
            NN_NO_CHECK(buildId(node, node, false, false)));
5808
1.49k
    }
5809
5810
1.28k
    if (ci_equal(name, WKTConstants::COORDINATEMETADATA)) {
5811
0
        return util::nn_static_pointer_cast<BaseObject>(
5812
0
            buildCoordinateMetadata(node));
5813
0
    }
5814
5815
1.28k
    throw ParsingException(concat("unhandled keyword: ", name));
5816
1.28k
}
5817
//! @endcond
5818
5819
// ---------------------------------------------------------------------------
5820
5821
//! @cond Doxygen_Suppress
5822
class JSONParser {
5823
    DatabaseContextPtr dbContext_{};
5824
    std::string deformationModelName_{};
5825
5826
    static std::string getString(const json &j, const char *key);
5827
    static json getObject(const json &j, const char *key);
5828
    static json getArray(const json &j, const char *key);
5829
    static int getInteger(const json &j, const char *key);
5830
    static double getNumber(const json &j, const char *key);
5831
    static UnitOfMeasure getUnit(const json &j, const char *key);
5832
    static std::string getName(const json &j);
5833
    static std::string getType(const json &j);
5834
    static Length getLength(const json &j, const char *key);
5835
    static Measure getMeasure(const json &j);
5836
5837
    IdentifierNNPtr buildId(const json &parentJ, const json &j,
5838
                            bool removeInverseOf);
5839
    static ObjectDomainPtr buildObjectDomain(const json &j);
5840
    PropertyMap buildProperties(const json &j, bool removeInverseOf = false,
5841
                                bool nameRequired = true);
5842
5843
    GeographicCRSNNPtr buildGeographicCRS(const json &j);
5844
    GeodeticCRSNNPtr buildGeodeticCRS(const json &j);
5845
    ProjectedCRSNNPtr buildProjectedCRS(const json &j);
5846
    ConversionNNPtr buildConversion(const json &j);
5847
    DatumEnsembleNNPtr buildDatumEnsemble(const json &j);
5848
    GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j);
5849
    VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const json &j);
5850
    DynamicGeodeticReferenceFrameNNPtr
5851
    buildDynamicGeodeticReferenceFrame(const json &j);
5852
    DynamicVerticalReferenceFrameNNPtr
5853
    buildDynamicVerticalReferenceFrame(const json &j);
5854
    EllipsoidNNPtr buildEllipsoid(const json &j);
5855
    PrimeMeridianNNPtr buildPrimeMeridian(const json &j);
5856
    CoordinateSystemNNPtr buildCS(const json &j);
5857
    MeridianNNPtr buildMeridian(const json &j);
5858
    CoordinateSystemAxisNNPtr buildAxis(const json &j);
5859
    VerticalCRSNNPtr buildVerticalCRS(const json &j);
5860
    CRSNNPtr buildCRS(const json &j);
5861
    CompoundCRSNNPtr buildCompoundCRS(const json &j);
5862
    BoundCRSNNPtr buildBoundCRS(const json &j);
5863
    TransformationNNPtr buildTransformation(const json &j);
5864
    PointMotionOperationNNPtr buildPointMotionOperation(const json &j);
5865
    ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j);
5866
    CoordinateMetadataNNPtr buildCoordinateMetadata(const json &j);
5867
5868
    void buildGeodeticDatumOrDatumEnsemble(const json &j,
5869
                                           GeodeticReferenceFramePtr &datum,
5870
                                           DatumEnsemblePtr &datumEnsemble);
5871
5872
0
    static util::optional<std::string> getAnchor(const json &j) {
5873
0
        util::optional<std::string> anchor;
5874
0
        if (j.contains("anchor")) {
5875
0
            anchor = getString(j, "anchor");
5876
0
        }
5877
0
        return anchor;
5878
0
    }
5879
5880
0
    static util::optional<common::Measure> getAnchorEpoch(const json &j) {
5881
0
        if (j.contains("anchor_epoch")) {
5882
0
            return util::optional<common::Measure>(common::Measure(
5883
0
                getNumber(j, "anchor_epoch"), common::UnitOfMeasure::YEAR));
5884
0
        }
5885
0
        return util::optional<common::Measure>();
5886
0
    }
5887
5888
0
    EngineeringDatumNNPtr buildEngineeringDatum(const json &j) {
5889
0
        return EngineeringDatum::create(buildProperties(j), getAnchor(j));
5890
0
    }
5891
5892
0
    ParametricDatumNNPtr buildParametricDatum(const json &j) {
5893
0
        return ParametricDatum::create(buildProperties(j), getAnchor(j));
5894
0
    }
5895
5896
0
    TemporalDatumNNPtr buildTemporalDatum(const json &j) {
5897
0
        auto calendar = getString(j, "calendar");
5898
0
        auto origin = DateTime::create(j.contains("time_origin")
5899
0
                                           ? getString(j, "time_origin")
5900
0
                                           : std::string());
5901
0
        return TemporalDatum::create(buildProperties(j), origin, calendar);
5902
0
    }
5903
5904
    template <class TargetCRS, class DatumBuilderType,
5905
              class CSClass = CoordinateSystem>
5906
    util::nn<std::shared_ptr<TargetCRS>> buildCRS(const json &j,
5907
0
                                                  DatumBuilderType f) {
5908
0
        auto datum = (this->*f)(getObject(j, "datum"));
5909
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
5910
0
        auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
5911
0
        if (!csCast) {
5912
0
            throw ParsingException("coordinate_system not of expected type");
5913
0
        }
5914
0
        return TargetCRS::create(buildProperties(j), datum,
5915
0
                                 NN_NO_CHECK(csCast));
5916
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&))
5917
5918
    template <class TargetCRS, class BaseCRS, class CSClass = CoordinateSystem>
5919
0
    util::nn<std::shared_ptr<TargetCRS>> buildDerivedCRS(const json &j) {
5920
0
        auto baseCRSObj = create(getObject(j, "base_crs"));
5921
0
        auto baseCRS = util::nn_dynamic_pointer_cast<BaseCRS>(baseCRSObj);
5922
0
        if (!baseCRS) {
5923
0
            throw ParsingException("base_crs not of expected type");
5924
0
        }
5925
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
5926
0
        auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
5927
0
        if (!csCast) {
5928
0
            throw ParsingException("coordinate_system not of expected type");
5929
0
        }
5930
0
        auto conv = buildConversion(getObject(j, "conversion"));
5931
0
        return TargetCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv,
5932
0
                                 NN_NO_CHECK(csCast));
5933
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&)
5934
5935
  public:
5936
36
    JSONParser() = default;
5937
5938
36
    JSONParser &attachDatabaseContext(const DatabaseContextPtr &dbContext) {
5939
36
        dbContext_ = dbContext;
5940
36
        return *this;
5941
36
    }
5942
5943
    BaseObjectNNPtr create(const json &j);
5944
};
5945
5946
// ---------------------------------------------------------------------------
5947
5948
36
std::string JSONParser::getString(const json &j, const char *key) {
5949
36
    if (!j.contains(key)) {
5950
6
        throw ParsingException(std::string("Missing \"") + key + "\" key");
5951
6
    }
5952
30
    auto v = j[key];
5953
30
    if (!v.is_string()) {
5954
30
        throw ParsingException(std::string("The value of \"") + key +
5955
30
                               "\" should be a string");
5956
30
    }
5957
0
    return v.get<std::string>();
5958
30
}
5959
5960
// ---------------------------------------------------------------------------
5961
5962
0
json JSONParser::getObject(const json &j, const char *key) {
5963
0
    if (!j.contains(key)) {
5964
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
5965
0
    }
5966
0
    auto v = j[key];
5967
0
    if (!v.is_object()) {
5968
0
        throw ParsingException(std::string("The value of \"") + key +
5969
0
                               "\" should be a object");
5970
0
    }
5971
0
    return v.get<json>();
5972
0
}
5973
5974
// ---------------------------------------------------------------------------
5975
5976
0
json JSONParser::getArray(const json &j, const char *key) {
5977
0
    if (!j.contains(key)) {
5978
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
5979
0
    }
5980
0
    auto v = j[key];
5981
0
    if (!v.is_array()) {
5982
0
        throw ParsingException(std::string("The value of \"") + key +
5983
0
                               "\" should be a array");
5984
0
    }
5985
0
    return v.get<json>();
5986
0
}
5987
5988
// ---------------------------------------------------------------------------
5989
5990
0
int JSONParser::getInteger(const json &j, const char *key) {
5991
0
    if (!j.contains(key)) {
5992
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
5993
0
    }
5994
0
    auto v = j[key];
5995
0
    if (!v.is_number()) {
5996
0
        throw ParsingException(std::string("The value of \"") + key +
5997
0
                               "\" should be an integer");
5998
0
    }
5999
0
    const double dbl = v.get<double>();
6000
0
    if (!(dbl >= std::numeric_limits<int>::min() &&
6001
0
          dbl <= std::numeric_limits<int>::max() &&
6002
0
          static_cast<int>(dbl) == dbl)) {
6003
0
        throw ParsingException(std::string("The value of \"") + key +
6004
0
                               "\" should be an integer");
6005
0
    }
6006
0
    return static_cast<int>(dbl);
6007
0
}
6008
6009
// ---------------------------------------------------------------------------
6010
6011
0
double JSONParser::getNumber(const json &j, const char *key) {
6012
0
    if (!j.contains(key)) {
6013
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6014
0
    }
6015
0
    auto v = j[key];
6016
0
    if (!v.is_number()) {
6017
0
        throw ParsingException(std::string("The value of \"") + key +
6018
0
                               "\" should be a number");
6019
0
    }
6020
0
    return v.get<double>();
6021
0
}
6022
6023
// ---------------------------------------------------------------------------
6024
6025
0
UnitOfMeasure JSONParser::getUnit(const json &j, const char *key) {
6026
0
    if (!j.contains(key)) {
6027
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6028
0
    }
6029
0
    auto v = j[key];
6030
0
    if (v.is_string()) {
6031
0
        auto vStr = v.get<std::string>();
6032
0
        for (const auto &unit : {UnitOfMeasure::METRE, UnitOfMeasure::DEGREE,
6033
0
                                 UnitOfMeasure::SCALE_UNITY}) {
6034
0
            if (vStr == unit.name())
6035
0
                return unit;
6036
0
        }
6037
0
        throw ParsingException("Unknown unit name: " + vStr);
6038
0
    }
6039
0
    if (!v.is_object()) {
6040
0
        throw ParsingException(std::string("The value of \"") + key +
6041
0
                               "\" should be a string or an object");
6042
0
    }
6043
0
    auto typeStr = getType(v);
6044
0
    UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN;
6045
0
    if (typeStr == "LinearUnit") {
6046
0
        type = UnitOfMeasure::Type::LINEAR;
6047
0
    } else if (typeStr == "AngularUnit") {
6048
0
        type = UnitOfMeasure::Type::ANGULAR;
6049
0
    } else if (typeStr == "ScaleUnit") {
6050
0
        type = UnitOfMeasure::Type::SCALE;
6051
0
    } else if (typeStr == "TimeUnit") {
6052
0
        type = UnitOfMeasure::Type::TIME;
6053
0
    } else if (typeStr == "ParametricUnit") {
6054
0
        type = UnitOfMeasure::Type::PARAMETRIC;
6055
0
    } else if (typeStr == "Unit") {
6056
0
        type = UnitOfMeasure::Type::UNKNOWN;
6057
0
    } else {
6058
0
        throw ParsingException("Unsupported value of \"type\"");
6059
0
    }
6060
0
    auto nameStr = getName(v);
6061
0
    auto convFactor = getNumber(v, "conversion_factor");
6062
0
    std::string authorityStr;
6063
0
    std::string codeStr;
6064
0
    if (v.contains("authority") && v.contains("code")) {
6065
0
        authorityStr = getString(v, "authority");
6066
0
        auto code = v["code"];
6067
0
        if (code.is_string()) {
6068
0
            codeStr = code.get<std::string>();
6069
0
        } else if (code.is_number_integer()) {
6070
0
            codeStr = internal::toString(code.get<int>());
6071
0
        } else {
6072
0
            throw ParsingException("Unexpected type for value of \"code\"");
6073
0
        }
6074
0
    }
6075
0
    return UnitOfMeasure(nameStr, convFactor, type, authorityStr, codeStr);
6076
0
}
6077
6078
// ---------------------------------------------------------------------------
6079
6080
0
std::string JSONParser::getName(const json &j) { return getString(j, "name"); }
6081
6082
// ---------------------------------------------------------------------------
6083
6084
0
std::string JSONParser::getType(const json &j) { return getString(j, "type"); }
6085
6086
// ---------------------------------------------------------------------------
6087
6088
0
Length JSONParser::getLength(const json &j, const char *key) {
6089
0
    if (!j.contains(key)) {
6090
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6091
0
    }
6092
0
    auto v = j[key];
6093
0
    if (v.is_number()) {
6094
0
        return Length(v.get<double>(), UnitOfMeasure::METRE);
6095
0
    }
6096
0
    if (v.is_object()) {
6097
0
        return Length(getMeasure(v));
6098
0
    }
6099
0
    throw ParsingException(std::string("The value of \"") + key +
6100
0
                           "\" should be a number or an object");
6101
0
}
6102
6103
// ---------------------------------------------------------------------------
6104
6105
0
Measure JSONParser::getMeasure(const json &j) {
6106
0
    return Measure(getNumber(j, "value"), getUnit(j, "unit"));
6107
0
}
6108
6109
// ---------------------------------------------------------------------------
6110
6111
0
ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) {
6112
0
    optional<std::string> scope;
6113
0
    if (j.contains("scope")) {
6114
0
        scope = getString(j, "scope");
6115
0
    }
6116
0
    std::string area;
6117
0
    if (j.contains("area")) {
6118
0
        area = getString(j, "area");
6119
0
    }
6120
0
    std::vector<GeographicExtentNNPtr> geogExtent;
6121
0
    if (j.contains("bbox")) {
6122
0
        auto bbox = getObject(j, "bbox");
6123
0
        double south = getNumber(bbox, "south_latitude");
6124
0
        double west = getNumber(bbox, "west_longitude");
6125
0
        double north = getNumber(bbox, "north_latitude");
6126
0
        double east = getNumber(bbox, "east_longitude");
6127
0
        try {
6128
0
            geogExtent.emplace_back(
6129
0
                GeographicBoundingBox::create(west, south, east, north));
6130
0
        } catch (const std::exception &e) {
6131
0
            throw ParsingException(
6132
0
                std::string("Invalid bbox node: ").append(e.what()));
6133
0
        }
6134
0
    }
6135
6136
0
    std::vector<VerticalExtentNNPtr> verticalExtent;
6137
0
    if (j.contains("vertical_extent")) {
6138
0
        const auto vertical_extent = getObject(j, "vertical_extent");
6139
0
        const auto min = getNumber(vertical_extent, "minimum");
6140
0
        const auto max = getNumber(vertical_extent, "maximum");
6141
0
        const auto unit = vertical_extent.contains("unit")
6142
0
                              ? getUnit(vertical_extent, "unit")
6143
0
                              : UnitOfMeasure::METRE;
6144
0
        verticalExtent.emplace_back(VerticalExtent::create(
6145
0
            min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
6146
0
    }
6147
6148
0
    std::vector<TemporalExtentNNPtr> temporalExtent;
6149
0
    if (j.contains("temporal_extent")) {
6150
0
        const auto temporal_extent = getObject(j, "temporal_extent");
6151
0
        const auto start = getString(temporal_extent, "start");
6152
0
        const auto end = getString(temporal_extent, "end");
6153
0
        temporalExtent.emplace_back(TemporalExtent::create(start, end));
6154
0
    }
6155
6156
0
    if (scope.has_value() || !area.empty() || !geogExtent.empty() ||
6157
0
        !verticalExtent.empty() || !temporalExtent.empty()) {
6158
0
        util::optional<std::string> description;
6159
0
        if (!area.empty())
6160
0
            description = area;
6161
0
        ExtentPtr extent;
6162
0
        if (description.has_value() || !geogExtent.empty() ||
6163
0
            !verticalExtent.empty() || !temporalExtent.empty()) {
6164
0
            extent = Extent::create(description, geogExtent, verticalExtent,
6165
0
                                    temporalExtent)
6166
0
                         .as_nullable();
6167
0
        }
6168
0
        return ObjectDomain::create(scope, extent).as_nullable();
6169
0
    }
6170
0
    return nullptr;
6171
0
}
6172
6173
// ---------------------------------------------------------------------------
6174
6175
IdentifierNNPtr JSONParser::buildId(const json &parentJ, const json &j,
6176
0
                                    bool removeInverseOf) {
6177
6178
0
    PropertyMap propertiesId;
6179
0
    auto codeSpace(getString(j, "authority"));
6180
0
    if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
6181
0
        codeSpace.back() == ')') {
6182
0
        codeSpace = codeSpace.substr(strlen("INVERSE("));
6183
0
        codeSpace.resize(codeSpace.size() - 1);
6184
0
    }
6185
6186
0
    std::string version;
6187
0
    if (j.contains("version")) {
6188
0
        auto versionJ = j["version"];
6189
0
        if (versionJ.is_string()) {
6190
0
            version = versionJ.get<std::string>();
6191
0
        } else if (versionJ.is_number()) {
6192
0
            const double dblVersion = versionJ.get<double>();
6193
0
            if (dblVersion >= std::numeric_limits<int>::min() &&
6194
0
                dblVersion <= std::numeric_limits<int>::max() &&
6195
0
                static_cast<int>(dblVersion) == dblVersion) {
6196
0
                version = internal::toString(static_cast<int>(dblVersion));
6197
0
            } else {
6198
0
                version = internal::toString(dblVersion, /*precision=*/15);
6199
0
            }
6200
0
        } else {
6201
0
            throw ParsingException("Unexpected type for value of \"version\"");
6202
0
        }
6203
0
    }
6204
6205
    // IAU + 2015 -> IAU_2015
6206
0
    if (dbContext_ && !version.empty()) {
6207
0
        std::string codeSpaceOut;
6208
0
        if (dbContext_->getVersionedAuthority(codeSpace, version,
6209
0
                                              codeSpaceOut)) {
6210
0
            codeSpace = std::move(codeSpaceOut);
6211
0
            version.clear();
6212
0
        }
6213
0
    }
6214
6215
0
    propertiesId.set(metadata::Identifier::CODESPACE_KEY, codeSpace);
6216
0
    propertiesId.set(metadata::Identifier::AUTHORITY_KEY, codeSpace);
6217
0
    if (!j.contains("code")) {
6218
0
        throw ParsingException("Missing \"code\" key");
6219
0
    }
6220
0
    std::string code;
6221
0
    auto codeJ = j["code"];
6222
0
    if (codeJ.is_string()) {
6223
0
        code = codeJ.get<std::string>();
6224
0
    } else if (codeJ.is_number_integer()) {
6225
0
        code = internal::toString(codeJ.get<int>());
6226
0
    } else {
6227
0
        throw ParsingException("Unexpected type for value of \"code\"");
6228
0
    }
6229
6230
    // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone
6231
    // south, we generated a wrong value. Auto-fix that
6232
0
    if (parentJ.contains("type") && getType(parentJ) == "Conversion" &&
6233
0
        codeSpace == Identifier::EPSG && parentJ.contains("name")) {
6234
0
        const auto parentNodeName(getName(parentJ));
6235
0
        if (ci_starts_with(parentNodeName, "UTM Zone ") &&
6236
0
            parentNodeName.find('S') != std::string::npos) {
6237
0
            const int nZone =
6238
0
                atoi(parentNodeName.c_str() + strlen("UTM Zone "));
6239
0
            if (nZone >= 1 && nZone <= 60) {
6240
0
                code = internal::toString(16100 + nZone);
6241
0
            }
6242
0
        }
6243
0
    }
6244
6245
0
    if (!version.empty()) {
6246
0
        propertiesId.set(Identifier::VERSION_KEY, version);
6247
0
    }
6248
6249
0
    if (j.contains("authority_citation")) {
6250
0
        propertiesId.set(Identifier::AUTHORITY_KEY,
6251
0
                         getString(j, "authority_citation"));
6252
0
    }
6253
6254
0
    if (j.contains("uri")) {
6255
0
        propertiesId.set(Identifier::URI_KEY, getString(j, "uri"));
6256
0
    }
6257
6258
0
    return Identifier::create(code, propertiesId);
6259
0
}
6260
6261
// ---------------------------------------------------------------------------
6262
6263
PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf,
6264
0
                                        bool nameRequired) {
6265
0
    PropertyMap map;
6266
6267
0
    if (j.contains("name") || nameRequired) {
6268
0
        std::string name(getName(j));
6269
0
        if (removeInverseOf && starts_with(name, "Inverse of ")) {
6270
0
            name = name.substr(strlen("Inverse of "));
6271
0
        }
6272
0
        map.set(IdentifiedObject::NAME_KEY, name);
6273
0
    }
6274
6275
0
    if (j.contains("ids")) {
6276
0
        auto idsJ = getArray(j, "ids");
6277
0
        auto identifiers = ArrayOfBaseObject::create();
6278
0
        for (const auto &idJ : idsJ) {
6279
0
            if (!idJ.is_object()) {
6280
0
                throw ParsingException(
6281
0
                    "Unexpected type for value of \"ids\" child");
6282
0
            }
6283
0
            identifiers->add(buildId(j, idJ, removeInverseOf));
6284
0
        }
6285
0
        map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
6286
0
    } else if (j.contains("id")) {
6287
0
        auto idJ = getObject(j, "id");
6288
0
        auto identifiers = ArrayOfBaseObject::create();
6289
0
        identifiers->add(buildId(j, idJ, removeInverseOf));
6290
0
        map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
6291
0
    }
6292
6293
0
    if (j.contains("remarks")) {
6294
0
        map.set(IdentifiedObject::REMARKS_KEY, getString(j, "remarks"));
6295
0
    }
6296
6297
0
    if (j.contains("usages")) {
6298
0
        ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
6299
0
        auto usages = j["usages"];
6300
0
        if (!usages.is_array()) {
6301
0
            throw ParsingException("Unexpected type for value of \"usages\"");
6302
0
        }
6303
0
        for (const auto &usage : usages) {
6304
0
            if (!usage.is_object()) {
6305
0
                throw ParsingException(
6306
0
                    "Unexpected type for value of \"usages\" child");
6307
0
            }
6308
0
            auto objectDomain = buildObjectDomain(usage);
6309
0
            if (!objectDomain) {
6310
0
                throw ParsingException("missing children in \"usages\" child");
6311
0
            }
6312
0
            array->add(NN_NO_CHECK(objectDomain));
6313
0
        }
6314
0
        if (!array->empty()) {
6315
0
            map.set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
6316
0
        }
6317
0
    } else {
6318
0
        auto objectDomain = buildObjectDomain(j);
6319
0
        if (objectDomain) {
6320
0
            map.set(ObjectUsage::OBJECT_DOMAIN_KEY, NN_NO_CHECK(objectDomain));
6321
0
        }
6322
0
    }
6323
6324
0
    return map;
6325
0
}
6326
6327
// ---------------------------------------------------------------------------
6328
6329
BaseObjectNNPtr JSONParser::create(const json &j)
6330
6331
36
{
6332
36
    if (!j.is_object()) {
6333
0
        throw ParsingException("JSON object expected");
6334
0
    }
6335
36
    auto type = getString(j, "type");
6336
36
    if (type == "GeographicCRS") {
6337
0
        return buildGeographicCRS(j);
6338
0
    }
6339
36
    if (type == "GeodeticCRS") {
6340
0
        return buildGeodeticCRS(j);
6341
0
    }
6342
36
    if (type == "ProjectedCRS") {
6343
0
        return buildProjectedCRS(j);
6344
0
    }
6345
36
    if (type == "VerticalCRS") {
6346
0
        return buildVerticalCRS(j);
6347
0
    }
6348
36
    if (type == "CompoundCRS") {
6349
0
        return buildCompoundCRS(j);
6350
0
    }
6351
36
    if (type == "BoundCRS") {
6352
0
        return buildBoundCRS(j);
6353
0
    }
6354
36
    if (type == "EngineeringCRS") {
6355
0
        return buildCRS<EngineeringCRS>(j, &JSONParser::buildEngineeringDatum);
6356
0
    }
6357
36
    if (type == "ParametricCRS") {
6358
0
        return buildCRS<ParametricCRS,
6359
0
                        decltype(&JSONParser::buildParametricDatum),
6360
0
                        ParametricCS>(j, &JSONParser::buildParametricDatum);
6361
0
    }
6362
36
    if (type == "TemporalCRS") {
6363
0
        return buildCRS<TemporalCRS, decltype(&JSONParser::buildTemporalDatum),
6364
0
                        TemporalCS>(j, &JSONParser::buildTemporalDatum);
6365
0
    }
6366
36
    if (type == "DerivedGeodeticCRS") {
6367
0
        auto baseCRSObj = create(getObject(j, "base_crs"));
6368
0
        auto baseCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(baseCRSObj);
6369
0
        if (!baseCRS) {
6370
0
            throw ParsingException("base_crs not of expected type");
6371
0
        }
6372
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
6373
0
        auto conv = buildConversion(getObject(j, "conversion"));
6374
0
        auto csCartesian = util::nn_dynamic_pointer_cast<CartesianCS>(cs);
6375
0
        if (csCartesian)
6376
0
            return DerivedGeodeticCRS::create(buildProperties(j),
6377
0
                                              NN_NO_CHECK(baseCRS), conv,
6378
0
                                              NN_NO_CHECK(csCartesian));
6379
0
        auto csSpherical = util::nn_dynamic_pointer_cast<SphericalCS>(cs);
6380
0
        if (csSpherical)
6381
0
            return DerivedGeodeticCRS::create(buildProperties(j),
6382
0
                                              NN_NO_CHECK(baseCRS), conv,
6383
0
                                              NN_NO_CHECK(csSpherical));
6384
0
        throw ParsingException("coordinate_system not of expected type");
6385
0
    }
6386
36
    if (type == "DerivedGeographicCRS") {
6387
0
        return buildDerivedCRS<DerivedGeographicCRS, GeodeticCRS,
6388
0
                               EllipsoidalCS>(j);
6389
0
    }
6390
36
    if (type == "DerivedProjectedCRS") {
6391
0
        return buildDerivedCRS<DerivedProjectedCRS, ProjectedCRS>(j);
6392
0
    }
6393
36
    if (type == "DerivedVerticalCRS") {
6394
0
        return buildDerivedCRS<DerivedVerticalCRS, VerticalCRS, VerticalCS>(j);
6395
0
    }
6396
36
    if (type == "DerivedEngineeringCRS") {
6397
0
        return buildDerivedCRS<DerivedEngineeringCRS, EngineeringCRS>(j);
6398
0
    }
6399
36
    if (type == "DerivedParametricCRS") {
6400
0
        return buildDerivedCRS<DerivedParametricCRS, ParametricCRS,
6401
0
                               ParametricCS>(j);
6402
0
    }
6403
36
    if (type == "DerivedTemporalCRS") {
6404
0
        return buildDerivedCRS<DerivedTemporalCRS, TemporalCRS, TemporalCS>(j);
6405
0
    }
6406
36
    if (type == "DatumEnsemble") {
6407
0
        return buildDatumEnsemble(j);
6408
0
    }
6409
36
    if (type == "GeodeticReferenceFrame") {
6410
0
        return buildGeodeticReferenceFrame(j);
6411
0
    }
6412
36
    if (type == "VerticalReferenceFrame") {
6413
0
        return buildVerticalReferenceFrame(j);
6414
0
    }
6415
36
    if (type == "DynamicGeodeticReferenceFrame") {
6416
0
        return buildDynamicGeodeticReferenceFrame(j);
6417
0
    }
6418
36
    if (type == "DynamicVerticalReferenceFrame") {
6419
0
        return buildDynamicVerticalReferenceFrame(j);
6420
0
    }
6421
36
    if (type == "EngineeringDatum") {
6422
0
        return buildEngineeringDatum(j);
6423
0
    }
6424
36
    if (type == "ParametricDatum") {
6425
0
        return buildParametricDatum(j);
6426
0
    }
6427
36
    if (type == "TemporalDatum") {
6428
0
        return buildTemporalDatum(j);
6429
0
    }
6430
36
    if (type == "Ellipsoid") {
6431
0
        return buildEllipsoid(j);
6432
0
    }
6433
36
    if (type == "PrimeMeridian") {
6434
0
        return buildPrimeMeridian(j);
6435
0
    }
6436
36
    if (type == "CoordinateSystem") {
6437
0
        return buildCS(j);
6438
0
    }
6439
36
    if (type == "Conversion") {
6440
0
        return buildConversion(j);
6441
0
    }
6442
36
    if (type == "Transformation") {
6443
0
        return buildTransformation(j);
6444
0
    }
6445
36
    if (type == "PointMotionOperation") {
6446
0
        return buildPointMotionOperation(j);
6447
0
    }
6448
36
    if (type == "ConcatenatedOperation") {
6449
0
        return buildConcatenatedOperation(j);
6450
0
    }
6451
36
    if (type == "CoordinateMetadata") {
6452
0
        return buildCoordinateMetadata(j);
6453
0
    }
6454
36
    if (type == "Axis") {
6455
0
        return buildAxis(j);
6456
0
    }
6457
36
    throw ParsingException("Unsupported value of \"type\"");
6458
36
}
6459
6460
// ---------------------------------------------------------------------------
6461
6462
void JSONParser::buildGeodeticDatumOrDatumEnsemble(
6463
    const json &j, GeodeticReferenceFramePtr &datum,
6464
0
    DatumEnsemblePtr &datumEnsemble) {
6465
0
    if (j.contains("datum")) {
6466
0
        auto datumJ = getObject(j, "datum");
6467
6468
0
        if (j.contains("deformation_models")) {
6469
0
            auto deformationModelsJ = getArray(j, "deformation_models");
6470
0
            if (!deformationModelsJ.empty()) {
6471
0
                const auto &deformationModelJ = deformationModelsJ[0];
6472
0
                deformationModelName_ = getString(deformationModelJ, "name");
6473
                // We can handle only one for now
6474
0
            }
6475
0
        }
6476
6477
0
        datum = util::nn_dynamic_pointer_cast<GeodeticReferenceFrame>(
6478
0
            create(datumJ));
6479
0
        if (!datum) {
6480
0
            throw ParsingException("datum of wrong type");
6481
0
        }
6482
6483
0
        deformationModelName_.clear();
6484
0
    } else {
6485
0
        datumEnsemble =
6486
0
            buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
6487
0
    }
6488
0
}
6489
6490
// ---------------------------------------------------------------------------
6491
6492
0
GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) {
6493
0
    GeodeticReferenceFramePtr datum;
6494
0
    DatumEnsemblePtr datumEnsemble;
6495
0
    buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
6496
0
    auto csJ = getObject(j, "coordinate_system");
6497
0
    auto ellipsoidalCS =
6498
0
        util::nn_dynamic_pointer_cast<EllipsoidalCS>(buildCS(csJ));
6499
0
    if (!ellipsoidalCS) {
6500
0
        throw ParsingException("expected an ellipsoidal CS");
6501
0
    }
6502
0
    return GeographicCRS::create(buildProperties(j), datum, datumEnsemble,
6503
0
                                 NN_NO_CHECK(ellipsoidalCS));
6504
0
}
6505
6506
// ---------------------------------------------------------------------------
6507
6508
0
GeodeticCRSNNPtr JSONParser::buildGeodeticCRS(const json &j) {
6509
0
    GeodeticReferenceFramePtr datum;
6510
0
    DatumEnsemblePtr datumEnsemble;
6511
0
    buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
6512
0
    auto csJ = getObject(j, "coordinate_system");
6513
0
    auto cs = buildCS(csJ);
6514
0
    auto props = buildProperties(j);
6515
0
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
6516
0
    if (cartesianCS) {
6517
0
        if (cartesianCS->axisList().size() != 3) {
6518
0
            throw ParsingException(
6519
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
6520
0
        }
6521
0
        try {
6522
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
6523
0
                                       NN_NO_CHECK(cartesianCS));
6524
0
        } catch (const util::Exception &e) {
6525
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
6526
0
                                   e.what());
6527
0
        }
6528
0
    }
6529
6530
0
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
6531
0
    if (sphericalCS) {
6532
0
        try {
6533
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
6534
0
                                       NN_NO_CHECK(sphericalCS));
6535
0
        } catch (const util::Exception &e) {
6536
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
6537
0
                                   e.what());
6538
0
        }
6539
0
    }
6540
0
    throw ParsingException("expected a Cartesian or spherical CS");
6541
0
}
6542
6543
// ---------------------------------------------------------------------------
6544
6545
0
ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) {
6546
0
    auto jBaseCRS = getObject(j, "base_crs");
6547
0
    auto jBaseCS = getObject(jBaseCRS, "coordinate_system");
6548
0
    auto baseCS = buildCS(jBaseCS);
6549
0
    auto baseCRS = dynamic_cast<EllipsoidalCS *>(baseCS.get()) != nullptr
6550
0
                       ? util::nn_static_pointer_cast<GeodeticCRS>(
6551
0
                             buildGeographicCRS(jBaseCRS))
6552
0
                       : buildGeodeticCRS(jBaseCRS);
6553
0
    auto csJ = getObject(j, "coordinate_system");
6554
0
    auto cartesianCS = util::nn_dynamic_pointer_cast<CartesianCS>(buildCS(csJ));
6555
0
    if (!cartesianCS) {
6556
0
        throw ParsingException("expected a Cartesian CS");
6557
0
    }
6558
0
    auto conv = buildConversion(getObject(j, "conversion"));
6559
0
    return ProjectedCRS::create(buildProperties(j), baseCRS, conv,
6560
0
                                NN_NO_CHECK(cartesianCS));
6561
0
}
6562
6563
// ---------------------------------------------------------------------------
6564
6565
0
VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) {
6566
0
    VerticalReferenceFramePtr datum;
6567
0
    DatumEnsemblePtr datumEnsemble;
6568
0
    if (j.contains("datum")) {
6569
0
        auto datumJ = getObject(j, "datum");
6570
6571
0
        if (j.contains("deformation_models")) {
6572
0
            auto deformationModelsJ = getArray(j, "deformation_models");
6573
0
            if (!deformationModelsJ.empty()) {
6574
0
                const auto &deformationModelJ = deformationModelsJ[0];
6575
0
                deformationModelName_ = getString(deformationModelJ, "name");
6576
                // We can handle only one for now
6577
0
            }
6578
0
        }
6579
6580
0
        datum = util::nn_dynamic_pointer_cast<VerticalReferenceFrame>(
6581
0
            create(datumJ));
6582
0
        if (!datum) {
6583
0
            throw ParsingException("datum of wrong type");
6584
0
        }
6585
0
    } else {
6586
0
        datumEnsemble =
6587
0
            buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
6588
0
    }
6589
0
    auto csJ = getObject(j, "coordinate_system");
6590
0
    auto verticalCS = util::nn_dynamic_pointer_cast<VerticalCS>(buildCS(csJ));
6591
0
    if (!verticalCS) {
6592
0
        throw ParsingException("expected a vertical CS");
6593
0
    }
6594
6595
0
    const auto buildGeoidModel = [this, &datum, &datumEnsemble,
6596
0
                                  &verticalCS](const json &geoidModelJ) {
6597
0
        auto propsModel = buildProperties(geoidModelJ);
6598
0
        const auto dummyCRS = VerticalCRS::create(
6599
0
            PropertyMap(), datum, datumEnsemble, NN_NO_CHECK(verticalCS));
6600
0
        CRSPtr interpolationCRS;
6601
0
        if (geoidModelJ.contains("interpolation_crs")) {
6602
0
            auto interpolationCRSJ =
6603
0
                getObject(geoidModelJ, "interpolation_crs");
6604
0
            interpolationCRS = buildCRS(interpolationCRSJ).as_nullable();
6605
0
        }
6606
0
        return Transformation::create(
6607
0
            propsModel, dummyCRS,
6608
0
            GeographicCRS::EPSG_4979, // arbitrarily chosen. Ignored,
6609
0
            interpolationCRS,
6610
0
            OperationMethod::create(PropertyMap(),
6611
0
                                    std::vector<OperationParameterNNPtr>()),
6612
0
            {}, {});
6613
0
    };
6614
6615
0
    auto props = buildProperties(j);
6616
0
    if (j.contains("geoid_model")) {
6617
0
        auto geoidModelJ = getObject(j, "geoid_model");
6618
0
        props.set("GEOID_MODEL", buildGeoidModel(geoidModelJ));
6619
0
    } else if (j.contains("geoid_models")) {
6620
0
        auto geoidModelsJ = getArray(j, "geoid_models");
6621
0
        auto geoidModels = ArrayOfBaseObject::create();
6622
0
        for (const auto &geoidModelJ : geoidModelsJ) {
6623
0
            geoidModels->add(buildGeoidModel(geoidModelJ));
6624
0
        }
6625
0
        props.set("GEOID_MODEL", geoidModels);
6626
0
    }
6627
6628
0
    return VerticalCRS::create(props, datum, datumEnsemble,
6629
0
                               NN_NO_CHECK(verticalCS));
6630
0
}
6631
6632
// ---------------------------------------------------------------------------
6633
6634
0
CRSNNPtr JSONParser::buildCRS(const json &j) {
6635
0
    auto crs = util::nn_dynamic_pointer_cast<CRS>(create(j));
6636
0
    if (crs) {
6637
0
        return NN_NO_CHECK(crs);
6638
0
    }
6639
0
    throw ParsingException("Object is not a CRS");
6640
0
}
6641
6642
// ---------------------------------------------------------------------------
6643
6644
0
CompoundCRSNNPtr JSONParser::buildCompoundCRS(const json &j) {
6645
0
    auto componentsJ = getArray(j, "components");
6646
0
    std::vector<CRSNNPtr> components;
6647
0
    for (const auto &componentJ : componentsJ) {
6648
0
        if (!componentJ.is_object()) {
6649
0
            throw ParsingException(
6650
0
                "Unexpected type for a \"components\" child");
6651
0
        }
6652
0
        components.push_back(buildCRS(componentJ));
6653
0
    }
6654
0
    return CompoundCRS::create(buildProperties(j), components);
6655
0
}
6656
6657
// ---------------------------------------------------------------------------
6658
6659
0
ConversionNNPtr JSONParser::buildConversion(const json &j) {
6660
0
    auto methodJ = getObject(j, "method");
6661
0
    auto convProps = buildProperties(j);
6662
0
    auto methodProps = buildProperties(methodJ);
6663
0
    if (!j.contains("parameters")) {
6664
0
        return Conversion::create(convProps, methodProps, {}, {});
6665
0
    }
6666
6667
0
    auto parametersJ = getArray(j, "parameters");
6668
0
    std::vector<OperationParameterNNPtr> parameters;
6669
0
    std::vector<ParameterValueNNPtr> values;
6670
0
    for (const auto &param : parametersJ) {
6671
0
        if (!param.is_object()) {
6672
0
            throw ParsingException(
6673
0
                "Unexpected type for a \"parameters\" child");
6674
0
        }
6675
0
        parameters.emplace_back(
6676
0
            OperationParameter::create(buildProperties(param)));
6677
0
        if (isIntegerParameter(parameters.back())) {
6678
0
            values.emplace_back(
6679
0
                ParameterValue::create(getInteger(param, "value")));
6680
0
        } else {
6681
0
            values.emplace_back(ParameterValue::create(getMeasure(param)));
6682
0
        }
6683
0
    }
6684
6685
0
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
6686
0
        dbContext_, parameters, values);
6687
6688
0
    std::string convName;
6689
0
    std::string methodName;
6690
0
    if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
6691
0
        methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
6692
0
        starts_with(convName, "Inverse of ") &&
6693
0
        starts_with(methodName, "Inverse of ")) {
6694
6695
0
        auto invConvProps = buildProperties(j, true);
6696
0
        auto invMethodProps = buildProperties(methodJ, true);
6697
0
        auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
6698
0
            Conversion::create(invConvProps, invMethodProps, parameters, values)
6699
0
                ->inverse()));
6700
0
        if (interpolationCRS)
6701
0
            conv->setInterpolationCRS(interpolationCRS);
6702
0
        return conv;
6703
0
    }
6704
0
    auto conv = Conversion::create(convProps, methodProps, parameters, values);
6705
0
    if (interpolationCRS)
6706
0
        conv->setInterpolationCRS(interpolationCRS);
6707
0
    return conv;
6708
0
}
6709
6710
// ---------------------------------------------------------------------------
6711
6712
0
BoundCRSNNPtr JSONParser::buildBoundCRS(const json &j) {
6713
6714
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6715
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6716
0
    auto transformationJ = getObject(j, "transformation");
6717
0
    auto methodJ = getObject(transformationJ, "method");
6718
0
    auto parametersJ = getArray(transformationJ, "parameters");
6719
0
    std::vector<OperationParameterNNPtr> parameters;
6720
0
    std::vector<ParameterValueNNPtr> values;
6721
0
    for (const auto &param : parametersJ) {
6722
0
        if (!param.is_object()) {
6723
0
            throw ParsingException(
6724
0
                "Unexpected type for a \"parameters\" child");
6725
0
        }
6726
0
        parameters.emplace_back(
6727
0
            OperationParameter::create(buildProperties(param)));
6728
0
        if (param.contains("value")) {
6729
0
            auto v = param["value"];
6730
0
            if (v.is_string()) {
6731
0
                values.emplace_back(
6732
0
                    ParameterValue::createFilename(v.get<std::string>()));
6733
0
                continue;
6734
0
            }
6735
0
        }
6736
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6737
0
    }
6738
6739
0
    const auto transformation = [&]() {
6740
        // Unofficial extension / mostly for testing purposes.
6741
        // Allow to explicitly specify the source_crs of the transformation of
6742
        // the boundCRS if it is not the source_crs of the BoundCRS. Cf
6743
        // https://github.com/OSGeo/PROJ/issues/3428 use case
6744
0
        if (transformationJ.contains("source_crs")) {
6745
0
            auto sourceTransformationCRS =
6746
0
                buildCRS(getObject(transformationJ, "source_crs"));
6747
0
            auto interpolationCRS =
6748
0
                dealWithEPSGCodeForInterpolationCRSParameter(
6749
0
                    dbContext_, parameters, values);
6750
0
            return Transformation::create(
6751
0
                buildProperties(transformationJ), sourceTransformationCRS,
6752
0
                targetCRS, interpolationCRS, buildProperties(methodJ),
6753
0
                parameters, values, std::vector<PositionalAccuracyNNPtr>());
6754
0
        }
6755
6756
0
        return buildTransformationForBoundCRS(
6757
0
            dbContext_, buildProperties(transformationJ),
6758
0
            buildProperties(methodJ), sourceCRS, targetCRS, parameters, values);
6759
0
    }();
6760
6761
0
    return BoundCRS::create(buildProperties(j,
6762
0
                                            /* removeInverseOf= */ false,
6763
0
                                            /* nameRequired=*/false),
6764
0
                            sourceCRS, targetCRS, transformation);
6765
0
}
6766
6767
// ---------------------------------------------------------------------------
6768
6769
0
TransformationNNPtr JSONParser::buildTransformation(const json &j) {
6770
6771
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6772
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6773
0
    auto methodJ = getObject(j, "method");
6774
0
    auto parametersJ = getArray(j, "parameters");
6775
0
    std::vector<OperationParameterNNPtr> parameters;
6776
0
    std::vector<ParameterValueNNPtr> values;
6777
0
    for (const auto &param : parametersJ) {
6778
0
        if (!param.is_object()) {
6779
0
            throw ParsingException(
6780
0
                "Unexpected type for a \"parameters\" child");
6781
0
        }
6782
0
        parameters.emplace_back(
6783
0
            OperationParameter::create(buildProperties(param)));
6784
0
        if (param.contains("value")) {
6785
0
            auto v = param["value"];
6786
0
            if (v.is_string()) {
6787
0
                values.emplace_back(
6788
0
                    ParameterValue::createFilename(v.get<std::string>()));
6789
0
                continue;
6790
0
            }
6791
0
        }
6792
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6793
0
    }
6794
0
    CRSPtr interpolationCRS;
6795
0
    if (j.contains("interpolation_crs")) {
6796
0
        interpolationCRS =
6797
0
            buildCRS(getObject(j, "interpolation_crs")).as_nullable();
6798
0
    }
6799
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6800
0
    if (j.contains("accuracy")) {
6801
0
        accuracies.push_back(
6802
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6803
0
    }
6804
6805
0
    return Transformation::create(buildProperties(j), sourceCRS, targetCRS,
6806
0
                                  interpolationCRS, buildProperties(methodJ),
6807
0
                                  parameters, values, accuracies);
6808
0
}
6809
6810
// ---------------------------------------------------------------------------
6811
6812
0
PointMotionOperationNNPtr JSONParser::buildPointMotionOperation(const json &j) {
6813
6814
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6815
0
    auto methodJ = getObject(j, "method");
6816
0
    auto parametersJ = getArray(j, "parameters");
6817
0
    std::vector<OperationParameterNNPtr> parameters;
6818
0
    std::vector<ParameterValueNNPtr> values;
6819
0
    for (const auto &param : parametersJ) {
6820
0
        if (!param.is_object()) {
6821
0
            throw ParsingException(
6822
0
                "Unexpected type for a \"parameters\" child");
6823
0
        }
6824
0
        parameters.emplace_back(
6825
0
            OperationParameter::create(buildProperties(param)));
6826
0
        if (param.contains("value")) {
6827
0
            auto v = param["value"];
6828
0
            if (v.is_string()) {
6829
0
                values.emplace_back(
6830
0
                    ParameterValue::createFilename(v.get<std::string>()));
6831
0
                continue;
6832
0
            }
6833
0
        }
6834
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6835
0
    }
6836
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6837
0
    if (j.contains("accuracy")) {
6838
0
        accuracies.push_back(
6839
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6840
0
    }
6841
6842
0
    return PointMotionOperation::create(buildProperties(j), sourceCRS,
6843
0
                                        buildProperties(methodJ), parameters,
6844
0
                                        values, accuracies);
6845
0
}
6846
6847
// ---------------------------------------------------------------------------
6848
6849
ConcatenatedOperationNNPtr
6850
0
JSONParser::buildConcatenatedOperation(const json &j) {
6851
6852
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6853
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6854
0
    auto stepsJ = getArray(j, "steps");
6855
0
    std::vector<CoordinateOperationNNPtr> operations;
6856
0
    for (const auto &stepJ : stepsJ) {
6857
0
        if (!stepJ.is_object()) {
6858
0
            throw ParsingException("Unexpected type for a \"steps\" child");
6859
0
        }
6860
0
        auto op = nn_dynamic_pointer_cast<CoordinateOperation>(create(stepJ));
6861
0
        if (!op) {
6862
0
            throw ParsingException("Invalid content in a \"steps\" child");
6863
0
        }
6864
0
        operations.emplace_back(NN_NO_CHECK(op));
6865
0
    }
6866
6867
0
    ConcatenatedOperation::fixSteps(sourceCRS, targetCRS, operations,
6868
0
                                    dbContext_,
6869
0
                                    /* fixDirectionAllowed = */ true);
6870
6871
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6872
0
    if (j.contains("accuracy")) {
6873
0
        accuracies.push_back(
6874
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6875
0
    }
6876
6877
0
    try {
6878
0
        return ConcatenatedOperation::create(buildProperties(j), operations,
6879
0
                                             accuracies);
6880
0
    } catch (const InvalidOperation &e) {
6881
0
        throw ParsingException(
6882
0
            std::string("Cannot build concatenated operation: ") + e.what());
6883
0
    }
6884
0
}
6885
6886
// ---------------------------------------------------------------------------
6887
6888
0
CoordinateMetadataNNPtr JSONParser::buildCoordinateMetadata(const json &j) {
6889
6890
0
    auto crs = buildCRS(getObject(j, "crs"));
6891
0
    if (j.contains("coordinateEpoch")) {
6892
0
        auto jCoordinateEpoch = j["coordinateEpoch"];
6893
0
        if (jCoordinateEpoch.is_number()) {
6894
0
            return CoordinateMetadata::create(
6895
0
                crs, jCoordinateEpoch.get<double>(), dbContext_);
6896
0
        }
6897
0
        throw ParsingException(
6898
0
            "Unexpected type for value of \"coordinateEpoch\"");
6899
0
    }
6900
0
    return CoordinateMetadata::create(crs);
6901
0
}
6902
6903
// ---------------------------------------------------------------------------
6904
6905
0
MeridianNNPtr JSONParser::buildMeridian(const json &j) {
6906
0
    if (!j.contains("longitude")) {
6907
0
        throw ParsingException("Missing \"longitude\" key");
6908
0
    }
6909
0
    auto longitude = j["longitude"];
6910
0
    if (longitude.is_number()) {
6911
0
        return Meridian::create(
6912
0
            Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
6913
0
    } else if (longitude.is_object()) {
6914
0
        return Meridian::create(Angle(getMeasure(longitude)));
6915
0
    }
6916
0
    throw ParsingException("Unexpected type for value of \"longitude\"");
6917
0
}
6918
6919
// ---------------------------------------------------------------------------
6920
6921
0
CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) {
6922
0
    auto dirString = getString(j, "direction");
6923
0
    auto abbreviation = getString(j, "abbreviation");
6924
0
    const UnitOfMeasure unit(
6925
0
        j.contains("unit")
6926
0
            ? getUnit(j, "unit")
6927
0
            : UnitOfMeasure(std::string(), 1.0, UnitOfMeasure::Type::NONE));
6928
0
    auto direction = AxisDirection::valueOf(dirString);
6929
0
    if (!direction) {
6930
0
        throw ParsingException(concat("unhandled axis direction: ", dirString));
6931
0
    }
6932
0
    auto meridian = j.contains("meridian")
6933
0
                        ? buildMeridian(getObject(j, "meridian")).as_nullable()
6934
0
                        : nullptr;
6935
6936
0
    util::optional<double> minVal;
6937
0
    if (j.contains("minimum_value")) {
6938
0
        minVal = getNumber(j, "minimum_value");
6939
0
    }
6940
6941
0
    util::optional<double> maxVal;
6942
0
    if (j.contains("maximum_value")) {
6943
0
        maxVal = getNumber(j, "maximum_value");
6944
0
    }
6945
6946
0
    util::optional<RangeMeaning> rangeMeaning;
6947
0
    if (j.contains("range_meaning")) {
6948
0
        const auto val = getString(j, "range_meaning");
6949
0
        const RangeMeaning *meaning = RangeMeaning::valueOf(val);
6950
0
        if (meaning == nullptr) {
6951
0
            throw ParsingException(
6952
0
                concat("buildAxis: invalid range_meaning value: ", val));
6953
0
        }
6954
0
        rangeMeaning = util::optional<RangeMeaning>(*meaning);
6955
0
    }
6956
6957
0
    return CoordinateSystemAxis::create(buildProperties(j), abbreviation,
6958
0
                                        *direction, unit, minVal, maxVal,
6959
0
                                        rangeMeaning, meridian);
6960
0
}
6961
6962
// ---------------------------------------------------------------------------
6963
6964
0
CoordinateSystemNNPtr JSONParser::buildCS(const json &j) {
6965
0
    auto subtype = getString(j, "subtype");
6966
0
    if (!j.contains("axis")) {
6967
0
        throw ParsingException("Missing \"axis\" key");
6968
0
    }
6969
0
    auto jAxisList = j["axis"];
6970
0
    if (!jAxisList.is_array()) {
6971
0
        throw ParsingException("Unexpected type for value of \"axis\"");
6972
0
    }
6973
0
    std::vector<CoordinateSystemAxisNNPtr> axisList;
6974
0
    for (const auto &axis : jAxisList) {
6975
0
        if (!axis.is_object()) {
6976
0
            throw ParsingException(
6977
0
                "Unexpected type for value of a \"axis\" member");
6978
0
        }
6979
0
        axisList.emplace_back(buildAxis(axis));
6980
0
    }
6981
0
    const PropertyMap &csMap = emptyPropertyMap;
6982
0
    const auto axisCount = axisList.size();
6983
0
    if (subtype == EllipsoidalCS::WKT2_TYPE) {
6984
0
        if (axisCount == 2) {
6985
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
6986
0
        }
6987
0
        if (axisCount == 3) {
6988
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
6989
0
                                         axisList[2]);
6990
0
        }
6991
0
        throw ParsingException("Expected 2 or 3 axis");
6992
0
    }
6993
0
    if (subtype == CartesianCS::WKT2_TYPE) {
6994
0
        if (axisCount == 2) {
6995
0
            return CartesianCS::create(csMap, axisList[0], axisList[1]);
6996
0
        }
6997
0
        if (axisCount == 3) {
6998
0
            return CartesianCS::create(csMap, axisList[0], axisList[1],
6999
0
                                       axisList[2]);
7000
0
        }
7001
0
        throw ParsingException("Expected 2 or 3 axis");
7002
0
    }
7003
0
    if (subtype == AffineCS::WKT2_TYPE) {
7004
0
        if (axisCount == 2) {
7005
0
            return AffineCS::create(csMap, axisList[0], axisList[1]);
7006
0
        }
7007
0
        if (axisCount == 3) {
7008
0
            return AffineCS::create(csMap, axisList[0], axisList[1],
7009
0
                                    axisList[2]);
7010
0
        }
7011
0
        throw ParsingException("Expected 2 or 3 axis");
7012
0
    }
7013
0
    if (subtype == VerticalCS::WKT2_TYPE) {
7014
0
        if (axisCount == 1) {
7015
0
            return VerticalCS::create(csMap, axisList[0]);
7016
0
        }
7017
0
        throw ParsingException("Expected 1 axis");
7018
0
    }
7019
0
    if (subtype == SphericalCS::WKT2_TYPE) {
7020
0
        if (axisCount == 2) {
7021
            // Extension to ISO19111 to support (planet)-ocentric CS with
7022
            // geocentric latitude
7023
0
            return SphericalCS::create(csMap, axisList[0], axisList[1]);
7024
0
        } else if (axisCount == 3) {
7025
0
            return SphericalCS::create(csMap, axisList[0], axisList[1],
7026
0
                                       axisList[2]);
7027
0
        }
7028
0
        throw ParsingException("Expected 2 or 3 axis");
7029
0
    }
7030
0
    if (subtype == OrdinalCS::WKT2_TYPE) {
7031
0
        return OrdinalCS::create(csMap, axisList);
7032
0
    }
7033
0
    if (subtype == ParametricCS::WKT2_TYPE) {
7034
0
        if (axisCount == 1) {
7035
0
            return ParametricCS::create(csMap, axisList[0]);
7036
0
        }
7037
0
        throw ParsingException("Expected 1 axis");
7038
0
    }
7039
0
    if (subtype == DateTimeTemporalCS::WKT2_2019_TYPE) {
7040
0
        if (axisCount == 1) {
7041
0
            return DateTimeTemporalCS::create(csMap, axisList[0]);
7042
0
        }
7043
0
        throw ParsingException("Expected 1 axis");
7044
0
    }
7045
0
    if (subtype == TemporalCountCS::WKT2_2019_TYPE) {
7046
0
        if (axisCount == 1) {
7047
0
            return TemporalCountCS::create(csMap, axisList[0]);
7048
0
        }
7049
0
        throw ParsingException("Expected 1 axis");
7050
0
    }
7051
0
    if (subtype == TemporalMeasureCS::WKT2_2019_TYPE) {
7052
0
        if (axisCount == 1) {
7053
0
            return TemporalMeasureCS::create(csMap, axisList[0]);
7054
0
        }
7055
0
        throw ParsingException("Expected 1 axis");
7056
0
    }
7057
0
    throw ParsingException("Unhandled value for subtype");
7058
0
}
7059
7060
// ---------------------------------------------------------------------------
7061
7062
0
DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) {
7063
0
    auto membersJ = getArray(j, "members");
7064
0
    std::vector<DatumNNPtr> datums;
7065
0
    const bool hasEllipsoid(j.contains("ellipsoid"));
7066
0
    for (const auto &memberJ : membersJ) {
7067
0
        if (!memberJ.is_object()) {
7068
0
            throw ParsingException(
7069
0
                "Unexpected type for value of a \"members\" member");
7070
0
        }
7071
0
        auto datumName(getName(memberJ));
7072
0
        bool datumAdded = false;
7073
0
        if (dbContext_ && memberJ.contains("id")) {
7074
0
            auto id = getObject(memberJ, "id");
7075
0
            auto authority = getString(id, "authority");
7076
0
            auto authFactory =
7077
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext_), authority);
7078
0
            auto code = id["code"];
7079
0
            std::string codeStr;
7080
0
            if (code.is_string()) {
7081
0
                codeStr = code.get<std::string>();
7082
0
            } else if (code.is_number_integer()) {
7083
0
                codeStr = internal::toString(code.get<int>());
7084
0
            } else {
7085
0
                throw ParsingException("Unexpected type for value of \"code\"");
7086
0
            }
7087
0
            try {
7088
0
                datums.push_back(authFactory->createDatum(codeStr));
7089
0
                datumAdded = true;
7090
0
            } catch (const std::exception &) {
7091
                // Silently ignore, as this isn't necessary an error.
7092
                // If an older PROJ version parses a DatumEnsemble object of
7093
                // a more recent PROJ version where the datum ensemble got
7094
                // a new member, it might be unknown from the older PROJ.
7095
0
            }
7096
0
        }
7097
7098
0
        if (dbContext_ && !datumAdded) {
7099
0
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
7100
0
                                                        std::string());
7101
0
            auto list = authFactory->createObjectsFromName(
7102
0
                datumName, {AuthorityFactory::ObjectType::DATUM},
7103
0
                false /* approximate=false*/);
7104
0
            if (!list.empty()) {
7105
0
                auto datum = util::nn_dynamic_pointer_cast<Datum>(list.front());
7106
0
                if (!datum)
7107
0
                    throw ParsingException(
7108
0
                        "DatumEnsemble member is not a datum");
7109
0
                datums.push_back(NN_NO_CHECK(datum));
7110
0
                datumAdded = true;
7111
0
            }
7112
0
        }
7113
7114
0
        if (!datumAdded) {
7115
            // Fallback if no db match
7116
0
            if (hasEllipsoid) {
7117
0
                datums.emplace_back(GeodeticReferenceFrame::create(
7118
0
                    buildProperties(memberJ),
7119
0
                    buildEllipsoid(getObject(j, "ellipsoid")),
7120
0
                    optional<std::string>(), PrimeMeridian::GREENWICH));
7121
0
            } else {
7122
0
                datums.emplace_back(
7123
0
                    VerticalReferenceFrame::create(buildProperties(memberJ)));
7124
0
            }
7125
0
        }
7126
0
    }
7127
0
    return DatumEnsemble::create(
7128
0
        buildProperties(j), datums,
7129
0
        PositionalAccuracy::create(getString(j, "accuracy")));
7130
0
}
7131
7132
// ---------------------------------------------------------------------------
7133
7134
GeodeticReferenceFrameNNPtr
7135
0
JSONParser::buildGeodeticReferenceFrame(const json &j) {
7136
0
    auto ellipsoidJ = getObject(j, "ellipsoid");
7137
0
    auto pm = j.contains("prime_meridian")
7138
0
                  ? buildPrimeMeridian(getObject(j, "prime_meridian"))
7139
0
                  : PrimeMeridian::GREENWICH;
7140
0
    return GeodeticReferenceFrame::create(buildProperties(j),
7141
0
                                          buildEllipsoid(ellipsoidJ),
7142
0
                                          getAnchor(j), getAnchorEpoch(j), pm);
7143
0
}
7144
7145
// ---------------------------------------------------------------------------
7146
7147
DynamicGeodeticReferenceFrameNNPtr
7148
0
JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) {
7149
0
    auto ellipsoidJ = getObject(j, "ellipsoid");
7150
0
    auto pm = j.contains("prime_meridian")
7151
0
                  ? buildPrimeMeridian(getObject(j, "prime_meridian"))
7152
0
                  : PrimeMeridian::GREENWICH;
7153
0
    Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
7154
0
                                UnitOfMeasure::YEAR);
7155
0
    optional<std::string> deformationModel;
7156
0
    if (j.contains("deformation_model")) {
7157
        // Before PROJJSON v0.5 / PROJ 9.1
7158
0
        deformationModel = getString(j, "deformation_model");
7159
0
    } else if (!deformationModelName_.empty()) {
7160
0
        deformationModel = deformationModelName_;
7161
0
    }
7162
0
    return DynamicGeodeticReferenceFrame::create(
7163
0
        buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm,
7164
0
        frameReferenceEpoch, deformationModel);
7165
0
}
7166
7167
// ---------------------------------------------------------------------------
7168
7169
VerticalReferenceFrameNNPtr
7170
0
JSONParser::buildVerticalReferenceFrame(const json &j) {
7171
0
    return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j),
7172
0
                                          getAnchorEpoch(j));
7173
0
}
7174
7175
// ---------------------------------------------------------------------------
7176
7177
DynamicVerticalReferenceFrameNNPtr
7178
0
JSONParser::buildDynamicVerticalReferenceFrame(const json &j) {
7179
0
    Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
7180
0
                                UnitOfMeasure::YEAR);
7181
0
    optional<std::string> deformationModel;
7182
0
    if (j.contains("deformation_model")) {
7183
        // Before PROJJSON v0.5 / PROJ 9.1
7184
0
        deformationModel = getString(j, "deformation_model");
7185
0
    } else if (!deformationModelName_.empty()) {
7186
0
        deformationModel = deformationModelName_;
7187
0
    }
7188
0
    return DynamicVerticalReferenceFrame::create(
7189
0
        buildProperties(j), getAnchor(j), util::optional<RealizationMethod>(),
7190
0
        frameReferenceEpoch, deformationModel);
7191
0
}
7192
7193
// ---------------------------------------------------------------------------
7194
7195
0
PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) {
7196
0
    if (!j.contains("longitude")) {
7197
0
        throw ParsingException("Missing \"longitude\" key");
7198
0
    }
7199
0
    auto longitude = j["longitude"];
7200
0
    if (longitude.is_number()) {
7201
0
        return PrimeMeridian::create(
7202
0
            buildProperties(j),
7203
0
            Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
7204
0
    } else if (longitude.is_object()) {
7205
0
        return PrimeMeridian::create(buildProperties(j),
7206
0
                                     Angle(getMeasure(longitude)));
7207
0
    }
7208
0
    throw ParsingException("Unexpected type for value of \"longitude\"");
7209
0
}
7210
7211
// ---------------------------------------------------------------------------
7212
7213
0
EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) {
7214
0
    if (j.contains("semi_major_axis")) {
7215
0
        auto semiMajorAxis = getLength(j, "semi_major_axis");
7216
0
        const auto ellpsProperties = buildProperties(j);
7217
0
        std::string ellpsName;
7218
0
        ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName);
7219
0
        const auto celestialBody(Ellipsoid::guessBodyName(
7220
0
            dbContext_, semiMajorAxis.getSIValue(), ellpsName));
7221
0
        if (j.contains("semi_minor_axis")) {
7222
0
            return Ellipsoid::createTwoAxis(ellpsProperties, semiMajorAxis,
7223
0
                                            getLength(j, "semi_minor_axis"),
7224
0
                                            celestialBody);
7225
0
        } else if (j.contains("inverse_flattening")) {
7226
0
            return Ellipsoid::createFlattenedSphere(
7227
0
                ellpsProperties, semiMajorAxis,
7228
0
                Scale(getNumber(j, "inverse_flattening")), celestialBody);
7229
0
        } else {
7230
0
            throw ParsingException(
7231
0
                "Missing semi_minor_axis or inverse_flattening");
7232
0
        }
7233
0
    } else if (j.contains("radius")) {
7234
0
        auto radius = getLength(j, "radius");
7235
0
        const auto celestialBody(
7236
0
            Ellipsoid::guessBodyName(dbContext_, radius.getSIValue()));
7237
0
        return Ellipsoid::createSphere(buildProperties(j), radius,
7238
0
                                       celestialBody);
7239
0
    }
7240
0
    throw ParsingException("Missing semi_major_axis or radius");
7241
0
}
7242
7243
//! @endcond
7244
7245
// ---------------------------------------------------------------------------
7246
7247
//! @cond Doxygen_Suppress
7248
7249
// import a CRS encoded as OGC Best Practice document 11-135.
7250
7251
static const char *const crsURLPrefixes[] = {
7252
    "http://opengis.net/def/crs",     "https://opengis.net/def/crs",
7253
    "http://www.opengis.net/def/crs", "https://www.opengis.net/def/crs",
7254
    "www.opengis.net/def/crs",
7255
};
7256
7257
3.16k
static bool isCRSURL(const std::string &text) {
7258
15.6k
    for (const auto crsURLPrefix : crsURLPrefixes) {
7259
15.6k
        if (starts_with(text, crsURLPrefix)) {
7260
85
            return true;
7261
85
        }
7262
15.6k
    }
7263
3.07k
    return false;
7264
3.16k
}
7265
7266
static CRSNNPtr importFromCRSURL(const std::string &text,
7267
107
                                 const DatabaseContextNNPtr &dbContext) {
7268
    // e.g http://www.opengis.net/def/crs/EPSG/0/4326
7269
107
    std::vector<std::string> parts;
7270
383
    for (const auto crsURLPrefix : crsURLPrefixes) {
7271
383
        if (starts_with(text, crsURLPrefix)) {
7272
82
            parts = split(text.substr(strlen(crsURLPrefix)), '/');
7273
82
            break;
7274
82
        }
7275
383
    }
7276
7277
    // e.g
7278
    // "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"
7279
107
    if (!parts.empty() && starts_with(parts[0], "-compound?")) {
7280
58
        parts = split(text.substr(text.find('?') + 1), '&');
7281
58
        std::map<int, std::string> mapParts;
7282
151
        for (const auto &part : parts) {
7283
151
            const auto queryParam = split(part, '=');
7284
151
            if (queryParam.size() != 2) {
7285
17
                throw ParsingException("invalid OGC CRS URL");
7286
17
            }
7287
134
            try {
7288
134
                mapParts[std::stoi(queryParam[0])] = queryParam[1];
7289
134
            } catch (const std::exception &) {
7290
6
                throw ParsingException("invalid OGC CRS URL");
7291
6
            }
7292
134
        }
7293
35
        std::vector<CRSNNPtr> components;
7294
35
        std::string name;
7295
60
        for (size_t i = 1; i <= mapParts.size(); ++i) {
7296
35
            const auto iter = mapParts.find(static_cast<int>(i));
7297
35
            if (iter == mapParts.end()) {
7298
10
                throw ParsingException("invalid OGC CRS URL");
7299
10
            }
7300
25
            components.emplace_back(importFromCRSURL(iter->second, dbContext));
7301
25
            if (!name.empty()) {
7302
0
                name += " + ";
7303
0
            }
7304
25
            name += components.back()->nameStr();
7305
25
        }
7306
25
        return CompoundCRS::create(
7307
25
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
7308
25
            components);
7309
35
    }
7310
7311
49
    if (parts.size() < 4) {
7312
41
        throw ParsingException("invalid OGC CRS URL");
7313
41
    }
7314
7315
8
    const auto &auth_name = parts[1];
7316
8
    const auto &code = parts[3];
7317
8
    try {
7318
8
        auto factoryCRS = AuthorityFactory::create(dbContext, auth_name);
7319
8
        return factoryCRS->createCoordinateReferenceSystem(code, true);
7320
8
    } catch (...) {
7321
8
        const auto &version = parts[2];
7322
8
        if (version.empty() || version == "0") {
7323
3
            const auto authoritiesFromAuthName =
7324
3
                dbContext->getVersionedAuthoritiesFromName(auth_name);
7325
3
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7326
0
                try {
7327
0
                    auto factoryCRS =
7328
0
                        AuthorityFactory::create(dbContext, authNameVersioned);
7329
0
                    return factoryCRS->createCoordinateReferenceSystem(code,
7330
0
                                                                       true);
7331
0
                } catch (...) {
7332
0
                }
7333
0
            }
7334
3
            throw;
7335
3
        }
7336
5
        std::string authNameWithVersion;
7337
5
        if (!dbContext->getVersionedAuthority(auth_name, version,
7338
5
                                              authNameWithVersion)) {
7339
5
            throw;
7340
5
        }
7341
0
        auto factoryCRS =
7342
0
            AuthorityFactory::create(dbContext, authNameWithVersion);
7343
0
        return factoryCRS->createCoordinateReferenceSystem(code, true);
7344
5
    }
7345
8
}
7346
7347
// ---------------------------------------------------------------------------
7348
7349
/* Import a CRS encoded as WMSAUTO string.
7350
 *
7351
 * Note that the WMS 1.3 specification does not include the
7352
 * units code, while apparently earlier specs do.  We try to
7353
 * guess around this.
7354
 *
7355
 * (code derived from GDAL's importFromWMSAUTO())
7356
 */
7357
7358
42
static CRSNNPtr importFromWMSAUTO(const std::string &text) {
7359
7360
42
    int nUnitsId = 9001;
7361
42
    double dfRefLong;
7362
42
    double dfRefLat = 0.0;
7363
7364
42
    assert(ci_starts_with(text, "AUTO:"));
7365
42
    const auto parts = split(text.substr(strlen("AUTO:")), ',');
7366
7367
42
    try {
7368
42
        constexpr int AUTO_MOLLWEIDE = 42005;
7369
42
        if (parts.size() == 4) {
7370
6
            nUnitsId = std::stoi(parts[1]);
7371
6
            dfRefLong = c_locale_stod(parts[2]);
7372
6
            dfRefLat = c_locale_stod(parts[3]);
7373
36
        } else if (parts.size() == 3 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
7374
0
            nUnitsId = std::stoi(parts[1]);
7375
0
            dfRefLong = c_locale_stod(parts[2]);
7376
36
        } else if (parts.size() == 3) {
7377
9
            dfRefLong = c_locale_stod(parts[1]);
7378
9
            dfRefLat = c_locale_stod(parts[2]);
7379
27
        } else if (parts.size() == 2 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
7380
0
            dfRefLong = c_locale_stod(parts[1]);
7381
27
        } else {
7382
27
            throw ParsingException("invalid WMS AUTO CRS definition");
7383
27
        }
7384
7385
15
        const auto getConversion = [dfRefLong, dfRefLat, &parts]() {
7386
0
            const int nProjId = std::stoi(parts[0]);
7387
0
            switch (nProjId) {
7388
0
            case 42001: // Auto UTM
7389
0
                if (!(dfRefLong >= -180 && dfRefLong < 180)) {
7390
0
                    throw ParsingException("invalid WMS AUTO CRS definition: "
7391
0
                                           "invalid longitude");
7392
0
                }
7393
0
                return Conversion::createUTM(
7394
0
                    util::PropertyMap(),
7395
0
                    static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
7396
0
                    dfRefLat >= 0.0);
7397
7398
0
            case 42002: // Auto TM (strangely very UTM-like).
7399
0
                return Conversion::createTransverseMercator(
7400
0
                    util::PropertyMap(), common::Angle(0),
7401
0
                    common::Angle(dfRefLong), common::Scale(0.9996),
7402
0
                    common::Length(500000),
7403
0
                    common::Length((dfRefLat >= 0.0) ? 0.0 : 10000000.0));
7404
7405
0
            case 42003: // Auto Orthographic.
7406
0
                return Conversion::createOrthographic(
7407
0
                    util::PropertyMap(), common::Angle(dfRefLat),
7408
0
                    common::Angle(dfRefLong), common::Length(0),
7409
0
                    common::Length(0));
7410
7411
0
            case 42004: // Auto Equirectangular
7412
0
                return Conversion::createEquidistantCylindrical(
7413
0
                    util::PropertyMap(), common::Angle(dfRefLat),
7414
0
                    common::Angle(dfRefLong), common::Length(0),
7415
0
                    common::Length(0));
7416
7417
0
            case 42005: // MSVC 2015 thinks that AUTO_MOLLWEIDE is not constant
7418
0
                return Conversion::createMollweide(
7419
0
                    util::PropertyMap(), common::Angle(dfRefLong),
7420
0
                    common::Length(0), common::Length(0));
7421
7422
0
            default:
7423
0
                throw ParsingException("invalid WMS AUTO CRS definition: "
7424
0
                                       "unsupported projection id");
7425
0
            }
7426
0
        };
7427
7428
15
        const auto getUnits = [nUnitsId]() -> const UnitOfMeasure & {
7429
0
            switch (nUnitsId) {
7430
0
            case 9001:
7431
0
                return UnitOfMeasure::METRE;
7432
7433
0
            case 9002:
7434
0
                return UnitOfMeasure::FOOT;
7435
7436
0
            case 9003:
7437
0
                return UnitOfMeasure::US_FOOT;
7438
7439
0
            default:
7440
0
                throw ParsingException("invalid WMS AUTO CRS definition: "
7441
0
                                       "unsupported units code");
7442
0
            }
7443
0
        };
7444
7445
15
        return crs::ProjectedCRS::create(
7446
15
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
7447
15
            crs::GeographicCRS::EPSG_4326, getConversion(),
7448
15
            cs::CartesianCS::createEastingNorthing(getUnits()));
7449
7450
42
    } catch (const std::exception &) {
7451
42
        throw ParsingException("invalid WMS AUTO CRS definition");
7452
42
    }
7453
42
}
7454
7455
// ---------------------------------------------------------------------------
7456
7457
static BaseObjectNNPtr createFromURNPart(const DatabaseContextPtr &dbContext,
7458
                                         const std::string &type,
7459
                                         const std::string &authName,
7460
                                         const std::string &version,
7461
129
                                         const std::string &code) {
7462
129
    if (!dbContext) {
7463
2
        throw ParsingException("no database context specified");
7464
2
    }
7465
127
    try {
7466
127
        auto factory =
7467
127
            AuthorityFactory::create(NN_NO_CHECK(dbContext), authName);
7468
127
        if (type == "crs") {
7469
91
            return factory->createCoordinateReferenceSystem(code);
7470
91
        }
7471
36
        if (type == "coordinateOperation") {
7472
0
            return factory->createCoordinateOperation(code, true);
7473
0
        }
7474
36
        if (type == "datum") {
7475
7
            return factory->createDatum(code);
7476
7
        }
7477
29
        if (type == "ensemble") {
7478
0
            return factory->createDatumEnsemble(code);
7479
0
        }
7480
29
        if (type == "ellipsoid") {
7481
0
            return factory->createEllipsoid(code);
7482
0
        }
7483
29
        if (type == "meridian") {
7484
0
            return factory->createPrimeMeridian(code);
7485
0
        }
7486
        // Extension of OGC URN syntax to CoordinateMetadata
7487
29
        if (type == "coordinateMetadata") {
7488
0
            return factory->createCoordinateMetadata(code);
7489
0
        }
7490
29
        throw ParsingException(concat("unhandled object type: ", type));
7491
50
    } catch (...) {
7492
50
        if (version.empty()) {
7493
9
            const auto authoritiesFromAuthName =
7494
9
                dbContext->getVersionedAuthoritiesFromName(authName);
7495
9
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7496
0
                try {
7497
0
                    return createFromURNPart(dbContext, type, authNameVersioned,
7498
0
                                             std::string(), code);
7499
0
                } catch (...) {
7500
0
                }
7501
0
            }
7502
9
            throw;
7503
9
        }
7504
41
        std::string authNameWithVersion;
7505
41
        if (!dbContext->getVersionedAuthority(authName, version,
7506
41
                                              authNameWithVersion)) {
7507
41
            throw;
7508
41
        }
7509
0
        return createFromURNPart(dbContext, type, authNameWithVersion,
7510
0
                                 std::string(), code);
7511
41
    }
7512
127
}
7513
7514
// ---------------------------------------------------------------------------
7515
7516
static BaseObjectNNPtr createFromUserInput(const std::string &text,
7517
                                           const DatabaseContextPtr &dbContext,
7518
                                           bool usePROJ4InitRules,
7519
                                           PJ_CONTEXT *ctx,
7520
67.9k
                                           bool ignoreCoordinateEpoch) {
7521
67.9k
    std::size_t idxFirstCharNotSpace = text.find_first_not_of(" \t\r\n");
7522
67.9k
    if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) {
7523
0
        return createFromUserInput(text.substr(idxFirstCharNotSpace), dbContext,
7524
0
                                   usePROJ4InitRules, ctx,
7525
0
                                   ignoreCoordinateEpoch);
7526
0
    }
7527
7528
    // Parse strings like "ITRF2014 @ 2025.0"
7529
67.9k
    const auto posAt = text.find('@');
7530
67.9k
    if (!ignoreCoordinateEpoch && posAt != std::string::npos) {
7531
7532
        // Try first as if belonged to the name
7533
8.54k
        try {
7534
8.54k
            return createFromUserInput(text, dbContext, usePROJ4InitRules, ctx,
7535
8.54k
                                       /* ignoreCoordinateEpoch = */ true);
7536
8.54k
        } catch (...) {
7537
3.75k
        }
7538
7539
3.75k
        std::string leftPart = text.substr(0, posAt);
7540
4.18k
        while (!leftPart.empty() && leftPart.back() == ' ')
7541
434
            leftPart.resize(leftPart.size() - 1);
7542
3.75k
        const auto nonSpacePos = text.find_first_not_of(' ', posAt + 1);
7543
3.75k
        if (nonSpacePos != std::string::npos) {
7544
3.41k
            auto obj =
7545
3.41k
                createFromUserInput(leftPart, dbContext, usePROJ4InitRules, ctx,
7546
3.41k
                                    /* ignoreCoordinateEpoch = */ true);
7547
3.41k
            auto crs = nn_dynamic_pointer_cast<CRS>(obj);
7548
3.41k
            if (crs) {
7549
191
                double epoch;
7550
191
                try {
7551
191
                    epoch = c_locale_stod(text.substr(nonSpacePos));
7552
191
                } catch (const std::exception &) {
7553
159
                    throw ParsingException("non-numeric value after @");
7554
159
                }
7555
32
                try {
7556
32
                    return CoordinateMetadata::create(NN_NO_CHECK(crs), epoch,
7557
32
                                                      dbContext);
7558
32
                } catch (const std::exception &e) {
7559
18
                    throw ParsingException(
7560
18
                        std::string(
7561
18
                            "CoordinateMetadata::create() failed with: ") +
7562
18
                        e.what());
7563
18
                }
7564
32
            }
7565
3.41k
        }
7566
3.75k
    }
7567
7568
62.9k
    if (!text.empty() && text[0] == '{') {
7569
1.65k
        json j;
7570
1.65k
        try {
7571
1.65k
            j = json::parse(text);
7572
1.65k
        } catch (const std::exception &e) {
7573
1.61k
            throw ParsingException(e.what());
7574
1.61k
        }
7575
36
        return JSONParser().attachDatabaseContext(dbContext).create(j);
7576
1.65k
    }
7577
7578
61.2k
    if (!ci_starts_with(text, "step proj=") &&
7579
61.2k
        !ci_starts_with(text, "step +proj=")) {
7580
5.42M
        for (const auto &wktConstant : WKTConstants::constants()) {
7581
5.42M
            if (ci_starts_with(text, wktConstant)) {
7582
11.9k
                for (auto wkt = text.c_str() + wktConstant.size(); *wkt != '\0';
7583
11.9k
                     ++wkt) {
7584
11.9k
                    if (isspace(static_cast<unsigned char>(*wkt)))
7585
1.79k
                        continue;
7586
10.1k
                    if (*wkt == '[') {
7587
9.93k
                        return WKTParser()
7588
9.93k
                            .attachDatabaseContext(dbContext)
7589
9.93k
                            .setStrict(false)
7590
9.93k
                            .createFromWKT(text);
7591
9.93k
                    }
7592
212
                    break;
7593
10.1k
                }
7594
10.1k
            }
7595
5.42M
        }
7596
58.4k
    }
7597
7598
51.3k
    const char *textWithoutPlusPrefix = text.c_str();
7599
51.3k
    if (textWithoutPlusPrefix[0] == '+')
7600
1.36k
        textWithoutPlusPrefix++;
7601
7602
51.3k
    if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 ||
7603
51.3k
        text.find(" +proj=") != std::string::npos ||
7604
51.3k
        text.find(" proj=") != std::string::npos ||
7605
51.3k
        strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 ||
7606
51.3k
        text.find(" +init=") != std::string::npos ||
7607
51.3k
        text.find(" init=") != std::string::npos ||
7608
51.3k
        strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) {
7609
45.4k
        return PROJStringParser()
7610
45.4k
            .attachDatabaseContext(dbContext)
7611
45.4k
            .attachContext(ctx)
7612
45.4k
            .setUsePROJ4InitRules(ctx != nullptr
7613
45.4k
                                      ? (proj_context_get_use_proj4_init_rules(
7614
34.5k
                                             ctx, false) == TRUE)
7615
45.4k
                                      : usePROJ4InitRules)
7616
45.4k
            .createFromPROJString(text);
7617
45.4k
    }
7618
7619
5.88k
    if (isCRSURL(text) && dbContext) {
7620
82
        return importFromCRSURL(text, NN_NO_CHECK(dbContext));
7621
82
    }
7622
7623
5.80k
    if (ci_starts_with(text, "AUTO:")) {
7624
24
        return importFromWMSAUTO(text);
7625
24
    }
7626
7627
5.78k
    auto tokens = split(text, ':');
7628
5.78k
    if (tokens.size() == 2) {
7629
1.44k
        if (!dbContext) {
7630
11
            throw ParsingException("no database context specified");
7631
11
        }
7632
1.43k
        DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
7633
1.43k
        const auto &authName = tokens[0];
7634
1.43k
        const auto &code = tokens[1];
7635
1.43k
        auto factory = AuthorityFactory::create(dbContextNNPtr, authName);
7636
1.43k
        try {
7637
1.43k
            return factory->createCoordinateReferenceSystem(code);
7638
1.43k
        } catch (...) {
7639
7640
            // Convenience for well-known misused code
7641
            // See https://github.com/OSGeo/PROJ/issues/1730
7642
762
            if (ci_equal(authName, "EPSG") && code == "102100") {
7643
15
                factory = AuthorityFactory::create(dbContextNNPtr, "ESRI");
7644
15
                return factory->createCoordinateReferenceSystem(code);
7645
15
            }
7646
7647
747
            const auto authoritiesFromAuthName =
7648
747
                dbContextNNPtr->getVersionedAuthoritiesFromName(authName);
7649
747
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7650
0
                factory =
7651
0
                    AuthorityFactory::create(dbContextNNPtr, authNameVersioned);
7652
0
                try {
7653
0
                    return factory->createCoordinateReferenceSystem(code);
7654
0
                } catch (...) {
7655
0
                }
7656
0
            }
7657
7658
747
            const auto allAuthorities = dbContextNNPtr->getAuthorities();
7659
2.81k
            for (const auto &authCandidate : allAuthorities) {
7660
2.81k
                if (ci_equal(authCandidate, authName)) {
7661
666
                    factory =
7662
666
                        AuthorityFactory::create(dbContextNNPtr, authCandidate);
7663
666
                    try {
7664
666
                        return factory->createCoordinateReferenceSystem(code);
7665
666
                    } catch (...) {
7666
                        // EPSG:4326+3855
7667
665
                        auto tokensCode = split(code, '+');
7668
665
                        if (tokensCode.size() == 2) {
7669
92
                            auto crs1(factory->createCoordinateReferenceSystem(
7670
92
                                tokensCode[0], false));
7671
92
                            auto crs2(factory->createCoordinateReferenceSystem(
7672
92
                                tokensCode[1], false));
7673
92
                            return CompoundCRS::createLax(
7674
92
                                util::PropertyMap().set(
7675
92
                                    IdentifiedObject::NAME_KEY,
7676
92
                                    crs1->nameStr() + " + " + crs2->nameStr()),
7677
92
                                {crs1, crs2}, dbContext);
7678
92
                        }
7679
573
                        throw;
7680
665
                    }
7681
666
                }
7682
2.81k
            }
7683
81
            throw;
7684
747
        }
7685
4.33k
    } else if (tokens.size() == 3) {
7686
        // ESRI:103668+EPSG:5703 ... compound
7687
148
        auto tokensCenter = split(tokens[1], '+');
7688
148
        if (tokensCenter.size() == 2) {
7689
34
            if (!dbContext) {
7690
0
                throw ParsingException("no database context specified");
7691
0
            }
7692
34
            DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
7693
7694
34
            const auto &authName1 = tokens[0];
7695
34
            const auto &code1 = tokensCenter[0];
7696
34
            const auto &authName2 = tokensCenter[1];
7697
34
            const auto &code2 = tokens[2];
7698
7699
34
            auto factory1 = AuthorityFactory::create(dbContextNNPtr, authName1);
7700
34
            auto crs1 = factory1->createCoordinateReferenceSystem(code1, false);
7701
34
            auto factory2 = AuthorityFactory::create(dbContextNNPtr, authName2);
7702
34
            auto crs2 = factory2->createCoordinateReferenceSystem(code2, false);
7703
34
            return CompoundCRS::createLax(
7704
34
                util::PropertyMap().set(IdentifiedObject::NAME_KEY,
7705
34
                                        crs1->nameStr() + " + " +
7706
34
                                            crs2->nameStr()),
7707
34
                {crs1, crs2}, dbContext);
7708
34
        }
7709
148
    }
7710
7711
4.29k
    if (starts_with(text, "urn:ogc:def:crs,")) {
7712
9
        if (!dbContext) {
7713
3
            throw ParsingException("no database context specified");
7714
3
        }
7715
6
        auto tokensComma = split(text, ',');
7716
6
        if (tokensComma.size() == 4 && starts_with(tokensComma[1], "crs:") &&
7717
6
            starts_with(tokensComma[2], "cs:") &&
7718
6
            starts_with(tokensComma[3], "coordinateOperation:")) {
7719
            // OGC 07-092r2: para 7.5.4
7720
            // URN combined references for projected or derived CRSs
7721
0
            const auto &crsPart = tokensComma[1];
7722
0
            const auto tokensCRS = split(crsPart, ':');
7723
0
            if (tokensCRS.size() != 4) {
7724
0
                throw ParsingException(
7725
0
                    concat("invalid crs component: ", crsPart));
7726
0
            }
7727
0
            auto factoryCRS =
7728
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCRS[1]);
7729
0
            auto baseCRS =
7730
0
                factoryCRS->createCoordinateReferenceSystem(tokensCRS[3], true);
7731
7732
0
            const auto &csPart = tokensComma[2];
7733
0
            auto tokensCS = split(csPart, ':');
7734
0
            if (tokensCS.size() != 4) {
7735
0
                throw ParsingException(
7736
0
                    concat("invalid cs component: ", csPart));
7737
0
            }
7738
0
            auto factoryCS =
7739
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCS[1]);
7740
0
            auto cs = factoryCS->createCoordinateSystem(tokensCS[3]);
7741
7742
0
            const auto &opPart = tokensComma[3];
7743
0
            auto tokensOp = split(opPart, ':');
7744
0
            if (tokensOp.size() != 4) {
7745
0
                throw ParsingException(
7746
0
                    concat("invalid coordinateOperation component: ", opPart));
7747
0
            }
7748
0
            auto factoryOp =
7749
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensOp[1]);
7750
0
            auto op = factoryOp->createCoordinateOperation(tokensOp[3], true);
7751
7752
0
            const auto &baseName = baseCRS->nameStr();
7753
0
            std::string name(baseName);
7754
0
            auto geogCRS =
7755
0
                util::nn_dynamic_pointer_cast<GeographicCRS>(baseCRS);
7756
0
            if (geogCRS &&
7757
0
                geogCRS->coordinateSystem()->axisList().size() == 3 &&
7758
0
                baseName.find("3D") == std::string::npos) {
7759
0
                name += " (3D)";
7760
0
            }
7761
0
            name += " / ";
7762
0
            name += op->nameStr();
7763
0
            auto props =
7764
0
                util::PropertyMap().set(IdentifiedObject::NAME_KEY, name);
7765
7766
0
            if (auto conv = util::nn_dynamic_pointer_cast<Conversion>(op)) {
7767
0
                auto convNN = NN_NO_CHECK(conv);
7768
0
                if (geogCRS != nullptr) {
7769
0
                    auto geogCRSNN = NN_NO_CHECK(geogCRS);
7770
0
                    if (CartesianCSPtr ccs =
7771
0
                            util::nn_dynamic_pointer_cast<CartesianCS>(cs)) {
7772
0
                        return ProjectedCRS::create(props, geogCRSNN, convNN,
7773
0
                                                    NN_NO_CHECK(ccs));
7774
0
                    }
7775
0
                    if (EllipsoidalCSPtr ecs =
7776
0
                            util::nn_dynamic_pointer_cast<EllipsoidalCS>(cs)) {
7777
0
                        return DerivedGeographicCRS::create(
7778
0
                            props, geogCRSNN, convNN, NN_NO_CHECK(ecs));
7779
0
                    }
7780
0
                } else if (dynamic_cast<GeodeticCRS *>(baseCRS.get()) &&
7781
0
                           dynamic_cast<CartesianCS *>(cs.get())) {
7782
0
                    return DerivedGeodeticCRS::create(
7783
0
                        props,
7784
0
                        NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
7785
0
                            baseCRS)),
7786
0
                        convNN,
7787
0
                        NN_NO_CHECK(
7788
0
                            util::nn_dynamic_pointer_cast<CartesianCS>(cs)));
7789
0
                } else if (auto pcrs =
7790
0
                               util::nn_dynamic_pointer_cast<ProjectedCRS>(
7791
0
                                   baseCRS)) {
7792
0
                    return DerivedProjectedCRS::create(props, NN_NO_CHECK(pcrs),
7793
0
                                                       convNN, cs);
7794
0
                } else if (auto vertBaseCRS =
7795
0
                               util::nn_dynamic_pointer_cast<VerticalCRS>(
7796
0
                                   baseCRS)) {
7797
0
                    if (auto vertCS =
7798
0
                            util::nn_dynamic_pointer_cast<VerticalCS>(cs)) {
7799
0
                        const int methodCode = convNN->method()->getEPSGCode();
7800
0
                        std::string newName(baseName);
7801
0
                        std::string unitNameSuffix;
7802
0
                        for (const char *suffix : {" (ft)", " (ftUS)"}) {
7803
0
                            if (ends_with(newName, suffix)) {
7804
0
                                unitNameSuffix = suffix;
7805
0
                                newName.resize(newName.size() - strlen(suffix));
7806
0
                                break;
7807
0
                            }
7808
0
                        }
7809
0
                        bool newNameOk = false;
7810
0
                        if (methodCode ==
7811
0
                                EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
7812
0
                            methodCode ==
7813
0
                                EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
7814
0
                            const auto &unitName =
7815
0
                                vertCS->axisList()[0]->unit().name();
7816
0
                            if (unitName == UnitOfMeasure::METRE.name()) {
7817
0
                                newNameOk = true;
7818
0
                            } else if (unitName == UnitOfMeasure::FOOT.name()) {
7819
0
                                newName += " (ft)";
7820
0
                                newNameOk = true;
7821
0
                            } else if (unitName ==
7822
0
                                       UnitOfMeasure::US_FOOT.name()) {
7823
0
                                newName += " (ftUS)";
7824
0
                                newNameOk = true;
7825
0
                            }
7826
0
                        } else if (methodCode ==
7827
0
                                   EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
7828
0
                            if (ends_with(newName, " height")) {
7829
0
                                newName.resize(newName.size() -
7830
0
                                               strlen(" height"));
7831
0
                                newName += " depth";
7832
0
                                newName += unitNameSuffix;
7833
0
                                newNameOk = true;
7834
0
                            } else if (ends_with(newName, " depth")) {
7835
0
                                newName.resize(newName.size() -
7836
0
                                               strlen(" depth"));
7837
0
                                newName += " height";
7838
0
                                newName += unitNameSuffix;
7839
0
                                newNameOk = true;
7840
0
                            }
7841
0
                        }
7842
0
                        if (newNameOk) {
7843
0
                            props.set(IdentifiedObject::NAME_KEY, newName);
7844
0
                        }
7845
0
                        return DerivedVerticalCRS::create(
7846
0
                            props, NN_NO_CHECK(vertBaseCRS), convNN,
7847
0
                            NN_NO_CHECK(vertCS));
7848
0
                    }
7849
0
                }
7850
0
            }
7851
7852
0
            throw ParsingException("unsupported combination of baseCRS, CS "
7853
0
                                   "and coordinateOperation for a "
7854
0
                                   "DerivedCRS");
7855
0
        }
7856
7857
        // OGC 07-092r2: para 7.5.2
7858
        // URN combined references for compound coordinate reference systems
7859
6
        std::vector<CRSNNPtr> components;
7860
6
        std::string name;
7861
6
        for (size_t i = 1; i < tokensComma.size(); i++) {
7862
6
            tokens = split(tokensComma[i], ':');
7863
6
            if (tokens.size() != 4) {
7864
4
                throw ParsingException(
7865
4
                    concat("invalid crs component: ", tokensComma[i]));
7866
4
            }
7867
2
            const auto &type = tokens[0];
7868
2
            auto factory =
7869
2
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
7870
2
            const auto &code = tokens[3];
7871
2
            if (type == "crs") {
7872
0
                auto crs(factory->createCoordinateReferenceSystem(code, false));
7873
0
                components.emplace_back(crs);
7874
0
                if (!name.empty()) {
7875
0
                    name += " + ";
7876
0
                }
7877
0
                name += crs->nameStr();
7878
2
            } else {
7879
2
                throw ParsingException(
7880
2
                    concat("unexpected object type: ", type));
7881
2
            }
7882
2
        }
7883
0
        return CompoundCRS::create(
7884
0
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
7885
0
            components);
7886
6
    }
7887
7888
    // OGC 07-092r2: para 7.5.3
7889
    // 7.5.3 URN combined references for concatenated operations
7890
4.29k
    if (starts_with(text, "urn:ogc:def:coordinateOperation,")) {
7891
5
        if (!dbContext) {
7892
2
            throw ParsingException("no database context specified");
7893
2
        }
7894
3
        auto tokensComma = split(text, ',');
7895
3
        std::vector<CoordinateOperationNNPtr> components;
7896
3
        for (size_t i = 1; i < tokensComma.size(); i++) {
7897
3
            tokens = split(tokensComma[i], ':');
7898
3
            if (tokens.size() != 4) {
7899
3
                throw ParsingException(concat(
7900
3
                    "invalid coordinateOperation component: ", tokensComma[i]));
7901
3
            }
7902
0
            const auto &type = tokens[0];
7903
0
            auto factory =
7904
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
7905
0
            const auto &code = tokens[3];
7906
0
            if (type == "coordinateOperation") {
7907
0
                auto op(factory->createCoordinateOperation(code, false));
7908
0
                components.emplace_back(op);
7909
0
            } else {
7910
0
                throw ParsingException(
7911
0
                    concat("unexpected object type: ", type));
7912
0
            }
7913
0
        }
7914
0
        return ConcatenatedOperation::createComputeMetadata(components, true);
7915
3
    }
7916
7917
    // urn:ogc:def:crs:EPSG::4326
7918
4.28k
    if (tokens.size() == 7 && tolower(tokens[0]) == "urn") {
7919
7920
17
        const std::string type(tokens[3] == "CRS" ? "crs" : tokens[3]);
7921
17
        const auto &authName = tokens[4];
7922
17
        const auto &version = tokens[5];
7923
17
        const auto &code = tokens[6];
7924
17
        return createFromURNPart(dbContext, type, authName, version, code);
7925
17
    }
7926
7927
    // urn:ogc:def:crs:OGC::AUTO42001:-117:33
7928
4.26k
    if (tokens.size() > 7 && tokens[0] == "urn" && tokens[4] == "OGC" &&
7929
4.26k
        ci_starts_with(tokens[6], "AUTO")) {
7930
18
        const auto textAUTO = text.substr(text.find(":AUTO") + 5);
7931
18
        return importFromWMSAUTO("AUTO:" + replaceAll(textAUTO, ":", ","));
7932
18
    }
7933
7934
    // Legacy urn:opengis:crs:EPSG:0:4326 (note the missing def: compared to
7935
    // above)
7936
4.25k
    if (tokens.size() == 6 && tokens[0] == "urn" && tokens[2] != "def") {
7937
32
        const auto &type = tokens[2];
7938
32
        const auto &authName = tokens[3];
7939
32
        const auto &version = tokens[4];
7940
32
        const auto &code = tokens[5];
7941
32
        return createFromURNPart(dbContext, type, authName, version, code);
7942
32
    }
7943
7944
    // Legacy urn:x-ogc:def:crs:EPSG:4326 (note the missing version)
7945
4.21k
    if (tokens.size() == 6 && tokens[0] == "urn") {
7946
80
        const auto &type = tokens[3];
7947
80
        const auto &authName = tokens[4];
7948
80
        const auto &code = tokens[5];
7949
80
        return createFromURNPart(dbContext, type, authName, std::string(),
7950
80
                                 code);
7951
80
    }
7952
7953
4.13k
    if (dbContext) {
7954
1.38k
        auto factory =
7955
1.38k
            AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string());
7956
7957
1.38k
        const auto searchObject =
7958
1.38k
            [&factory](
7959
1.38k
                const std::string &objectName, bool approximateMatch,
7960
1.38k
                const std::vector<AuthorityFactory::ObjectType> &objectTypes)
7961
5.00k
            -> IdentifiedObjectPtr {
7962
5.00k
            constexpr size_t limitResultCount = 10;
7963
5.00k
            auto res = factory->createObjectsFromName(
7964
5.00k
                objectName, objectTypes, approximateMatch, limitResultCount);
7965
5.00k
            if (res.size() == 1) {
7966
120
                return res.front().as_nullable();
7967
120
            }
7968
4.88k
            if (res.size() > 1) {
7969
667
                if (objectTypes.size() == 1 &&
7970
667
                    objectTypes[0] == AuthorityFactory::ObjectType::CRS) {
7971
638
                    for (size_t ndim = 2; ndim <= 3; ndim++) {
7972
1.83k
                        for (const auto &obj : res) {
7973
1.83k
                            auto crs =
7974
1.83k
                                dynamic_cast<crs::GeographicCRS *>(obj.get());
7975
1.83k
                            if (crs &&
7976
1.83k
                                crs->coordinateSystem()->axisList().size() ==
7977
746
                                    ndim) {
7978
500
                                return obj.as_nullable();
7979
500
                            }
7980
1.83k
                        }
7981
594
                    }
7982
544
                }
7983
7984
                // If there's exactly only one object whose name is equivalent
7985
                // to the user input, return it.
7986
501
                for (int pass = 0; pass <= 1; ++pass) {
7987
334
                    IdentifiedObjectPtr identifiedObj;
7988
2.77k
                    for (const auto &obj : res) {
7989
2.77k
                        if (Identifier::isEquivalentName(
7990
2.77k
                                obj->nameStr().c_str(), objectName.c_str(),
7991
2.77k
                                /* biggerDifferencesAllowed = */ pass == 1)) {
7992
0
                            if (identifiedObj == nullptr) {
7993
0
                                identifiedObj = obj.as_nullable();
7994
0
                            } else {
7995
0
                                identifiedObj = nullptr;
7996
0
                                break;
7997
0
                            }
7998
0
                        }
7999
2.77k
                    }
8000
334
                    if (identifiedObj) {
8001
0
                        return identifiedObj;
8002
0
                    }
8003
334
                }
8004
8005
167
                std::string msg("several objects matching this name: ");
8006
167
                bool first = true;
8007
1.18k
                for (const auto &obj : res) {
8008
1.18k
                    if (msg.size() > 200) {
8009
103
                        msg += ", ...";
8010
103
                        break;
8011
103
                    }
8012
1.08k
                    if (!first) {
8013
913
                        msg += ", ";
8014
913
                    }
8015
1.08k
                    first = false;
8016
1.08k
                    msg += obj->nameStr();
8017
1.08k
                }
8018
167
                throw ParsingException(msg);
8019
167
            }
8020
4.21k
            return nullptr;
8021
4.88k
        };
8022
8023
1.38k
        const auto searchCRS = [&searchObject](const std::string &objectName) {
8024
28
            const auto objectTypes = std::vector<AuthorityFactory::ObjectType>{
8025
28
                AuthorityFactory::ObjectType::CRS};
8026
28
            {
8027
28
                constexpr bool approximateMatch = false;
8028
28
                auto ret =
8029
28
                    searchObject(objectName, approximateMatch, objectTypes);
8030
28
                if (ret)
8031
0
                    return ret;
8032
28
            }
8033
8034
28
            constexpr bool approximateMatch = true;
8035
28
            return searchObject(objectName, approximateMatch, objectTypes);
8036
28
        };
8037
8038
        // strings like "WGS 84 + EGM96 height"
8039
1.38k
        CompoundCRSPtr compoundCRS;
8040
1.38k
        try {
8041
1.38k
            const auto tokensCompound = split(text, " + ");
8042
1.38k
            if (tokensCompound.size() == 2) {
8043
14
                auto obj1 = searchCRS(tokensCompound[0]);
8044
14
                auto obj2 = searchCRS(tokensCompound[1]);
8045
14
                auto crs1 = std::dynamic_pointer_cast<CRS>(obj1);
8046
14
                auto crs2 = std::dynamic_pointer_cast<CRS>(obj2);
8047
14
                if (crs1 && crs2) {
8048
0
                    compoundCRS =
8049
0
                        CompoundCRS::create(
8050
0
                            util::PropertyMap().set(IdentifiedObject::NAME_KEY,
8051
0
                                                    crs1->nameStr() + " + " +
8052
0
                                                        crs2->nameStr()),
8053
0
                            {NN_NO_CHECK(crs1), NN_NO_CHECK(crs2)})
8054
0
                            .as_nullable();
8055
0
                }
8056
14
            }
8057
1.38k
        } catch (const std::exception &) {
8058
0
        }
8059
8060
        // First pass: exact match on CRS objects
8061
        // Second pass: exact match on other objects
8062
        // Third pass: approximate match on CRS objects
8063
        // Fourth pass: approximate match on other objects
8064
        // But only allow approximate matching if the size of the text is
8065
        // large enough (>= 5), otherwise we get a lot of false positives:
8066
        // "foo" -> "Amersfoort", "bar" -> "Barbados 1938"
8067
        // Also only accept approximate matching if the ratio between the
8068
        // input and match size is not too small, so that "omerc" doesn't match
8069
        // with "WGS 84 / Pseudo-Mercator"
8070
1.38k
        const int maxNumberPasses = text.size() <= 4 ? 2 : 4;
8071
5.81k
        for (int pass = 0; pass < maxNumberPasses; ++pass) {
8072
4.94k
            const bool approximateMatch = (pass >= 2);
8073
4.94k
            auto ret = searchObject(
8074
4.94k
                text, approximateMatch,
8075
4.94k
                (pass == 0 || pass == 2)
8076
4.94k
                    ? std::vector<
8077
2.70k
                          AuthorityFactory::ObjectType>{AuthorityFactory::
8078
2.70k
                                                            ObjectType::CRS}
8079
4.94k
                    : std::vector<AuthorityFactory::ObjectType>{
8080
2.24k
                          AuthorityFactory::ObjectType::ELLIPSOID,
8081
2.24k
                          AuthorityFactory::ObjectType::DATUM,
8082
2.24k
                          AuthorityFactory::ObjectType::DATUM_ENSEMBLE,
8083
2.24k
                          AuthorityFactory::ObjectType::COORDINATE_OPERATION});
8084
4.94k
            if (ret) {
8085
617
                if (!approximateMatch ||
8086
617
                    ret->nameStr().size() < 2 * text.size())
8087
514
                    return NN_NO_CHECK(ret);
8088
617
            }
8089
4.43k
            if (compoundCRS) {
8090
0
                if (!approximateMatch ||
8091
0
                    compoundCRS->nameStr().size() < 2 * text.size())
8092
0
                    return NN_NO_CHECK(compoundCRS);
8093
0
            }
8094
4.43k
        }
8095
1.38k
    }
8096
8097
3.62k
    throw ParsingException("unrecognized format / unknown name");
8098
4.13k
}
8099
//! @endcond
8100
8101
// ---------------------------------------------------------------------------
8102
8103
/** \brief Instantiate a sub-class of BaseObject from a user specified text.
8104
 *
8105
 * The text can be a:
8106
 * <ul>
8107
 * <li>WKT string</li>
8108
 * <li>PROJ string</li>
8109
 * <li>database code, prefixed by its authority. e.g. "EPSG:4326"</li>
8110
 * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
8111
 *     "urn:ogc:def:coordinateOperation:EPSG::1671",
8112
 *     "urn:ogc:def:ellipsoid:EPSG::7001"
8113
 *     or "urn:ogc:def:datum:EPSG::6326"</li>
8114
 * <li> OGC URN combining references for compound coordinate reference systems
8115
 *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
8116
 *      We also accept a custom abbreviated syntax EPSG:2393+5717
8117
 *      or ESRI:103668+EPSG:5703
8118
 * </li>
8119
 * <li> OGC URN combining references for references for projected or derived
8120
 * CRSs
8121
 *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
8122
 *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
8123
 * </li>
8124
 * <li>Extension of OGC URN for CoordinateMetadata.
8125
 *     e.g.
8126
 * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"</li>
8127
 * <li> OGC URN combining references for concatenated operations
8128
 *      e.g.
8129
 * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
8130
 * <li>OGC URL for a single CRS. e.g.
8131
 * "http://www.opengis.net/def/crs/EPSG/0/4326"</li>
8132
 * <li>OGC URL for a compound
8133
 * CRS. e.g
8134
 * "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>
8135
 * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
8136
 *     uniqueness is not guaranteed, the function may apply heuristics to
8137
 *     determine the appropriate best match.</li>
8138
 * <li>a CRS name and a coordinate epoch, separated with '@'. For example
8139
 *     "ITRF2014@2025.0". (added in PROJ 9.2)</li>
8140
 * <li>a compound CRS made from two object names separated with " + ".
8141
 *     e.g. "WGS 84 + EGM96 height"</li>
8142
 * <li>PROJJSON string</li>
8143
 * </ul>
8144
 *
8145
 * @param text One of the above mentioned text format
8146
 * @param dbContext Database context, or nullptr (in which case database
8147
 * lookups will not work)
8148
 * @param usePROJ4InitRules When set to true,
8149
 * init=epsg:XXXX syntax will be allowed and will be interpreted according to
8150
 * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
8151
 * order and will expect/output coordinates in radians. ProjectedCRS will have
8152
 * easting, northing axis order (except the ones with Transverse Mercator South
8153
 * Orientated projection). In that mode, the epsg:XXXX syntax will be also
8154
 * interpreted the same way.
8155
 * @throw ParsingException if the string cannot be parsed.
8156
 */
8157
BaseObjectNNPtr createFromUserInput(const std::string &text,
8158
                                    const DatabaseContextPtr &dbContext,
8159
11.6k
                                    bool usePROJ4InitRules) {
8160
11.6k
    return createFromUserInput(text, dbContext, usePROJ4InitRules, nullptr,
8161
11.6k
                               /* ignoreCoordinateEpoch = */ false);
8162
11.6k
}
8163
8164
// ---------------------------------------------------------------------------
8165
8166
/** \brief Instantiate a sub-class of BaseObject from a user specified text.
8167
 *
8168
 * The text can be a:
8169
 * <ul>
8170
 * <li>WKT string</li>
8171
 * <li>PROJ string</li>
8172
 * <li>database code, prefixed by its authority. e.g. "EPSG:4326"</li>
8173
 * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
8174
 *     "urn:ogc:def:coordinateOperation:EPSG::1671",
8175
 *     "urn:ogc:def:ellipsoid:EPSG::7001"
8176
 *     or "urn:ogc:def:datum:EPSG::6326"</li>
8177
 * <li> OGC URN combining references for compound coordinate reference systems
8178
 *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
8179
 *      We also accept a custom abbreviated syntax EPSG:2393+5717
8180
 * </li>
8181
 * <li> OGC URN combining references for references for projected or derived
8182
 * CRSs
8183
 *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
8184
 *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
8185
 * </li>
8186
 * <li>Extension of OGC URN for CoordinateMetadata.
8187
 *     e.g.
8188
 * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"</li>
8189
 * <li> OGC URN combining references for concatenated operations
8190
 *      e.g.
8191
 * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
8192
 * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
8193
 *     uniqueness is not guaranteed, the function may apply heuristics to
8194
 *     determine the appropriate best match.</li>
8195
 * <li>a compound CRS made from two object names separated with " + ".
8196
 *     e.g. "WGS 84 + EGM96 height"</li>
8197
 * <li>PROJJSON string</li>
8198
 * </ul>
8199
 *
8200
 * @param text One of the above mentioned text format
8201
 * @param ctx PROJ context
8202
 * @throw ParsingException if the string cannot be parsed.
8203
 */
8204
44.2k
BaseObjectNNPtr createFromUserInput(const std::string &text, PJ_CONTEXT *ctx) {
8205
44.2k
    DatabaseContextPtr dbContext;
8206
44.2k
    try {
8207
44.2k
        if (ctx != nullptr) {
8208
            // Only connect to proj.db if needed
8209
44.2k
            if (text.find("proj=") == std::string::npos ||
8210
44.2k
                text.find("init=") != std::string::npos) {
8211
22.3k
                dbContext =
8212
22.3k
                    ctx->get_cpp_context()->getDatabaseContext().as_nullable();
8213
22.3k
            }
8214
44.2k
        }
8215
44.2k
    } catch (const std::exception &) {
8216
0
    }
8217
44.2k
    return createFromUserInput(text, dbContext, false, ctx,
8218
44.2k
                               /* ignoreCoordinateEpoch = */ false);
8219
44.2k
}
8220
8221
// ---------------------------------------------------------------------------
8222
8223
/** \brief Instantiate a sub-class of BaseObject from a WKT string.
8224
 *
8225
 * By default, validation is strict (to the extent of the checks that are
8226
 * actually implemented. Currently only WKT1 strict grammar is checked), and
8227
 * any issue detected will cause an exception to be thrown, unless
8228
 * setStrict(false) is called priorly.
8229
 *
8230
 * In non-strict mode, non-fatal issues will be recovered and simply listed
8231
 * in warningList(). This does not prevent more severe errors to cause an
8232
 * exception to be thrown.
8233
 *
8234
 * @throw ParsingException if the string cannot be parsed.
8235
 */
8236
9.93k
BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) {
8237
8238
9.93k
    const auto dialect = guessDialect(wkt);
8239
9.93k
    d->maybeEsriStyle_ = (dialect == WKTGuessedDialect::WKT1_ESRI);
8240
9.93k
    if (d->maybeEsriStyle_) {
8241
1.88k
        if (wkt.find("PARAMETER[\"X_Scale\",") != std::string::npos) {
8242
0
            d->esriStyle_ = true;
8243
0
            d->maybeEsriStyle_ = false;
8244
0
        }
8245
1.88k
    }
8246
8247
9.93k
    const auto build = [this, &wkt]() -> BaseObjectNNPtr {
8248
9.93k
        size_t indexEnd;
8249
9.93k
        WKTNodeNNPtr root = WKTNode::createFrom(wkt, 0, 0, indexEnd);
8250
9.93k
        const std::string &name(root->GP()->value());
8251
9.93k
        if (ci_equal(name, WKTConstants::DATUM) ||
8252
9.93k
            ci_equal(name, WKTConstants::GEODETICDATUM) ||
8253
9.93k
            ci_equal(name, WKTConstants::TRF)) {
8254
8255
28
            auto primeMeridian = PrimeMeridian::GREENWICH;
8256
28
            if (indexEnd < wkt.size()) {
8257
23
                indexEnd = skipSpace(wkt, indexEnd);
8258
23
                if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
8259
16
                    ++indexEnd;
8260
16
                    indexEnd = skipSpace(wkt, indexEnd);
8261
16
                    if (indexEnd < wkt.size() &&
8262
16
                        ci_starts_with(wkt.c_str() + indexEnd,
8263
14
                                       WKTConstants::PRIMEM.c_str())) {
8264
10
                        primeMeridian = d->buildPrimeMeridian(
8265
10
                            WKTNode::createFrom(wkt, indexEnd, 0, indexEnd),
8266
10
                            UnitOfMeasure::DEGREE);
8267
10
                    }
8268
16
                }
8269
23
            }
8270
28
            return d->buildGeodeticReferenceFrame(root, primeMeridian,
8271
28
                                                  null_node);
8272
9.90k
        } else if (ci_equal(name, WKTConstants::GEOGCS) ||
8273
9.90k
                   ci_equal(name, WKTConstants::PROJCS)) {
8274
            // Parse implicit compoundCRS from ESRI that is
8275
            // "PROJCS[...],VERTCS[...]" or "GEOGCS[...],VERTCS[...]"
8276
862
            if (indexEnd < wkt.size()) {
8277
223
                indexEnd = skipSpace(wkt, indexEnd);
8278
223
                if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
8279
83
                    ++indexEnd;
8280
83
                    indexEnd = skipSpace(wkt, indexEnd);
8281
83
                    if (indexEnd < wkt.size() &&
8282
83
                        ci_starts_with(wkt.c_str() + indexEnd,
8283
5
                                       WKTConstants::VERTCS.c_str())) {
8284
0
                        auto horizCRS = d->buildCRS(root);
8285
0
                        if (horizCRS) {
8286
0
                            auto vertCRS =
8287
0
                                d->buildVerticalCRS(WKTNode::createFrom(
8288
0
                                    wkt, indexEnd, 0, indexEnd));
8289
0
                            return CompoundCRS::createLax(
8290
0
                                util::PropertyMap().set(
8291
0
                                    IdentifiedObject::NAME_KEY,
8292
0
                                    horizCRS->nameStr() + " + " +
8293
0
                                        vertCRS->nameStr()),
8294
0
                                {NN_NO_CHECK(horizCRS), vertCRS},
8295
0
                                d->dbContext_);
8296
0
                        }
8297
0
                    }
8298
83
                }
8299
223
            }
8300
862
        }
8301
9.90k
        return d->build(root);
8302
9.93k
    };
8303
8304
9.93k
    auto obj = build();
8305
8306
9.93k
    if (dialect == WKTGuessedDialect::WKT1_GDAL ||
8307
9.93k
        dialect == WKTGuessedDialect::WKT1_ESRI) {
8308
2.89k
        auto errorMsg = pj_wkt1_parse(wkt);
8309
2.89k
        if (!errorMsg.empty()) {
8310
2.69k
            d->emitGrammarError(errorMsg);
8311
2.69k
        }
8312
7.04k
    } else if (dialect == WKTGuessedDialect::WKT2_2015 ||
8313
7.04k
               dialect == WKTGuessedDialect::WKT2_2019) {
8314
5.15k
        auto errorMsg = pj_wkt2_parse(wkt);
8315
5.15k
        if (!errorMsg.empty()) {
8316
4.94k
            d->emitGrammarError(errorMsg);
8317
4.94k
        }
8318
5.15k
    }
8319
8320
9.93k
    return obj;
8321
9.93k
}
8322
8323
// ---------------------------------------------------------------------------
8324
8325
/** \brief Attach a database context, to allow queries in it if needed.
8326
 */
8327
WKTParser &
8328
9.93k
WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
8329
9.93k
    d->dbContext_ = dbContext;
8330
9.93k
    return *this;
8331
9.93k
}
8332
8333
// ---------------------------------------------------------------------------
8334
8335
/** \brief Guess the "dialect" of the WKT string.
8336
 */
8337
WKTParser::WKTGuessedDialect
8338
9.93k
WKTParser::guessDialect(const std::string &inputWkt) noexcept {
8339
8340
    // cppcheck complains (rightly) that the method could be static
8341
9.93k
    (void)this;
8342
8343
9.93k
    std::string wkt = inputWkt;
8344
9.93k
    std::size_t idxFirstCharNotSpace = wkt.find_first_not_of(" \t\r\n");
8345
9.93k
    if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) {
8346
0
        wkt = wkt.substr(idxFirstCharNotSpace);
8347
0
    }
8348
9.93k
    if (ci_starts_with(wkt, WKTConstants::VERTCS)) {
8349
272
        return WKTGuessedDialect::WKT1_ESRI;
8350
272
    }
8351
9.66k
    const std::string *const wkt1_keywords[] = {
8352
9.66k
        &WKTConstants::GEOCCS, &WKTConstants::GEOGCS,  &WKTConstants::COMPD_CS,
8353
9.66k
        &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS};
8354
53.4k
    for (const auto &pointerKeyword : wkt1_keywords) {
8355
53.4k
        if (ci_starts_with(wkt, *pointerKeyword)) {
8356
8357
2.81k
            if ((ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos ||
8358
2.81k
                 (!ci_starts_with(wkt, WKTConstants::LOCAL_CS) &&
8359
2.75k
                  ci_find(wkt, "AXIS[") == std::string::npos &&
8360
2.75k
                  ci_find(wkt, "AUTHORITY[") == std::string::npos)) &&
8361
                // WKT1:GDAL and WKT1:ESRI have both a
8362
                // Hotine_Oblique_Mercator_Azimuth_Center If providing a
8363
                // WKT1:GDAL without AXIS, we may wrongly detect it as WKT1:ESRI
8364
                // and skip the rectified_grid_angle parameter cf
8365
                // https://github.com/OSGeo/PROJ/issues/3279
8366
2.81k
                ci_find(wkt, "PARAMETER[\"rectified_grid_angle") ==
8367
1.61k
                    std::string::npos) {
8368
1.61k
                return WKTGuessedDialect::WKT1_ESRI;
8369
1.61k
            }
8370
8371
1.19k
            return WKTGuessedDialect::WKT1_GDAL;
8372
2.81k
        }
8373
53.4k
    }
8374
8375
6.84k
    const std::string *const wkt2_2019_only_keywords[] = {
8376
6.84k
        &WKTConstants::GEOGCRS,
8377
        // contained in previous one
8378
        // &WKTConstants::BASEGEOGCRS,
8379
6.84k
        &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE,
8380
6.84k
        &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL,
8381
6.84k
        &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE,
8382
6.84k
        &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS,
8383
6.84k
        &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF,
8384
6.84k
        &WKTConstants::POINTMOTIONOPERATION};
8385
8386
94.8k
    for (const auto &pointerKeyword : wkt2_2019_only_keywords) {
8387
94.8k
        auto pos = ci_find(wkt, *pointerKeyword);
8388
94.8k
        if (pos != std::string::npos &&
8389
94.8k
            wkt[pos + pointerKeyword->size()] == '[') {
8390
173
            return WKTGuessedDialect::WKT2_2019;
8391
173
        }
8392
94.8k
    }
8393
6.67k
    static const char *const wkt2_2019_only_substrings[] = {
8394
6.67k
        "CS[TemporalDateTime,",
8395
6.67k
        "CS[TemporalCount,",
8396
6.67k
        "CS[TemporalMeasure,",
8397
6.67k
    };
8398
19.9k
    for (const auto &substrings : wkt2_2019_only_substrings) {
8399
19.9k
        if (ci_find(wkt, substrings) != std::string::npos) {
8400
199
            return WKTGuessedDialect::WKT2_2019;
8401
199
        }
8402
19.9k
    }
8403
8404
273k
    for (const auto &wktConstant : WKTConstants::constants()) {
8405
273k
        if (ci_starts_with(wkt, wktConstant)) {
8406
6.47k
            for (auto wktPtr = wkt.c_str() + wktConstant.size();
8407
7.97k
                 *wktPtr != '\0'; ++wktPtr) {
8408
7.97k
                if (isspace(static_cast<unsigned char>(*wktPtr)))
8409
1.49k
                    continue;
8410
6.47k
                if (*wktPtr == '[') {
8411
6.47k
                    return WKTGuessedDialect::WKT2_2015;
8412
6.47k
                }
8413
0
                break;
8414
6.47k
            }
8415
6.47k
        }
8416
273k
    }
8417
8418
0
    return WKTGuessedDialect::NOT_WKT;
8419
6.47k
}
8420
8421
// ---------------------------------------------------------------------------
8422
8423
//! @cond Doxygen_Suppress
8424
FormattingException::FormattingException(const char *message)
8425
211
    : Exception(message) {}
8426
8427
// ---------------------------------------------------------------------------
8428
8429
FormattingException::FormattingException(const std::string &message)
8430
1.36k
    : Exception(message) {}
8431
8432
// ---------------------------------------------------------------------------
8433
8434
0
FormattingException::FormattingException(const FormattingException &) = default;
8435
8436
// ---------------------------------------------------------------------------
8437
8438
1.57k
FormattingException::~FormattingException() = default;
8439
8440
// ---------------------------------------------------------------------------
8441
8442
1
void FormattingException::Throw(const char *msg) {
8443
1
    throw FormattingException(msg);
8444
1
}
8445
8446
// ---------------------------------------------------------------------------
8447
8448
0
void FormattingException::Throw(const std::string &msg) {
8449
0
    throw FormattingException(msg);
8450
0
}
8451
8452
// ---------------------------------------------------------------------------
8453
8454
3.45k
ParsingException::ParsingException(const char *message) : Exception(message) {}
8455
8456
// ---------------------------------------------------------------------------
8457
8458
ParsingException::ParsingException(const std::string &message)
8459
3.88k
    : Exception(message) {}
8460
8461
// ---------------------------------------------------------------------------
8462
8463
0
ParsingException::ParsingException(const ParsingException &) = default;
8464
8465
// ---------------------------------------------------------------------------
8466
8467
7.33k
ParsingException::~ParsingException() = default;
8468
8469
// ---------------------------------------------------------------------------
8470
8471
203k
IPROJStringExportable::~IPROJStringExportable() = default;
8472
8473
// ---------------------------------------------------------------------------
8474
8475
std::string IPROJStringExportable::exportToPROJString(
8476
21.1k
    PROJStringFormatter *formatter) const {
8477
21.1k
    const bool bIsCRS = dynamic_cast<const crs::CRS *>(this) != nullptr;
8478
21.1k
    if (bIsCRS) {
8479
9.11k
        formatter->setCRSExport(true);
8480
9.11k
    }
8481
21.1k
    _exportToPROJString(formatter);
8482
21.1k
    if (formatter->getAddNoDefs() && bIsCRS) {
8483
7.61k
        if (!formatter->hasParam("no_defs")) {
8484
6.91k
            formatter->addParam("no_defs");
8485
6.91k
        }
8486
7.61k
    }
8487
21.1k
    if (bIsCRS) {
8488
9.11k
        if (!formatter->hasParam("type")) {
8489
9.10k
            formatter->addParam("type", "crs");
8490
9.10k
        }
8491
9.11k
        formatter->setCRSExport(false);
8492
9.11k
    }
8493
21.1k
    return formatter->toString();
8494
21.1k
}
8495
//! @endcond
8496
8497
// ---------------------------------------------------------------------------
8498
8499
//! @cond Doxygen_Suppress
8500
8501
struct Step {
8502
    std::string name{};
8503
    bool isInit = false;
8504
    bool inverted{false};
8505
8506
    struct KeyValue {
8507
        std::string key{};
8508
        std::string value{};
8509
        bool usedByParser = false; // only for PROJStringParser used
8510
8511
392k
        explicit KeyValue(const std::string &keyIn) : key(keyIn) {}
8512
8513
        KeyValue(const char *keyIn, const std::string &valueIn);
8514
8515
        KeyValue(const std::string &keyIn, const std::string &valueIn)
8516
359k
            : key(keyIn), value(valueIn) {}
8517
8518
        // cppcheck-suppress functionStatic
8519
140k
        bool keyEquals(const char *otherKey) const noexcept {
8520
140k
            return key == otherKey;
8521
140k
        }
8522
8523
        // cppcheck-suppress functionStatic
8524
11.3k
        bool equals(const char *otherKey, const char *otherVal) const noexcept {
8525
11.3k
            return key == otherKey && value == otherVal;
8526
11.3k
        }
8527
8528
114
        bool operator==(const KeyValue &other) const noexcept {
8529
114
            return key == other.key && value == other.value;
8530
114
        }
8531
8532
14.5k
        bool operator!=(const KeyValue &other) const noexcept {
8533
14.5k
            return key != other.key || value != other.value;
8534
14.5k
        }
8535
    };
8536
8537
    std::vector<KeyValue> paramValues{};
8538
8539
29.6k
    bool hasKey(const char *keyName) const {
8540
214k
        for (const auto &kv : paramValues) {
8541
214k
            if (kv.key == keyName) {
8542
51
                return true;
8543
51
            }
8544
214k
        }
8545
29.5k
        return false;
8546
29.6k
    }
8547
};
8548
8549
Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn)
8550
4.15k
    : key(keyIn), value(valueIn) {}
8551
8552
struct PROJStringFormatter::Private {
8553
    PROJStringFormatter::Convention convention_ =
8554
        PROJStringFormatter::Convention::PROJ_5;
8555
    std::vector<double> toWGS84Parameters_{};
8556
    std::string vDatumExtension_{};
8557
    std::string geoidCRSValue_{};
8558
    std::string hDatumExtension_{};
8559
    crs::GeographicCRSPtr geogCRSOfCompoundCRS_{};
8560
8561
    std::list<Step> steps_{};
8562
    std::vector<Step::KeyValue> globalParamValues_{};
8563
8564
    struct InversionStackElt {
8565
        std::list<Step>::iterator startIter{};
8566
        bool iterValid = false;
8567
        bool currentInversionState = false;
8568
    };
8569
    std::vector<InversionStackElt> inversionStack_{InversionStackElt()};
8570
    bool omitProjLongLatIfPossible_ = false;
8571
    std::vector<bool> omitZUnitConversion_{false};
8572
    std::vector<bool> omitHorizontalConversionInVertTransformation_{false};
8573
    DatabaseContextPtr dbContext_{};
8574
    bool useApproxTMerc_ = false;
8575
    bool addNoDefs_ = true;
8576
    bool coordOperationOptimizations_ = false;
8577
    bool crsExport_ = false;
8578
    bool legacyCRSToCRSContext_ = false;
8579
    bool multiLine_ = false;
8580
    bool normalizeOutput_ = false;
8581
    int indentWidth_ = 2;
8582
    int indentLevel_ = 0;
8583
    int maxLineLength_ = 80;
8584
8585
    std::string result_{};
8586
8587
    // cppcheck-suppress functionStatic
8588
    void appendToResult(const char *str);
8589
8590
    // cppcheck-suppress functionStatic
8591
    void addStep();
8592
};
8593
8594
//! @endcond
8595
8596
// ---------------------------------------------------------------------------
8597
8598
//! @cond Doxygen_Suppress
8599
PROJStringFormatter::PROJStringFormatter(Convention conventionIn,
8600
                                         const DatabaseContextPtr &dbContext)
8601
31.5k
    : d(std::make_unique<Private>()) {
8602
31.5k
    d->convention_ = conventionIn;
8603
31.5k
    d->dbContext_ = dbContext;
8604
31.5k
}
8605
//! @endcond
8606
8607
// ---------------------------------------------------------------------------
8608
8609
//! @cond Doxygen_Suppress
8610
31.5k
PROJStringFormatter::~PROJStringFormatter() = default;
8611
//! @endcond
8612
8613
// ---------------------------------------------------------------------------
8614
8615
/** \brief Constructs a new formatter.
8616
 *
8617
 * A formatter can be used only once (its internal state is mutated)
8618
 *
8619
 * Its default behavior can be adjusted with the different setters.
8620
 *
8621
 * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5
8622
 * @param dbContext Database context (can help to find alternative grid names).
8623
 * May be nullptr
8624
 * @return new formatter.
8625
 */
8626
PROJStringFormatterNNPtr
8627
PROJStringFormatter::create(Convention conventionIn,
8628
31.5k
                            DatabaseContextPtr dbContext) {
8629
31.5k
    return NN_NO_CHECK(PROJStringFormatter::make_unique<PROJStringFormatter>(
8630
31.5k
        conventionIn, dbContext));
8631
31.5k
}
8632
8633
// ---------------------------------------------------------------------------
8634
8635
/** \brief Set whether approximate Transverse Mercator or UTM should be used */
8636
0
void PROJStringFormatter::setUseApproxTMerc(bool flag) {
8637
0
    d->useApproxTMerc_ = flag;
8638
0
}
8639
8640
// ---------------------------------------------------------------------------
8641
8642
/** \brief Whether to use multi line output or not. */
8643
PROJStringFormatter &
8644
0
PROJStringFormatter::setMultiLine(bool multiLine) noexcept {
8645
0
    d->multiLine_ = multiLine;
8646
0
    return *this;
8647
0
}
8648
8649
// ---------------------------------------------------------------------------
8650
8651
/** \brief Set number of spaces for each indentation level (defaults to 2).
8652
 */
8653
PROJStringFormatter &
8654
0
PROJStringFormatter::setIndentationWidth(int width) noexcept {
8655
0
    d->indentWidth_ = width;
8656
0
    return *this;
8657
0
}
8658
8659
// ---------------------------------------------------------------------------
8660
8661
/** \brief Set the maximum size of a line (when multiline output is enable).
8662
 * Can be set to 0 for unlimited length.
8663
 */
8664
PROJStringFormatter &
8665
0
PROJStringFormatter::setMaxLineLength(int maxLineLength) noexcept {
8666
0
    d->maxLineLength_ = maxLineLength;
8667
0
    return *this;
8668
0
}
8669
8670
// ---------------------------------------------------------------------------
8671
8672
/** \brief Returns the PROJ string. */
8673
19.5k
const std::string &PROJStringFormatter::toString() const {
8674
8675
19.5k
    assert(d->inversionStack_.size() == 1);
8676
8677
19.5k
    d->result_.clear();
8678
8679
19.5k
    auto &steps = d->steps_;
8680
8681
19.5k
    if (d->normalizeOutput_) {
8682
        // Sort +key=value options of each step in lexicographic order.
8683
0
        for (auto &step : steps) {
8684
0
            std::sort(step.paramValues.begin(), step.paramValues.end(),
8685
0
                      [](const Step::KeyValue &a, const Step::KeyValue &b) {
8686
0
                          return a.key < b.key;
8687
0
                      });
8688
0
        }
8689
0
    }
8690
8691
78.3k
    for (auto iter = steps.begin(); iter != steps.end();) {
8692
        // Remove no-op helmert
8693
58.7k
        auto &step = *iter;
8694
58.7k
        const auto paramCount = step.paramValues.size();
8695
58.7k
        if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) &&
8696
58.7k
            step.paramValues[0].equals("x", "0") &&
8697
58.7k
            step.paramValues[1].equals("y", "0") &&
8698
58.7k
            step.paramValues[2].equals("z", "0") &&
8699
58.7k
            (paramCount == 3 ||
8700
76
             (step.paramValues[3].equals("rx", "0") &&
8701
63
              step.paramValues[4].equals("ry", "0") &&
8702
63
              step.paramValues[5].equals("rz", "0") &&
8703
63
              step.paramValues[6].equals("s", "0") &&
8704
76
              step.paramValues[7].keyEquals("convention")))) {
8705
76
            iter = steps.erase(iter);
8706
58.6k
        } else if (d->coordOperationOptimizations_ &&
8707
58.6k
                   step.name == "unitconvert" && paramCount == 2 &&
8708
58.6k
                   step.paramValues[0].keyEquals("xy_in") &&
8709
58.6k
                   step.paramValues[1].keyEquals("xy_out") &&
8710
58.6k
                   step.paramValues[0].value == step.paramValues[1].value) {
8711
0
            iter = steps.erase(iter);
8712
58.6k
        } else if (step.name == "push" && step.inverted) {
8713
404
            step.name = "pop";
8714
404
            step.inverted = false;
8715
404
            ++iter;
8716
58.2k
        } else if (step.name == "pop" && step.inverted) {
8717
259
            step.name = "push";
8718
259
            step.inverted = false;
8719
259
            ++iter;
8720
58.0k
        } else if (step.name == "noop" && steps.size() > 1) {
8721
0
            iter = steps.erase(iter);
8722
58.0k
        } else {
8723
58.0k
            ++iter;
8724
58.0k
        }
8725
58.7k
    }
8726
8727
58.6k
    for (auto &step : steps) {
8728
58.6k
        if (!step.inverted) {
8729
38.5k
            continue;
8730
38.5k
        }
8731
8732
20.0k
        const auto paramCount = step.paramValues.size();
8733
8734
        // axisswap order=2,1 (or 1,-2) is its own inverse
8735
20.0k
        if (step.name == "axisswap" && paramCount == 1 &&
8736
20.0k
            (step.paramValues[0].equals("order", "2,1") ||
8737
632
             step.paramValues[0].equals("order", "1,-2"))) {
8738
381
            step.inverted = false;
8739
381
            continue;
8740
381
        }
8741
8742
        // axisswap inv order=2,-1 ==> axisswap order -2,1
8743
19.7k
        if (step.name == "axisswap" && paramCount == 1 &&
8744
19.7k
            step.paramValues[0].equals("order", "2,-1")) {
8745
83
            step.inverted = false;
8746
83
            step.paramValues[0] = Step::KeyValue("order", "-2,1");
8747
83
            continue;
8748
83
        }
8749
8750
        // axisswap order=1,2,-3 is its own inverse
8751
19.6k
        if (step.name == "axisswap" && paramCount == 1 &&
8752
19.6k
            step.paramValues[0].equals("order", "1,2,-3")) {
8753
0
            step.inverted = false;
8754
0
            continue;
8755
0
        }
8756
8757
        // handle unitconvert inverse
8758
19.6k
        if (step.name == "unitconvert" && paramCount == 2 &&
8759
19.6k
            step.paramValues[0].keyEquals("xy_in") &&
8760
19.6k
            step.paramValues[1].keyEquals("xy_out")) {
8761
265
            std::swap(step.paramValues[0].value, step.paramValues[1].value);
8762
265
            step.inverted = false;
8763
265
            continue;
8764
265
        }
8765
8766
19.3k
        if (step.name == "unitconvert" && paramCount == 2 &&
8767
19.3k
            step.paramValues[0].keyEquals("z_in") &&
8768
19.3k
            step.paramValues[1].keyEquals("z_out")) {
8769
160
            std::swap(step.paramValues[0].value, step.paramValues[1].value);
8770
160
            step.inverted = false;
8771
160
            continue;
8772
160
        }
8773
8774
19.2k
        if (step.name == "unitconvert" && paramCount == 4 &&
8775
19.2k
            step.paramValues[0].keyEquals("xy_in") &&
8776
19.2k
            step.paramValues[1].keyEquals("z_in") &&
8777
19.2k
            step.paramValues[2].keyEquals("xy_out") &&
8778
19.2k
            step.paramValues[3].keyEquals("z_out")) {
8779
0
            std::swap(step.paramValues[0].value, step.paramValues[2].value);
8780
0
            std::swap(step.paramValues[1].value, step.paramValues[3].value);
8781
0
            step.inverted = false;
8782
0
            continue;
8783
0
        }
8784
19.2k
    }
8785
8786
19.5k
    {
8787
19.5k
        auto iterCur = steps.begin();
8788
19.5k
        if (iterCur != steps.end()) {
8789
19.1k
            ++iterCur;
8790
19.1k
        }
8791
67.7k
        while (iterCur != steps.end()) {
8792
8793
48.2k
            assert(iterCur != steps.begin());
8794
48.2k
            auto iterPrev = std::prev(iterCur);
8795
48.2k
            auto &prevStep = *iterPrev;
8796
48.2k
            auto &curStep = *iterCur;
8797
8798
48.2k
            const auto curStepParamCount = curStep.paramValues.size();
8799
48.2k
            const auto prevStepParamCount = prevStep.paramValues.size();
8800
8801
48.2k
            const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() {
8802
10.8k
                iterCur = steps.erase(iterPrev, std::next(iterCur));
8803
10.8k
                if (iterCur != steps.begin())
8804
10.4k
                    iterCur = std::prev(iterCur);
8805
10.8k
                if (iterCur == steps.begin() && iterCur != steps.end())
8806
1.05k
                    ++iterCur;
8807
10.8k
            };
8808
8809
            // longlat (or its inverse) with ellipsoid only is a no-op
8810
            // do that only for an internal step
8811
48.2k
            if (std::next(iterCur) != steps.end() &&
8812
48.2k
                curStep.name == "longlat" && curStepParamCount == 1 &&
8813
48.2k
                curStep.paramValues[0].keyEquals("ellps")) {
8814
20
                iterCur = steps.erase(iterCur);
8815
20
                continue;
8816
20
            }
8817
8818
            // push v_x followed by pop v_x is a no-op.
8819
48.2k
            if (curStep.name == "pop" && prevStep.name == "push" &&
8820
48.2k
                !curStep.inverted && !prevStep.inverted &&
8821
48.2k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
8822
48.2k
                curStep.paramValues[0].key == prevStep.paramValues[0].key) {
8823
43
                deletePrevAndCurIter();
8824
43
                continue;
8825
43
            }
8826
8827
            // pop v_x followed by push v_x is, almost, a no-op. For our
8828
            // purposes,
8829
            // we consider it as a no-op for better pipeline optimizations.
8830
48.2k
            if (curStep.name == "push" && prevStep.name == "pop" &&
8831
48.2k
                !curStep.inverted && !prevStep.inverted &&
8832
48.2k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
8833
48.2k
                curStep.paramValues[0].key == prevStep.paramValues[0].key) {
8834
32
                deletePrevAndCurIter();
8835
32
                continue;
8836
32
            }
8837
8838
            // unitconvert (xy) followed by its inverse is a no-op
8839
48.1k
            if (curStep.name == "unitconvert" &&
8840
48.1k
                prevStep.name == "unitconvert" && !curStep.inverted &&
8841
48.1k
                !prevStep.inverted && curStepParamCount == 2 &&
8842
48.1k
                prevStepParamCount == 2 &&
8843
48.1k
                curStep.paramValues[0].keyEquals("xy_in") &&
8844
48.1k
                prevStep.paramValues[0].keyEquals("xy_in") &&
8845
48.1k
                curStep.paramValues[1].keyEquals("xy_out") &&
8846
48.1k
                prevStep.paramValues[1].keyEquals("xy_out") &&
8847
48.1k
                curStep.paramValues[0].value == prevStep.paramValues[1].value &&
8848
48.1k
                curStep.paramValues[1].value == prevStep.paramValues[0].value) {
8849
3
                deletePrevAndCurIter();
8850
3
                continue;
8851
3
            }
8852
8853
            // unitconvert (z) followed by its inverse is a no-op
8854
48.1k
            if (curStep.name == "unitconvert" &&
8855
48.1k
                prevStep.name == "unitconvert" && !curStep.inverted &&
8856
48.1k
                !prevStep.inverted && curStepParamCount == 2 &&
8857
48.1k
                prevStepParamCount == 2 &&
8858
48.1k
                curStep.paramValues[0].keyEquals("z_in") &&
8859
48.1k
                prevStep.paramValues[0].keyEquals("z_in") &&
8860
48.1k
                curStep.paramValues[1].keyEquals("z_out") &&
8861
48.1k
                prevStep.paramValues[1].keyEquals("z_out") &&
8862
48.1k
                curStep.paramValues[0].value == prevStep.paramValues[1].value &&
8863
48.1k
                curStep.paramValues[1].value == prevStep.paramValues[0].value) {
8864
9
                deletePrevAndCurIter();
8865
9
                continue;
8866
9
            }
8867
8868
            // unitconvert (xyz) followed by its inverse is a no-op
8869
48.1k
            if (curStep.name == "unitconvert" &&
8870
48.1k
                prevStep.name == "unitconvert" && !curStep.inverted &&
8871
48.1k
                !prevStep.inverted && curStepParamCount == 4 &&
8872
48.1k
                prevStepParamCount == 4 &&
8873
48.1k
                curStep.paramValues[0].keyEquals("xy_in") &&
8874
48.1k
                prevStep.paramValues[0].keyEquals("xy_in") &&
8875
48.1k
                curStep.paramValues[1].keyEquals("z_in") &&
8876
48.1k
                prevStep.paramValues[1].keyEquals("z_in") &&
8877
48.1k
                curStep.paramValues[2].keyEquals("xy_out") &&
8878
48.1k
                prevStep.paramValues[2].keyEquals("xy_out") &&
8879
48.1k
                curStep.paramValues[3].keyEquals("z_out") &&
8880
48.1k
                prevStep.paramValues[3].keyEquals("z_out") &&
8881
48.1k
                curStep.paramValues[0].value == prevStep.paramValues[2].value &&
8882
48.1k
                curStep.paramValues[1].value == prevStep.paramValues[3].value &&
8883
48.1k
                curStep.paramValues[2].value == prevStep.paramValues[0].value &&
8884
48.1k
                curStep.paramValues[3].value == prevStep.paramValues[1].value) {
8885
0
                deletePrevAndCurIter();
8886
0
                continue;
8887
0
            }
8888
8889
48.1k
            const auto deletePrevIter = [&steps, &iterPrev, &iterCur]() {
8890
769
                steps.erase(iterPrev, iterCur);
8891
769
                if (iterCur != steps.begin())
8892
571
                    iterCur = std::prev(iterCur);
8893
769
                if (iterCur == steps.begin())
8894
455
                    ++iterCur;
8895
769
            };
8896
8897
            // combine unitconvert (xy) and unitconvert (z)
8898
48.1k
            bool changeDone = false;
8899
143k
            for (int k = 0; k < 2; ++k) {
8900
96.1k
                auto &first = (k == 0) ? curStep : prevStep;
8901
96.1k
                auto &second = (k == 0) ? prevStep : curStep;
8902
96.1k
                if (first.name == "unitconvert" &&
8903
96.1k
                    second.name == "unitconvert" && !first.inverted &&
8904
96.1k
                    !second.inverted && first.paramValues.size() == 2 &&
8905
96.1k
                    second.paramValues.size() == 2 &&
8906
96.1k
                    second.paramValues[0].keyEquals("xy_in") &&
8907
96.1k
                    second.paramValues[1].keyEquals("xy_out") &&
8908
96.1k
                    first.paramValues[0].keyEquals("z_in") &&
8909
96.1k
                    first.paramValues[1].keyEquals("z_out")) {
8910
8911
407
                    const std::string xy_in(second.paramValues[0].value);
8912
407
                    const std::string xy_out(second.paramValues[1].value);
8913
407
                    const std::string z_in(first.paramValues[0].value);
8914
407
                    const std::string z_out(first.paramValues[1].value);
8915
8916
407
                    iterCur->paramValues.clear();
8917
407
                    iterCur->paramValues.emplace_back(
8918
407
                        Step::KeyValue("xy_in", xy_in));
8919
407
                    iterCur->paramValues.emplace_back(
8920
407
                        Step::KeyValue("z_in", z_in));
8921
407
                    iterCur->paramValues.emplace_back(
8922
407
                        Step::KeyValue("xy_out", xy_out));
8923
407
                    iterCur->paramValues.emplace_back(
8924
407
                        Step::KeyValue("z_out", z_out));
8925
8926
407
                    deletePrevIter();
8927
407
                    changeDone = true;
8928
407
                    break;
8929
407
                }
8930
96.1k
            }
8931
48.1k
            if (changeDone) {
8932
407
                continue;
8933
407
            }
8934
8935
            // +step +proj=unitconvert +xy_in=X1 +xy_out=X2
8936
            //  +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2
8937
            // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2
8938
143k
            for (int k = 0; k < 2; ++k) {
8939
95.5k
                auto &first = (k == 0) ? curStep : prevStep;
8940
95.5k
                auto &second = (k == 0) ? prevStep : curStep;
8941
95.5k
                if (first.name == "unitconvert" &&
8942
95.5k
                    second.name == "unitconvert" && !first.inverted &&
8943
95.5k
                    !second.inverted && first.paramValues.size() == 4 &&
8944
95.5k
                    second.paramValues.size() == 2 &&
8945
95.5k
                    first.paramValues[0].keyEquals("xy_in") &&
8946
95.5k
                    first.paramValues[1].keyEquals("z_in") &&
8947
95.5k
                    first.paramValues[2].keyEquals("xy_out") &&
8948
95.5k
                    first.paramValues[3].keyEquals("z_out") &&
8949
95.5k
                    second.paramValues[0].keyEquals("xy_in") &&
8950
95.5k
                    second.paramValues[1].keyEquals("xy_out") &&
8951
95.5k
                    first.paramValues[0].value == second.paramValues[1].value &&
8952
95.5k
                    first.paramValues[2].value == second.paramValues[0].value) {
8953
18
                    const std::string z_in(first.paramValues[1].value);
8954
18
                    const std::string z_out(first.paramValues[3].value);
8955
18
                    if (z_in != z_out) {
8956
18
                        iterCur->paramValues.clear();
8957
18
                        iterCur->paramValues.emplace_back(
8958
18
                            Step::KeyValue("z_in", z_in));
8959
18
                        iterCur->paramValues.emplace_back(
8960
18
                            Step::KeyValue("z_out", z_out));
8961
18
                        deletePrevIter();
8962
18
                    } else {
8963
0
                        deletePrevAndCurIter();
8964
0
                    }
8965
18
                    changeDone = true;
8966
18
                    break;
8967
18
                }
8968
95.5k
            }
8969
47.7k
            if (changeDone) {
8970
18
                continue;
8971
18
            }
8972
8973
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
8974
            // +step +proj=unitconvert +z_in=Z2 +z_out=Z3
8975
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
8976
            // +z_out=Z3
8977
47.7k
            if (prevStep.name == "unitconvert" &&
8978
47.7k
                curStep.name == "unitconvert" && !prevStep.inverted &&
8979
47.7k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
8980
47.7k
                curStep.paramValues.size() == 2 &&
8981
47.7k
                prevStep.paramValues[0].keyEquals("xy_in") &&
8982
47.7k
                prevStep.paramValues[1].keyEquals("z_in") &&
8983
47.7k
                prevStep.paramValues[2].keyEquals("xy_out") &&
8984
47.7k
                prevStep.paramValues[3].keyEquals("z_out") &&
8985
47.7k
                curStep.paramValues[0].keyEquals("z_in") &&
8986
47.7k
                curStep.paramValues[1].keyEquals("z_out") &&
8987
47.7k
                prevStep.paramValues[3].value == curStep.paramValues[0].value) {
8988
17
                const std::string xy_in(prevStep.paramValues[0].value);
8989
17
                const std::string z_in(prevStep.paramValues[1].value);
8990
17
                const std::string xy_out(prevStep.paramValues[2].value);
8991
17
                const std::string z_out(curStep.paramValues[1].value);
8992
8993
17
                iterCur->paramValues.clear();
8994
17
                iterCur->paramValues.emplace_back(
8995
17
                    Step::KeyValue("xy_in", xy_in));
8996
17
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
8997
17
                iterCur->paramValues.emplace_back(
8998
17
                    Step::KeyValue("xy_out", xy_out));
8999
17
                iterCur->paramValues.emplace_back(
9000
17
                    Step::KeyValue("z_out", z_out));
9001
9002
17
                deletePrevIter();
9003
17
                continue;
9004
17
            }
9005
9006
            // +step +proj=unitconvert +z_in=Z1 +z_out=Z2
9007
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X2 +z_out=Z3
9008
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
9009
            // +z_out=Z3
9010
47.7k
            if (prevStep.name == "unitconvert" &&
9011
47.7k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9012
47.7k
                !curStep.inverted && prevStep.paramValues.size() == 2 &&
9013
47.7k
                curStep.paramValues.size() == 4 &&
9014
47.7k
                prevStep.paramValues[0].keyEquals("z_in") &&
9015
47.7k
                prevStep.paramValues[1].keyEquals("z_out") &&
9016
47.7k
                curStep.paramValues[0].keyEquals("xy_in") &&
9017
47.7k
                curStep.paramValues[1].keyEquals("z_in") &&
9018
47.7k
                curStep.paramValues[2].keyEquals("xy_out") &&
9019
47.7k
                curStep.paramValues[3].keyEquals("z_out") &&
9020
47.7k
                prevStep.paramValues[1].value == curStep.paramValues[1].value) {
9021
8
                const std::string xy_in(curStep.paramValues[0].value);
9022
8
                const std::string z_in(prevStep.paramValues[0].value);
9023
8
                const std::string xy_out(curStep.paramValues[2].value);
9024
8
                const std::string z_out(curStep.paramValues[3].value);
9025
9026
8
                iterCur->paramValues.clear();
9027
8
                iterCur->paramValues.emplace_back(
9028
8
                    Step::KeyValue("xy_in", xy_in));
9029
8
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9030
8
                iterCur->paramValues.emplace_back(
9031
8
                    Step::KeyValue("xy_out", xy_out));
9032
8
                iterCur->paramValues.emplace_back(
9033
8
                    Step::KeyValue("z_out", z_out));
9034
9035
8
                deletePrevIter();
9036
8
                continue;
9037
8
            }
9038
9039
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9040
            // +step +proj=unitconvert +xy_in=X2 +xy_out=X3
9041
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3
9042
            // +z_out=Z2
9043
47.7k
            if (prevStep.name == "unitconvert" &&
9044
47.7k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9045
47.7k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9046
47.7k
                curStep.paramValues.size() == 2 &&
9047
47.7k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9048
47.7k
                prevStep.paramValues[1].keyEquals("z_in") &&
9049
47.7k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9050
47.7k
                prevStep.paramValues[3].keyEquals("z_out") &&
9051
47.7k
                curStep.paramValues[0].keyEquals("xy_in") &&
9052
47.7k
                curStep.paramValues[1].keyEquals("xy_out") &&
9053
47.7k
                prevStep.paramValues[2].value == curStep.paramValues[0].value) {
9054
50
                const std::string xy_in(prevStep.paramValues[0].value);
9055
50
                const std::string z_in(prevStep.paramValues[1].value);
9056
50
                const std::string xy_out(curStep.paramValues[1].value);
9057
50
                const std::string z_out(prevStep.paramValues[3].value);
9058
9059
50
                iterCur->paramValues.clear();
9060
50
                iterCur->paramValues.emplace_back(
9061
50
                    Step::KeyValue("xy_in", xy_in));
9062
50
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9063
50
                iterCur->paramValues.emplace_back(
9064
50
                    Step::KeyValue("xy_out", xy_out));
9065
50
                iterCur->paramValues.emplace_back(
9066
50
                    Step::KeyValue("z_out", z_out));
9067
9068
50
                deletePrevIter();
9069
50
                continue;
9070
50
            }
9071
9072
            // clang-format off
9073
            // A bit odd. Used to simplify geog3d_feet -> EPSG:6318+6360
9074
            // of https://github.com/OSGeo/PROJ/issues/3938
9075
            // where we get originally
9076
            // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=us-ft
9077
            // +step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m
9078
            // and want it simplified as:
9079
            // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=deg +z_out=us-ft
9080
            //
9081
            // More generally:
9082
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9083
            // +step +proj=unitconvert +xy_in=X2 +z_in=Z3 +xy_out=X3 +z_out=Z3
9084
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3 +z_out=Z2
9085
            // clang-format on
9086
47.6k
            if (prevStep.name == "unitconvert" &&
9087
47.6k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9088
47.6k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9089
47.6k
                curStep.paramValues.size() == 4 &&
9090
47.6k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9091
47.6k
                prevStep.paramValues[1].keyEquals("z_in") &&
9092
47.6k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9093
47.6k
                prevStep.paramValues[3].keyEquals("z_out") &&
9094
47.6k
                curStep.paramValues[0].keyEquals("xy_in") &&
9095
47.6k
                curStep.paramValues[1].keyEquals("z_in") &&
9096
47.6k
                curStep.paramValues[2].keyEquals("xy_out") &&
9097
47.6k
                curStep.paramValues[3].keyEquals("z_out") &&
9098
47.6k
                prevStep.paramValues[2].value == curStep.paramValues[0].value &&
9099
47.6k
                curStep.paramValues[1].value == curStep.paramValues[3].value) {
9100
19
                const std::string xy_in(prevStep.paramValues[0].value);
9101
19
                const std::string z_in(prevStep.paramValues[1].value);
9102
19
                const std::string xy_out(curStep.paramValues[2].value);
9103
19
                const std::string z_out(prevStep.paramValues[3].value);
9104
9105
19
                iterCur->paramValues.clear();
9106
19
                iterCur->paramValues.emplace_back(
9107
19
                    Step::KeyValue("xy_in", xy_in));
9108
19
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9109
19
                iterCur->paramValues.emplace_back(
9110
19
                    Step::KeyValue("xy_out", xy_out));
9111
19
                iterCur->paramValues.emplace_back(
9112
19
                    Step::KeyValue("z_out", z_out));
9113
9114
19
                deletePrevIter();
9115
19
                continue;
9116
19
            }
9117
9118
            // clang-format off
9119
            // Variant of above
9120
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z1
9121
            // +step +proj=unitconvert +xy_in=X2 +z_in=Z2 +xy_out=X3 +z_out=Z3
9122
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X3 +z_out=Z3
9123
            // clang-format on
9124
47.6k
            if (prevStep.name == "unitconvert" &&
9125
47.6k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9126
47.6k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9127
47.6k
                curStep.paramValues.size() == 4 &&
9128
47.6k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9129
47.6k
                prevStep.paramValues[1].keyEquals("z_in") &&
9130
47.6k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9131
47.6k
                prevStep.paramValues[3].keyEquals("z_out") &&
9132
47.6k
                curStep.paramValues[0].keyEquals("xy_in") &&
9133
47.6k
                curStep.paramValues[1].keyEquals("z_in") &&
9134
47.6k
                curStep.paramValues[2].keyEquals("xy_out") &&
9135
47.6k
                curStep.paramValues[3].keyEquals("z_out") &&
9136
47.6k
                prevStep.paramValues[1].value ==
9137
62
                    prevStep.paramValues[3].value &&
9138
47.6k
                curStep.paramValues[0].value == prevStep.paramValues[2].value) {
9139
58
                const std::string xy_in(prevStep.paramValues[0].value);
9140
58
                const std::string z_in(curStep.paramValues[1].value);
9141
58
                const std::string xy_out(curStep.paramValues[2].value);
9142
58
                const std::string z_out(curStep.paramValues[3].value);
9143
9144
58
                iterCur->paramValues.clear();
9145
58
                iterCur->paramValues.emplace_back(
9146
58
                    Step::KeyValue("xy_in", xy_in));
9147
58
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9148
58
                iterCur->paramValues.emplace_back(
9149
58
                    Step::KeyValue("xy_out", xy_out));
9150
58
                iterCur->paramValues.emplace_back(
9151
58
                    Step::KeyValue("z_out", z_out));
9152
9153
58
                deletePrevIter();
9154
58
                continue;
9155
58
            }
9156
9157
            // unitconvert (1), axisswap order=2,1, unitconvert(2)  ==>
9158
            // axisswap order=2,1, unitconvert (1), unitconvert(2) which
9159
            // will get further optimized by previous case
9160
47.6k
            if (std::next(iterCur) != steps.end() &&
9161
47.6k
                prevStep.name == "unitconvert" && curStep.name == "axisswap" &&
9162
47.6k
                curStepParamCount == 1 &&
9163
47.6k
                curStep.paramValues[0].equals("order", "2,1")) {
9164
74
                auto iterNext = std::next(iterCur);
9165
74
                auto &nextStep = *iterNext;
9166
74
                if (nextStep.name == "unitconvert") {
9167
19
                    std::swap(*iterPrev, *iterCur);
9168
19
                    ++iterCur;
9169
19
                    continue;
9170
19
                }
9171
74
            }
9172
9173
            // axisswap order=2,1 followed by itself is a no-op
9174
47.5k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9175
47.5k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9176
47.5k
                curStep.paramValues[0].equals("order", "2,1") &&
9177
47.5k
                prevStep.paramValues[0].equals("order", "2,1")) {
9178
30
                deletePrevAndCurIter();
9179
30
                continue;
9180
30
            }
9181
9182
            // axisswap order=2,-1 followed by axisswap order=-2,1 is a no-op
9183
47.5k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9184
47.5k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9185
47.5k
                !prevStep.inverted &&
9186
47.5k
                prevStep.paramValues[0].equals("order", "2,-1") &&
9187
47.5k
                !curStep.inverted &&
9188
47.5k
                curStep.paramValues[0].equals("order", "-2,1")) {
9189
14
                deletePrevAndCurIter();
9190
14
                continue;
9191
14
            }
9192
9193
            // axisswap order=2,-1 followed by axisswap order=1,-2 is
9194
            // equivalent to axisswap order=2,1
9195
47.5k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9196
47.5k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9197
47.5k
                !prevStep.inverted &&
9198
47.5k
                prevStep.paramValues[0].equals("order", "2,-1") &&
9199
47.5k
                !curStep.inverted &&
9200
47.5k
                curStep.paramValues[0].equals("order", "1,-2")) {
9201
17
                prevStep.inverted = false;
9202
17
                prevStep.paramValues[0] = Step::KeyValue("order", "2,1");
9203
                // Delete this iter
9204
17
                iterCur = steps.erase(iterCur);
9205
17
                continue;
9206
17
            }
9207
9208
            // axisswap order=2,1 followed by axisswap order=2,-1 is
9209
            // equivalent to axisswap order=1,-2
9210
            // Same for axisswap order=-2,1 followed by axisswap order=2,1
9211
47.5k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9212
47.5k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9213
47.5k
                ((prevStep.paramValues[0].equals("order", "2,1") &&
9214
310
                  !curStep.inverted &&
9215
310
                  curStep.paramValues[0].equals("order", "2,-1")) ||
9216
310
                 (prevStep.paramValues[0].equals("order", "-2,1") &&
9217
296
                  !prevStep.inverted &&
9218
296
                  curStep.paramValues[0].equals("order", "2,1")))) {
9219
9220
31
                prevStep.inverted = false;
9221
31
                prevStep.paramValues[0] = Step::KeyValue("order", "1,-2");
9222
                // Delete this iter
9223
31
                iterCur = steps.erase(iterCur);
9224
31
                continue;
9225
31
            }
9226
9227
            // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can
9228
            // suppress axisswap
9229
47.4k
            if (std::next(iterCur) != steps.end() &&
9230
47.4k
                prevStep.name == "axisswap" && curStep.name == "unitconvert" &&
9231
47.4k
                prevStepParamCount == 1 &&
9232
47.4k
                prevStep.paramValues[0].equals("order", "2,1")) {
9233
152
                auto iterNext = std::next(iterCur);
9234
152
                auto &nextStep = *iterNext;
9235
152
                if (nextStep.name == "axisswap" &&
9236
152
                    nextStep.paramValues.size() == 1 &&
9237
152
                    nextStep.paramValues[0].equals("order", "2,1")) {
9238
18
                    steps.erase(iterPrev);
9239
18
                    steps.erase(iterNext);
9240
                    // Coverity complains about invalid usage of iterCur
9241
                    // due to the above erase(iterNext). To the best of our
9242
                    // understanding, this is a false-positive.
9243
                    // coverity[use_iterator]
9244
18
                    if (iterCur != steps.begin())
9245
6
                        iterCur = std::prev(iterCur);
9246
18
                    if (iterCur == steps.begin())
9247
18
                        ++iterCur;
9248
18
                    continue;
9249
18
                }
9250
152
            }
9251
9252
            // for practical purposes WGS84 and GRS80 ellipsoids are
9253
            // equivalents (cartesian transform between both lead to differences
9254
            // of the order of 1e-14 deg..).
9255
            // No need to do a cart roundtrip for that...
9256
            // and actually IGNF uses the GRS80 definition for the WGS84 datum
9257
47.4k
            if (curStep.name == "cart" && prevStep.name == "cart" &&
9258
47.4k
                curStep.inverted == !prevStep.inverted &&
9259
47.4k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9260
47.4k
                ((curStep.paramValues[0].equals("ellps", "WGS84") &&
9261
50
                  prevStep.paramValues[0].equals("ellps", "GRS80")) ||
9262
50
                 (curStep.paramValues[0].equals("ellps", "GRS80") &&
9263
48
                  prevStep.paramValues[0].equals("ellps", "WGS84")))) {
9264
2
                deletePrevAndCurIter();
9265
2
                continue;
9266
2
            }
9267
9268
47.4k
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9269
47.4k
                !curStep.inverted && !prevStep.inverted &&
9270
47.4k
                curStepParamCount == 3 &&
9271
47.4k
                curStepParamCount == prevStepParamCount) {
9272
739
                std::map<std::string, double> leftParamsMap;
9273
739
                std::map<std::string, double> rightParamsMap;
9274
739
                try {
9275
2.20k
                    for (const auto &kv : prevStep.paramValues) {
9276
2.20k
                        leftParamsMap[kv.key] = c_locale_stod(kv.value);
9277
2.20k
                    }
9278
2.16k
                    for (const auto &kv : curStep.paramValues) {
9279
2.16k
                        rightParamsMap[kv.key] = c_locale_stod(kv.value);
9280
2.16k
                    }
9281
739
                } catch (const std::invalid_argument &) {
9282
83
                    break;
9283
83
                }
9284
656
                const std::string x("x");
9285
656
                const std::string y("y");
9286
656
                const std::string z("z");
9287
656
                if (leftParamsMap.find(x) != leftParamsMap.end() &&
9288
656
                    leftParamsMap.find(y) != leftParamsMap.end() &&
9289
656
                    leftParamsMap.find(z) != leftParamsMap.end() &&
9290
656
                    rightParamsMap.find(x) != rightParamsMap.end() &&
9291
656
                    rightParamsMap.find(y) != rightParamsMap.end() &&
9292
656
                    rightParamsMap.find(z) != rightParamsMap.end()) {
9293
9294
139
                    const double xSum = leftParamsMap[x] + rightParamsMap[x];
9295
139
                    const double ySum = leftParamsMap[y] + rightParamsMap[y];
9296
139
                    const double zSum = leftParamsMap[z] + rightParamsMap[z];
9297
139
                    if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) {
9298
5
                        deletePrevAndCurIter();
9299
134
                    } else {
9300
134
                        prevStep.paramValues[0] =
9301
134
                            Step::KeyValue("x", internal::toString(xSum));
9302
134
                        prevStep.paramValues[1] =
9303
134
                            Step::KeyValue("y", internal::toString(ySum));
9304
134
                        prevStep.paramValues[2] =
9305
134
                            Step::KeyValue("z", internal::toString(zSum));
9306
9307
                        // Delete this iter
9308
134
                        iterCur = steps.erase(iterCur);
9309
134
                    }
9310
139
                    continue;
9311
139
                }
9312
656
            }
9313
9314
            // Helmert followed by its inverse is a no-op
9315
47.2k
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9316
47.2k
                !curStep.inverted && !prevStep.inverted &&
9317
47.2k
                curStepParamCount == prevStepParamCount) {
9318
1.62k
                std::set<std::string> leftParamsSet;
9319
1.62k
                std::set<std::string> rightParamsSet;
9320
1.62k
                std::map<std::string, std::string> leftParamsMap;
9321
1.62k
                std::map<std::string, std::string> rightParamsMap;
9322
3.82k
                for (const auto &kv : prevStep.paramValues) {
9323
3.82k
                    leftParamsSet.insert(kv.key);
9324
3.82k
                    leftParamsMap[kv.key] = kv.value;
9325
3.82k
                }
9326
3.82k
                for (const auto &kv : curStep.paramValues) {
9327
3.82k
                    rightParamsSet.insert(kv.key);
9328
3.82k
                    rightParamsMap[kv.key] = kv.value;
9329
3.82k
                }
9330
1.62k
                if (leftParamsSet == rightParamsSet) {
9331
933
                    bool doErase = true;
9332
933
                    try {
9333
933
                        for (const auto &param : leftParamsSet) {
9334
434
                            if (param == "convention" || param == "t_epoch" ||
9335
434
                                param == "t_obs") {
9336
287
                                if (leftParamsMap[param] !=
9337
287
                                    rightParamsMap[param]) {
9338
250
                                    doErase = false;
9339
250
                                    break;
9340
250
                                }
9341
287
                            } else if (c_locale_stod(leftParamsMap[param]) !=
9342
147
                                       -c_locale_stod(rightParamsMap[param])) {
9343
94
                                doErase = false;
9344
94
                                break;
9345
94
                            }
9346
434
                        }
9347
933
                    } catch (const std::invalid_argument &) {
9348
23
                        break;
9349
23
                    }
9350
910
                    if (doErase) {
9351
566
                        deletePrevAndCurIter();
9352
566
                        continue;
9353
566
                    }
9354
910
                }
9355
1.62k
            }
9356
9357
            // The following should be optimized as a no-op
9358
            // +step +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=-0.35
9359
            // +rz=-0.736 +s=0 +convention=coordinate_frame
9360
            // +step +inv +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=0.35
9361
            // +rz=0.736 +s=0 +convention=position_vector
9362
46.6k
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9363
46.6k
                ((curStep.inverted && !prevStep.inverted) ||
9364
2.83k
                 (!curStep.inverted && prevStep.inverted)) &&
9365
46.6k
                curStepParamCount == prevStepParamCount) {
9366
720
                std::set<std::string> leftParamsSet;
9367
720
                std::set<std::string> rightParamsSet;
9368
720
                std::map<std::string, std::string> leftParamsMap;
9369
720
                std::map<std::string, std::string> rightParamsMap;
9370
12.9k
                for (const auto &kv : prevStep.paramValues) {
9371
12.9k
                    leftParamsSet.insert(kv.key);
9372
12.9k
                    leftParamsMap[kv.key] = kv.value;
9373
12.9k
                }
9374
12.9k
                for (const auto &kv : curStep.paramValues) {
9375
12.9k
                    rightParamsSet.insert(kv.key);
9376
12.9k
                    rightParamsMap[kv.key] = kv.value;
9377
12.9k
                }
9378
720
                if (leftParamsSet == rightParamsSet) {
9379
319
                    bool doErase = true;
9380
319
                    try {
9381
3.66k
                        for (const auto &param : leftParamsSet) {
9382
3.66k
                            if (param == "convention") {
9383
                                // Convention must be different
9384
98
                                if (leftParamsMap[param] ==
9385
98
                                    rightParamsMap[param]) {
9386
30
                                    doErase = false;
9387
30
                                    break;
9388
30
                                }
9389
3.56k
                            } else if (param == "rx" || param == "ry" ||
9390
3.56k
                                       param == "rz" || param == "drx" ||
9391
3.56k
                                       param == "dry" || param == "drz") {
9392
                                // Rotational parameters should have opposite
9393
                                // value
9394
0
                                if (c_locale_stod(leftParamsMap[param]) !=
9395
0
                                    -c_locale_stod(rightParamsMap[param])) {
9396
0
                                    doErase = false;
9397
0
                                    break;
9398
0
                                }
9399
3.56k
                            } else {
9400
                                // Non rotational parameters should have the
9401
                                // same value
9402
3.56k
                                if (leftParamsMap[param] !=
9403
3.56k
                                    rightParamsMap[param]) {
9404
266
                                    doErase = false;
9405
266
                                    break;
9406
266
                                }
9407
3.56k
                            }
9408
3.66k
                        }
9409
319
                    } catch (const std::invalid_argument &) {
9410
0
                        break;
9411
0
                    }
9412
319
                    if (doErase) {
9413
23
                        deletePrevAndCurIter();
9414
23
                        continue;
9415
23
                    }
9416
319
                }
9417
720
            }
9418
9419
            // Optimize patterns like Krovak (South West) to Krovak East North
9420
            // (also applies to Modified Krovak)
9421
            //   +step +inv +proj=krovak +axis=swu +lat_0=49.5
9422
            //   +lon_0=24.8333333333333
9423
            //     +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel
9424
            //   +step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333
9425
            //     +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel
9426
            // as:
9427
            //   +step +proj=axisswap +order=-2,-1
9428
            // Also applies for the symmetrical case where +axis=swu is on the
9429
            // second step.
9430
46.6k
            if (curStep.inverted != prevStep.inverted &&
9431
46.6k
                curStep.name == prevStep.name &&
9432
46.6k
                ((curStepParamCount + 1 == prevStepParamCount &&
9433
17.8k
                  prevStep.paramValues[0].equals("axis", "swu")) ||
9434
17.8k
                 (prevStepParamCount + 1 == curStepParamCount &&
9435
17.8k
                  curStep.paramValues[0].equals("axis", "swu")))) {
9436
245
                const auto &swStep = (curStepParamCount < prevStepParamCount)
9437
245
                                         ? prevStep
9438
245
                                         : curStep;
9439
245
                const auto &enStep = (curStepParamCount < prevStepParamCount)
9440
245
                                         ? curStep
9441
245
                                         : prevStep;
9442
                // Check if all remaining parameters (except leading axis=swu
9443
                // in swStep) are identical.
9444
245
                bool allSame = true;
9445
245
                for (size_t j = 0;
9446
245
                     j < std::min(curStepParamCount, prevStepParamCount); j++) {
9447
53
                    if (enStep.paramValues[j] != swStep.paramValues[j + 1]) {
9448
53
                        allSame = false;
9449
53
                        break;
9450
53
                    }
9451
53
                }
9452
245
                if (allSame) {
9453
192
                    iterCur->inverted = false;
9454
192
                    iterCur->name = "axisswap";
9455
192
                    iterCur->paramValues.clear();
9456
192
                    iterCur->paramValues.emplace_back(
9457
192
                        Step::KeyValue("order", "-2,-1"));
9458
9459
192
                    deletePrevIter();
9460
192
                    continue;
9461
192
                }
9462
245
            }
9463
9464
            // detect a step and its inverse
9465
46.4k
            if (curStep.inverted != prevStep.inverted &&
9466
46.4k
                curStep.name == prevStep.name &&
9467
46.4k
                curStepParamCount == prevStepParamCount) {
9468
12.2k
                bool allSame = true;
9469
19.3k
                for (size_t j = 0; j < curStepParamCount; j++) {
9470
9.23k
                    if (curStep.paramValues[j] != prevStep.paramValues[j]) {
9471
2.10k
                        allSame = false;
9472
2.10k
                        break;
9473
2.10k
                    }
9474
9.23k
                }
9475
12.2k
                if (allSame) {
9476
10.1k
                    deletePrevAndCurIter();
9477
10.1k
                    continue;
9478
10.1k
                }
9479
12.2k
            }
9480
9481
36.2k
            ++iterCur;
9482
36.2k
        }
9483
19.5k
    }
9484
9485
19.5k
    {
9486
19.5k
        auto iterCur = steps.begin();
9487
19.5k
        if (iterCur != steps.end()) {
9488
19.1k
            ++iterCur;
9489
19.1k
        }
9490
36.4k
        while (iterCur != steps.end()) {
9491
9492
16.8k
            assert(iterCur != steps.begin());
9493
16.8k
            auto iterPrev = std::prev(iterCur);
9494
16.8k
            auto &prevStep = *iterPrev;
9495
16.8k
            auto &curStep = *iterCur;
9496
9497
16.8k
            const auto curStepParamCount = curStep.paramValues.size();
9498
16.8k
            const auto prevStepParamCount = prevStep.paramValues.size();
9499
9500
            // +step +proj=hgridshift +grids=grid_A
9501
            // +step +proj=vgridshift [...] <== curStep
9502
            // +step +inv +proj=hgridshift +grids=grid_A
9503
            // ==>
9504
            // +step +proj=push +v_1 +v_2
9505
            // +step +proj=hgridshift +grids=grid_A +omit_inv
9506
            // +step +proj=vgridshift [...]
9507
            // +step +inv +proj=hgridshift +grids=grid_A +omit_fwd
9508
            // +step +proj=pop +v_1 +v_2
9509
16.8k
            if (std::next(iterCur) != steps.end() &&
9510
16.8k
                prevStep.name == "hgridshift" && prevStepParamCount == 1 &&
9511
16.8k
                curStep.name == "vgridshift") {
9512
184
                auto iterNext = std::next(iterCur);
9513
184
                auto &nextStep = *iterNext;
9514
184
                if (nextStep.name == "hgridshift" &&
9515
184
                    nextStep.inverted != prevStep.inverted &&
9516
184
                    nextStep.paramValues.size() == 1 &&
9517
184
                    prevStep.paramValues[0] == nextStep.paramValues[0]) {
9518
50
                    Step pushStep;
9519
50
                    pushStep.name = "push";
9520
50
                    pushStep.paramValues.emplace_back("v_1");
9521
50
                    pushStep.paramValues.emplace_back("v_2");
9522
50
                    steps.insert(iterPrev, pushStep);
9523
9524
50
                    prevStep.paramValues.emplace_back("omit_inv");
9525
9526
50
                    nextStep.paramValues.emplace_back("omit_fwd");
9527
9528
50
                    Step popStep;
9529
50
                    popStep.name = "pop";
9530
50
                    popStep.paramValues.emplace_back("v_1");
9531
50
                    popStep.paramValues.emplace_back("v_2");
9532
50
                    steps.insert(std::next(iterNext), popStep);
9533
9534
50
                    continue;
9535
50
                }
9536
184
            }
9537
9538
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9539
            // +step +proj=axisswap +order=2,1
9540
            // +step +proj=push +v_1 +v_2
9541
            // +step +proj=axisswap +order=2,1
9542
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9543
            // +step +proj=vgridshift ...
9544
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9545
            // +step +proj=axisswap +order=2,1
9546
            // +step +proj=pop +v_1 +v_2
9547
            // ==>
9548
            // +step +proj=vgridshift ...
9549
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9550
            // +step +proj=axisswap +order=2,1
9551
16.7k
            if (prevStep.name == "unitconvert" && prevStepParamCount == 2 &&
9552
16.7k
                prevStep.paramValues[0].equals("xy_in", "rad") &&
9553
16.7k
                prevStep.paramValues[1].equals("xy_out", "deg") &&
9554
16.7k
                curStep.name == "axisswap" && curStepParamCount == 1 &&
9555
16.7k
                curStep.paramValues[0].equals("order", "2,1")) {
9556
68
                auto iterNext = std::next(iterCur);
9557
68
                bool ok = false;
9558
68
                if (iterNext != steps.end()) {
9559
0
                    auto &nextStep = *iterNext;
9560
0
                    if (nextStep.name == "push" &&
9561
0
                        nextStep.paramValues.size() == 2 &&
9562
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9563
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9564
0
                        ok = true;
9565
0
                        iterNext = std::next(iterNext);
9566
0
                    }
9567
0
                }
9568
68
                ok &= iterNext != steps.end();
9569
68
                if (ok) {
9570
0
                    ok = false;
9571
0
                    auto &nextStep = *iterNext;
9572
0
                    if (nextStep.name == "axisswap" &&
9573
0
                        nextStep.paramValues.size() == 1 &&
9574
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9575
0
                        ok = true;
9576
0
                        iterNext = std::next(iterNext);
9577
0
                    }
9578
0
                }
9579
68
                ok &= iterNext != steps.end();
9580
68
                if (ok) {
9581
0
                    ok = false;
9582
0
                    auto &nextStep = *iterNext;
9583
0
                    if (nextStep.name == "unitconvert" &&
9584
0
                        nextStep.paramValues.size() == 2 &&
9585
0
                        nextStep.paramValues[0].equals("xy_in", "deg") &&
9586
0
                        nextStep.paramValues[1].equals("xy_out", "rad")) {
9587
0
                        ok = true;
9588
0
                        iterNext = std::next(iterNext);
9589
0
                    }
9590
0
                }
9591
68
                auto iterVgridshift = iterNext;
9592
68
                ok &= iterNext != steps.end();
9593
68
                if (ok) {
9594
0
                    ok = false;
9595
0
                    auto &nextStep = *iterNext;
9596
0
                    if (nextStep.name == "vgridshift") {
9597
0
                        ok = true;
9598
0
                        iterNext = std::next(iterNext);
9599
0
                    }
9600
0
                }
9601
68
                ok &= iterNext != steps.end();
9602
68
                if (ok) {
9603
0
                    ok = false;
9604
0
                    auto &nextStep = *iterNext;
9605
0
                    if (nextStep.name == "unitconvert" &&
9606
0
                        nextStep.paramValues.size() == 2 &&
9607
0
                        nextStep.paramValues[0].equals("xy_in", "rad") &&
9608
0
                        nextStep.paramValues[1].equals("xy_out", "deg")) {
9609
0
                        ok = true;
9610
0
                        iterNext = std::next(iterNext);
9611
0
                    }
9612
0
                }
9613
68
                ok &= iterNext != steps.end();
9614
68
                if (ok) {
9615
0
                    ok = false;
9616
0
                    auto &nextStep = *iterNext;
9617
0
                    if (nextStep.name == "axisswap" &&
9618
0
                        nextStep.paramValues.size() == 1 &&
9619
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9620
0
                        ok = true;
9621
0
                        iterNext = std::next(iterNext);
9622
0
                    }
9623
0
                }
9624
68
                ok &= iterNext != steps.end();
9625
68
                if (ok) {
9626
0
                    ok = false;
9627
0
                    auto &nextStep = *iterNext;
9628
0
                    if (nextStep.name == "pop" &&
9629
0
                        nextStep.paramValues.size() == 2 &&
9630
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9631
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9632
0
                        ok = true;
9633
                        // iterNext = std::next(iterNext);
9634
0
                    }
9635
0
                }
9636
68
                if (ok) {
9637
0
                    steps.erase(iterPrev, iterVgridshift);
9638
0
                    steps.erase(iterNext, std::next(iterNext));
9639
0
                    iterPrev = std::prev(iterVgridshift);
9640
0
                    iterCur = iterVgridshift;
9641
0
                    continue;
9642
0
                }
9643
68
            }
9644
9645
            // +step +proj=axisswap +order=2,1
9646
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9647
            // +step +proj=vgridshift ...
9648
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9649
            // +step +proj=axisswap +order=2,1
9650
            // +step +proj=push +v_1 +v_2
9651
            // +step +proj=axisswap +order=2,1
9652
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9653
            // ==>
9654
            // +step +proj=push +v_1 +v_2
9655
            // +step +proj=axisswap +order=2,1
9656
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9657
            // +step +proj=vgridshift ...
9658
9659
16.7k
            if (prevStep.name == "axisswap" && prevStepParamCount == 1 &&
9660
16.7k
                prevStep.paramValues[0].equals("order", "2,1") &&
9661
16.7k
                curStep.name == "unitconvert" && curStepParamCount == 2 &&
9662
16.7k
                !curStep.inverted &&
9663
16.7k
                curStep.paramValues[0].equals("xy_in", "deg") &&
9664
16.7k
                curStep.paramValues[1].equals("xy_out", "rad")) {
9665
56
                auto iterNext = std::next(iterCur);
9666
56
                bool ok = false;
9667
56
                auto iterVgridshift = iterNext;
9668
56
                if (iterNext != steps.end()) {
9669
56
                    auto &nextStep = *iterNext;
9670
56
                    if (nextStep.name == "vgridshift") {
9671
0
                        ok = true;
9672
0
                        iterNext = std::next(iterNext);
9673
0
                    }
9674
56
                }
9675
56
                ok &= iterNext != steps.end();
9676
56
                if (ok) {
9677
0
                    ok = false;
9678
0
                    auto &nextStep = *iterNext;
9679
0
                    if (nextStep.name == "unitconvert" && !nextStep.inverted &&
9680
0
                        nextStep.paramValues.size() == 2 &&
9681
0
                        nextStep.paramValues[0].equals("xy_in", "rad") &&
9682
0
                        nextStep.paramValues[1].equals("xy_out", "deg")) {
9683
0
                        ok = true;
9684
0
                        iterNext = std::next(iterNext);
9685
0
                    }
9686
0
                }
9687
56
                ok &= iterNext != steps.end();
9688
56
                if (ok) {
9689
0
                    ok = false;
9690
0
                    auto &nextStep = *iterNext;
9691
0
                    if (nextStep.name == "axisswap" &&
9692
0
                        nextStep.paramValues.size() == 1 &&
9693
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9694
0
                        ok = true;
9695
0
                        iterNext = std::next(iterNext);
9696
0
                    }
9697
0
                }
9698
56
                auto iterPush = iterNext;
9699
56
                ok &= iterNext != steps.end();
9700
56
                if (ok) {
9701
0
                    ok = false;
9702
0
                    auto &nextStep = *iterNext;
9703
0
                    if (nextStep.name == "push" &&
9704
0
                        nextStep.paramValues.size() == 2 &&
9705
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9706
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9707
0
                        ok = true;
9708
0
                        iterNext = std::next(iterNext);
9709
0
                    }
9710
0
                }
9711
56
                ok &= iterNext != steps.end();
9712
56
                if (ok) {
9713
0
                    ok = false;
9714
0
                    auto &nextStep = *iterNext;
9715
0
                    if (nextStep.name == "axisswap" &&
9716
0
                        nextStep.paramValues.size() == 1 &&
9717
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9718
0
                        ok = true;
9719
0
                        iterNext = std::next(iterNext);
9720
0
                    }
9721
0
                }
9722
56
                ok &= iterNext != steps.end();
9723
56
                if (ok) {
9724
0
                    ok = false;
9725
0
                    auto &nextStep = *iterNext;
9726
0
                    if (nextStep.name == "unitconvert" &&
9727
0
                        nextStep.paramValues.size() == 2 &&
9728
0
                        !nextStep.inverted &&
9729
0
                        nextStep.paramValues[0].equals("xy_in", "deg") &&
9730
0
                        nextStep.paramValues[1].equals("xy_out", "rad")) {
9731
0
                        ok = true;
9732
                        // iterNext = std::next(iterNext);
9733
0
                    }
9734
0
                }
9735
9736
56
                if (ok) {
9737
0
                    Step stepVgridshift(*iterVgridshift);
9738
0
                    steps.erase(iterPrev, iterPush);
9739
0
                    steps.insert(std::next(iterNext),
9740
0
                                 std::move(stepVgridshift));
9741
0
                    iterPrev = iterPush;
9742
0
                    iterCur = std::next(iterPush);
9743
0
                    continue;
9744
0
                }
9745
56
            }
9746
9747
16.7k
            ++iterCur;
9748
16.7k
        }
9749
19.5k
    }
9750
9751
19.5k
    {
9752
19.5k
        auto iterCur = steps.begin();
9753
19.5k
        if (iterCur != steps.end()) {
9754
19.1k
            ++iterCur;
9755
19.1k
        }
9756
37.0k
        while (iterCur != steps.end()) {
9757
9758
17.4k
            assert(iterCur != steps.begin());
9759
17.4k
            auto iterPrev = std::prev(iterCur);
9760
17.4k
            auto &prevStep = *iterPrev;
9761
17.4k
            auto &curStep = *iterCur;
9762
9763
17.4k
            const auto curStepParamCount = curStep.paramValues.size();
9764
17.4k
            const auto prevStepParamCount = prevStep.paramValues.size();
9765
9766
17.4k
            const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() {
9767
622
                iterCur = steps.erase(iterPrev, std::next(iterCur));
9768
622
                if (iterCur != steps.begin())
9769
622
                    iterCur = std::prev(iterCur);
9770
622
                if (iterCur == steps.begin() && iterCur != steps.end())
9771
0
                    ++iterCur;
9772
622
            };
9773
9774
            // axisswap order=2,1 followed by itself is a no-op
9775
17.4k
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9776
17.4k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9777
17.4k
                curStep.paramValues[0].equals("order", "2,1") &&
9778
17.4k
                prevStep.paramValues[0].equals("order", "2,1")) {
9779
0
                deletePrevAndCurIter();
9780
0
                continue;
9781
0
            }
9782
9783
            // detect a step and its inverse
9784
17.4k
            if (curStep.inverted != prevStep.inverted &&
9785
17.4k
                curStep.name == prevStep.name &&
9786
17.4k
                curStepParamCount == prevStepParamCount) {
9787
1.77k
                bool allSame = true;
9788
5.92k
                for (size_t j = 0; j < curStepParamCount; j++) {
9789
5.30k
                    if (curStep.paramValues[j] != prevStep.paramValues[j]) {
9790
1.14k
                        allSame = false;
9791
1.14k
                        break;
9792
1.14k
                    }
9793
5.30k
                }
9794
1.77k
                if (allSame) {
9795
622
                    deletePrevAndCurIter();
9796
622
                    continue;
9797
622
                }
9798
1.77k
            }
9799
9800
16.8k
            ++iterCur;
9801
16.8k
        }
9802
19.5k
    }
9803
9804
19.5k
    if (steps.size() > 1 ||
9805
19.5k
        (steps.size() == 1 &&
9806
16.7k
         (steps.front().inverted || steps.front().hasKey("omit_inv") ||
9807
16.3k
          steps.front().hasKey("omit_fwd") ||
9808
16.3k
          !d->globalParamValues_.empty()))) {
9809
4.74k
        d->appendToResult("+proj=pipeline");
9810
9811
18.7k
        for (const auto &paramValue : d->globalParamValues_) {
9812
18.7k
            d->appendToResult("+");
9813
18.7k
            d->result_ += paramValue.key;
9814
18.7k
            if (!paramValue.value.empty()) {
9815
5.24k
                d->result_ += '=';
9816
5.24k
                d->result_ +=
9817
5.24k
                    pj_double_quote_string_param_if_needed(paramValue.value);
9818
5.24k
            }
9819
18.7k
        }
9820
9821
4.74k
        if (d->multiLine_) {
9822
0
            d->indentLevel_++;
9823
0
        }
9824
4.74k
    }
9825
9826
34.7k
    for (const auto &step : steps) {
9827
34.7k
        std::string curLine;
9828
34.7k
        if (!d->result_.empty()) {
9829
20.3k
            if (d->multiLine_) {
9830
0
                curLine = std::string(static_cast<size_t>(d->indentLevel_) *
9831
0
                                          d->indentWidth_,
9832
0
                                      ' ');
9833
0
                curLine += "+step";
9834
20.3k
            } else {
9835
20.3k
                curLine = " +step";
9836
20.3k
            }
9837
20.3k
        }
9838
34.7k
        if (step.inverted) {
9839
8.19k
            curLine += " +inv";
9840
8.19k
        }
9841
34.7k
        if (!step.name.empty()) {
9842
26.3k
            if (!curLine.empty())
9843
12.0k
                curLine += ' ';
9844
26.3k
            curLine += step.isInit ? "+init=" : "+proj=";
9845
26.3k
            curLine += step.name;
9846
26.3k
        }
9847
166k
        for (const auto &paramValue : step.paramValues) {
9848
166k
            std::string newKV = "+";
9849
166k
            newKV += paramValue.key;
9850
166k
            if (!paramValue.value.empty()) {
9851
97.3k
                newKV += '=';
9852
97.3k
                newKV +=
9853
97.3k
                    pj_double_quote_string_param_if_needed(paramValue.value);
9854
97.3k
            }
9855
166k
            if (d->maxLineLength_ > 0 && d->multiLine_ &&
9856
166k
                curLine.size() + newKV.size() >
9857
0
                    static_cast<size_t>(d->maxLineLength_)) {
9858
0
                if (!d->result_.empty())
9859
0
                    d->result_ += '\n';
9860
0
                d->result_ += curLine;
9861
0
                curLine = std::string(static_cast<size_t>(d->indentLevel_) *
9862
0
                                              d->indentWidth_ +
9863
0
                                          strlen("+step "),
9864
0
                                      ' ');
9865
166k
            } else {
9866
166k
                if (!curLine.empty())
9867
166k
                    curLine += ' ';
9868
166k
            }
9869
166k
            curLine += newKV;
9870
166k
        }
9871
34.7k
        if (d->multiLine_ && !d->result_.empty())
9872
0
            d->result_ += '\n';
9873
34.7k
        d->result_ += curLine;
9874
34.7k
    }
9875
9876
19.5k
    if (d->result_.empty()) {
9877
429
        d->appendToResult("+proj=noop");
9878
429
    }
9879
9880
19.5k
    return d->result_;
9881
19.5k
}
9882
9883
// ---------------------------------------------------------------------------
9884
9885
//! @cond Doxygen_Suppress
9886
9887
5.22k
PROJStringFormatter::Convention PROJStringFormatter::convention() const {
9888
5.22k
    return d->convention_;
9889
5.22k
}
9890
9891
// ---------------------------------------------------------------------------
9892
9893
// Return the number of steps in the pipeline.
9894
// Note: this value will change after calling toString() that will run
9895
// optimizations.
9896
0
size_t PROJStringFormatter::getStepCount() const { return d->steps_.size(); }
9897
9898
// ---------------------------------------------------------------------------
9899
9900
1.27k
bool PROJStringFormatter::getUseApproxTMerc() const {
9901
1.27k
    return d->useApproxTMerc_;
9902
1.27k
}
9903
9904
// ---------------------------------------------------------------------------
9905
9906
1.89k
void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) {
9907
1.89k
    d->coordOperationOptimizations_ = enable;
9908
1.89k
}
9909
9910
// ---------------------------------------------------------------------------
9911
9912
23.8k
void PROJStringFormatter::Private::appendToResult(const char *str) {
9913
23.8k
    if (!result_.empty()) {
9914
18.7k
        result_ += ' ';
9915
18.7k
    }
9916
23.8k
    result_ += str;
9917
23.8k
}
9918
9919
// ---------------------------------------------------------------------------
9920
9921
static void
9922
PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
9923
                       std::vector<Step::KeyValue> &globalParamValues,
9924
76.1k
                       std::string &title) {
9925
76.1k
    std::vector<std::string> tokens;
9926
9927
76.1k
    bool hasProj = false;
9928
76.1k
    bool hasInit = false;
9929
76.1k
    bool hasPipeline = false;
9930
9931
76.1k
    std::string projStringModified(projString);
9932
9933
    // Special case for "+title=several words +foo=bar"
9934
76.1k
    if (starts_with(projStringModified, "+title=") &&
9935
76.1k
        projStringModified.size() > 7 && projStringModified[7] != '"') {
9936
10
        const auto plusPos = projStringModified.find(" +", 1);
9937
10
        const auto spacePos = projStringModified.find(' ');
9938
10
        if (plusPos != std::string::npos && spacePos != std::string::npos &&
9939
10
            spacePos < plusPos) {
9940
0
            std::string tmp("+title=");
9941
0
            tmp += pj_double_quote_string_param_if_needed(
9942
0
                projStringModified.substr(7, plusPos - 7));
9943
0
            tmp += projStringModified.substr(plusPos);
9944
0
            projStringModified = std::move(tmp);
9945
0
        }
9946
10
    }
9947
9948
76.1k
    size_t argc = pj_trim_argc(&projStringModified[0]);
9949
76.1k
    char **argv = pj_trim_argv(argc, &projStringModified[0]);
9950
1.09M
    for (size_t i = 0; i < argc; i++) {
9951
1.01M
        std::string token(argv[i]);
9952
1.01M
        if (!hasPipeline && token == "proj=pipeline") {
9953
13.3k
            hasPipeline = true;
9954
1.00M
        } else if (!hasProj && starts_with(token, "proj=")) {
9955
64.4k
            hasProj = true;
9956
936k
        } else if (!hasInit && starts_with(token, "init=")) {
9957
14.3k
            hasInit = true;
9958
14.3k
        }
9959
1.01M
        tokens.emplace_back(token);
9960
1.01M
    }
9961
76.1k
    free(argv);
9962
9963
76.1k
    if (!hasPipeline) {
9964
62.8k
        if (hasProj || hasInit) {
9965
62.5k
            steps.push_back(Step());
9966
62.5k
        }
9967
9968
507k
        for (auto &word : tokens) {
9969
507k
            if (starts_with(word, "proj=") && !hasInit &&
9970
507k
                steps.back().name.empty()) {
9971
50.4k
                assert(hasProj);
9972
50.4k
                auto stepName = word.substr(strlen("proj="));
9973
50.4k
                steps.back().name = std::move(stepName);
9974
457k
            } else if (starts_with(word, "init=")) {
9975
13.7k
                assert(hasInit);
9976
13.7k
                auto initName = word.substr(strlen("init="));
9977
13.7k
                steps.back().name = std::move(initName);
9978
13.7k
                steps.back().isInit = true;
9979
443k
            } else if (word == "inv") {
9980
7.26k
                if (!steps.empty()) {
9981
7.20k
                    steps.back().inverted = true;
9982
7.20k
                }
9983
436k
            } else if (starts_with(word, "title=")) {
9984
2.31k
                title = word.substr(strlen("title="));
9985
433k
            } else if (word != "step") {
9986
425k
                const auto pos = word.find('=');
9987
425k
                const auto key = word.substr(0, pos);
9988
9989
425k
                Step::KeyValue pair(
9990
425k
                    (pos != std::string::npos)
9991
425k
                        ? Step::KeyValue(key, word.substr(pos + 1))
9992
425k
                        : Step::KeyValue(key));
9993
425k
                if (steps.empty()) {
9994
2.97k
                    globalParamValues.push_back(std::move(pair));
9995
422k
                } else {
9996
422k
                    steps.back().paramValues.push_back(std::move(pair));
9997
422k
                }
9998
425k
            }
9999
507k
        }
10000
62.8k
        return;
10001
62.8k
    }
10002
10003
13.3k
    bool inPipeline = false;
10004
13.3k
    bool invGlobal = false;
10005
506k
    for (auto &word : tokens) {
10006
506k
        if (word == "proj=pipeline") {
10007
13.3k
            if (inPipeline) {
10008
10
                throw ParsingException("nested pipeline not supported");
10009
10
            }
10010
13.2k
            inPipeline = true;
10011
493k
        } else if (word == "step") {
10012
135k
            if (!inPipeline) {
10013
7
                throw ParsingException("+step found outside pipeline");
10014
7
            }
10015
135k
            steps.push_back(Step());
10016
357k
        } else if (word == "inv") {
10017
58.8k
            if (steps.empty()) {
10018
3.16k
                invGlobal = true;
10019
55.7k
            } else {
10020
55.7k
                steps.back().inverted = true;
10021
55.7k
            }
10022
298k
        } else if (inPipeline && !steps.empty() && starts_with(word, "proj=") &&
10023
298k
                   steps.back().name.empty()) {
10024
35.1k
            auto stepName = word.substr(strlen("proj="));
10025
35.1k
            steps.back().name = std::move(stepName);
10026
263k
        } else if (inPipeline && !steps.empty() && starts_with(word, "init=") &&
10027
263k
                   steps.back().name.empty()) {
10028
5.52k
            auto initName = word.substr(strlen("init="));
10029
5.52k
            steps.back().name = std::move(initName);
10030
5.52k
            steps.back().isInit = true;
10031
258k
        } else if (!inPipeline && starts_with(word, "title=")) {
10032
2.09k
            title = word.substr(strlen("title="));
10033
255k
        } else {
10034
255k
            const auto pos = word.find('=');
10035
255k
            auto key = word.substr(0, pos);
10036
255k
            Step::KeyValue pair((pos != std::string::npos)
10037
255k
                                    ? Step::KeyValue(key, word.substr(pos + 1))
10038
255k
                                    : Step::KeyValue(key));
10039
255k
            if (steps.empty()) {
10040
85.1k
                globalParamValues.emplace_back(std::move(pair));
10041
170k
            } else {
10042
170k
                steps.back().paramValues.emplace_back(std::move(pair));
10043
170k
            }
10044
255k
        }
10045
506k
    }
10046
13.2k
    if (invGlobal) {
10047
52.7k
        for (auto &step : steps) {
10048
52.7k
            step.inverted = !step.inverted;
10049
52.7k
        }
10050
2.97k
        std::reverse(steps.begin(), steps.end());
10051
2.97k
    }
10052
13.2k
}
10053
10054
// ---------------------------------------------------------------------------
10055
10056
void PROJStringFormatter::ingestPROJString(
10057
    const std::string &str) // throw ParsingException
10058
21.3k
{
10059
21.3k
    std::vector<Step> steps;
10060
21.3k
    std::string title;
10061
21.3k
    PROJStringSyntaxParser(str, steps, d->globalParamValues_, title);
10062
21.3k
    d->steps_.insert(d->steps_.end(), steps.begin(), steps.end());
10063
21.3k
}
10064
10065
// ---------------------------------------------------------------------------
10066
10067
18.2k
void PROJStringFormatter::setCRSExport(bool b) { d->crsExport_ = b; }
10068
10069
// ---------------------------------------------------------------------------
10070
10071
55.7k
bool PROJStringFormatter::getCRSExport() const { return d->crsExport_; }
10072
10073
// ---------------------------------------------------------------------------
10074
10075
100
void PROJStringFormatter::startInversion() {
10076
100
    PROJStringFormatter::Private::InversionStackElt elt;
10077
100
    elt.startIter = d->steps_.end();
10078
100
    if (elt.startIter != d->steps_.begin()) {
10079
25
        elt.iterValid = true;
10080
25
        --elt.startIter; // point to the last valid element
10081
75
    } else {
10082
75
        elt.iterValid = false;
10083
75
    }
10084
100
    elt.currentInversionState =
10085
100
        !d->inversionStack_.back().currentInversionState;
10086
100
    d->inversionStack_.push_back(elt);
10087
100
}
10088
10089
// ---------------------------------------------------------------------------
10090
10091
100
void PROJStringFormatter::stopInversion() {
10092
100
    assert(!d->inversionStack_.empty());
10093
100
    auto startIter = d->inversionStack_.back().startIter;
10094
100
    if (!d->inversionStack_.back().iterValid) {
10095
75
        startIter = d->steps_.begin();
10096
75
    } else {
10097
25
        ++startIter; // advance after the last valid element we marked above
10098
25
    }
10099
    // Invert the inversion status of the steps between the start point and
10100
    // the current end of steps
10101
282
    for (auto iter = startIter; iter != d->steps_.end(); ++iter) {
10102
182
        iter->inverted = !iter->inverted;
10103
294
        for (auto &paramValue : iter->paramValues) {
10104
294
            if (paramValue.key == "omit_fwd")
10105
0
                paramValue.key = "omit_inv";
10106
294
            else if (paramValue.key == "omit_inv")
10107
0
                paramValue.key = "omit_fwd";
10108
294
        }
10109
182
    }
10110
    // And reverse the order of steps in that range as well.
10111
100
    std::reverse(startIter, d->steps_.end());
10112
100
    d->inversionStack_.pop_back();
10113
100
}
10114
10115
// ---------------------------------------------------------------------------
10116
10117
0
bool PROJStringFormatter::isInverted() const {
10118
0
    return d->inversionStack_.back().currentInversionState;
10119
0
}
10120
10121
// ---------------------------------------------------------------------------
10122
10123
8.96k
void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); }
10124
10125
// ---------------------------------------------------------------------------
10126
10127
7.90k
void PROJStringFormatter::addStep(const char *stepName) {
10128
7.90k
    d->addStep();
10129
7.90k
    d->steps_.back().name.assign(stepName);
10130
7.90k
}
10131
10132
// ---------------------------------------------------------------------------
10133
10134
976
void PROJStringFormatter::addStep(const std::string &stepName) {
10135
976
    d->addStep();
10136
976
    d->steps_.back().name = stepName;
10137
976
}
10138
10139
// ---------------------------------------------------------------------------
10140
10141
12
void PROJStringFormatter::setCurrentStepInverted(bool inverted) {
10142
12
    assert(!d->steps_.empty());
10143
12
    d->steps_.back().inverted = inverted;
10144
12
}
10145
10146
// ---------------------------------------------------------------------------
10147
10148
16.7k
bool PROJStringFormatter::hasParam(const char *paramName) const {
10149
16.7k
    if (!d->steps_.empty()) {
10150
104k
        for (const auto &paramValue : d->steps_.back().paramValues) {
10151
104k
            if (paramValue.keyEquals(paramName)) {
10152
714
                return true;
10153
714
            }
10154
104k
        }
10155
16.7k
    }
10156
16.0k
    return false;
10157
16.7k
}
10158
10159
// ---------------------------------------------------------------------------
10160
10161
1.49k
void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; }
10162
10163
// ---------------------------------------------------------------------------
10164
10165
19.5k
bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; }
10166
10167
// ---------------------------------------------------------------------------
10168
10169
11.9k
void PROJStringFormatter::addParam(const std::string &paramName) {
10170
11.9k
    if (d->steps_.empty()) {
10171
0
        d->addStep();
10172
0
    }
10173
11.9k
    d->steps_.back().paramValues.push_back(Step::KeyValue(paramName));
10174
11.9k
}
10175
10176
// ---------------------------------------------------------------------------
10177
10178
603
void PROJStringFormatter::addParam(const char *paramName, int val) {
10179
603
    addParam(std::string(paramName), val);
10180
603
}
10181
10182
603
void PROJStringFormatter::addParam(const std::string &paramName, int val) {
10183
603
    addParam(paramName, internal::toString(val));
10184
603
}
10185
10186
// ---------------------------------------------------------------------------
10187
10188
33.9k
static std::string formatToString(double val) {
10189
33.9k
    if (std::abs(val * 10 - std::round(val * 10)) < 1e-8) {
10190
        // For the purpose of
10191
        // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561
10192
        // Latitude of natural of origin to be properly rounded from 55 grad
10193
        // to
10194
        // 49.5 deg
10195
31.9k
        val = std::round(val * 10) / 10;
10196
31.9k
    }
10197
33.9k
    return normalizeSerializedString(internal::toString(val));
10198
33.9k
}
10199
10200
// ---------------------------------------------------------------------------
10201
10202
29.4k
void PROJStringFormatter::addParam(const char *paramName, double val) {
10203
29.4k
    addParam(std::string(paramName), val);
10204
29.4k
}
10205
10206
29.9k
void PROJStringFormatter::addParam(const std::string &paramName, double val) {
10207
29.9k
    addParam(paramName, formatToString(val));
10208
29.9k
}
10209
10210
// ---------------------------------------------------------------------------
10211
10212
void PROJStringFormatter::addParam(const char *paramName,
10213
568
                                   const std::vector<double> &vals) {
10214
568
    std::string paramValue;
10215
4.54k
    for (size_t i = 0; i < vals.size(); ++i) {
10216
3.97k
        if (i > 0) {
10217
3.40k
            paramValue += ',';
10218
3.40k
        }
10219
3.97k
        paramValue += formatToString(vals[i]);
10220
3.97k
    }
10221
568
    addParam(paramName, paramValue);
10222
568
}
10223
10224
// ---------------------------------------------------------------------------
10225
10226
19.7k
void PROJStringFormatter::addParam(const char *paramName, const char *val) {
10227
19.7k
    addParam(std::string(paramName), val);
10228
19.7k
}
10229
10230
void PROJStringFormatter::addParam(const char *paramName,
10231
7.50k
                                   const std::string &val) {
10232
7.50k
    addParam(std::string(paramName), val);
10233
7.50k
}
10234
10235
void PROJStringFormatter::addParam(const std::string &paramName,
10236
19.7k
                                   const char *val) {
10237
19.7k
    addParam(paramName, std::string(val));
10238
19.7k
}
10239
10240
// ---------------------------------------------------------------------------
10241
10242
void PROJStringFormatter::addParam(const std::string &paramName,
10243
58.2k
                                   const std::string &val) {
10244
58.2k
    if (d->steps_.empty()) {
10245
87
        d->addStep();
10246
87
    }
10247
58.2k
    d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val));
10248
58.2k
}
10249
10250
// ---------------------------------------------------------------------------
10251
10252
void PROJStringFormatter::setTOWGS84Parameters(
10253
1.55k
    const std::vector<double> &params) {
10254
1.55k
    d->toWGS84Parameters_ = params;
10255
1.55k
}
10256
10257
// ---------------------------------------------------------------------------
10258
10259
6.45k
const std::vector<double> &PROJStringFormatter::getTOWGS84Parameters() const {
10260
6.45k
    return d->toWGS84Parameters_;
10261
6.45k
}
10262
10263
// ---------------------------------------------------------------------------
10264
10265
0
std::set<std::string> PROJStringFormatter::getUsedGridNames() const {
10266
0
    std::set<std::string> res;
10267
0
    for (const auto &step : d->steps_) {
10268
0
        for (const auto &param : step.paramValues) {
10269
0
            if (param.keyEquals("grids") || param.keyEquals("file")) {
10270
0
                const auto gridNames = split(param.value, ",");
10271
0
                for (const auto &gridName : gridNames) {
10272
0
                    res.insert(gridName);
10273
0
                }
10274
0
            }
10275
0
        }
10276
0
    }
10277
0
    return res;
10278
0
}
10279
10280
// ---------------------------------------------------------------------------
10281
10282
10.3k
bool PROJStringFormatter::requiresPerCoordinateInputTime() const {
10283
49.5k
    for (const auto &step : d->steps_) {
10284
49.5k
        if (step.name == "set" && !step.inverted) {
10285
3.50k
            for (const auto &param : step.paramValues) {
10286
3.50k
                if (param.keyEquals("v_4")) {
10287
52
                    return false;
10288
52
                }
10289
3.50k
            }
10290
49.3k
        } else if (step.name == "helmert") {
10291
9.95k
            for (const auto &param : step.paramValues) {
10292
9.95k
                if (param.keyEquals("t_epoch")) {
10293
5
                    return true;
10294
5
                }
10295
9.95k
            }
10296
46.1k
        } else if (step.name == "deformation") {
10297
662
            for (const auto &param : step.paramValues) {
10298
662
                if (param.keyEquals("t_epoch")) {
10299
16
                    return true;
10300
16
                }
10301
662
            }
10302
46.0k
        } else if (step.name == "defmodel") {
10303
0
            return true;
10304
0
        }
10305
49.5k
    }
10306
10.2k
    return false;
10307
10.3k
}
10308
10309
// ---------------------------------------------------------------------------
10310
10311
void PROJStringFormatter::setVDatumExtension(const std::string &filename,
10312
1.69k
                                             const std::string &geoidCRSValue) {
10313
1.69k
    d->vDatumExtension_ = filename;
10314
1.69k
    d->geoidCRSValue_ = geoidCRSValue;
10315
1.69k
}
10316
10317
// ---------------------------------------------------------------------------
10318
10319
967
const std::string &PROJStringFormatter::getVDatumExtension() const {
10320
967
    return d->vDatumExtension_;
10321
967
}
10322
10323
// ---------------------------------------------------------------------------
10324
10325
967
const std::string &PROJStringFormatter::getGeoidCRSValue() const {
10326
967
    return d->geoidCRSValue_;
10327
967
}
10328
10329
// ---------------------------------------------------------------------------
10330
10331
724
void PROJStringFormatter::setHDatumExtension(const std::string &filename) {
10332
724
    d->hDatumExtension_ = filename;
10333
724
}
10334
10335
// ---------------------------------------------------------------------------
10336
10337
6.45k
const std::string &PROJStringFormatter::getHDatumExtension() const {
10338
6.45k
    return d->hDatumExtension_;
10339
6.45k
}
10340
10341
// ---------------------------------------------------------------------------
10342
10343
void PROJStringFormatter::setGeogCRSOfCompoundCRS(
10344
1.76k
    const crs::GeographicCRSPtr &crs) {
10345
1.76k
    d->geogCRSOfCompoundCRS_ = crs;
10346
1.76k
}
10347
10348
// ---------------------------------------------------------------------------
10349
10350
const crs::GeographicCRSPtr &
10351
1.99k
PROJStringFormatter::getGeogCRSOfCompoundCRS() const {
10352
1.99k
    return d->geogCRSOfCompoundCRS_;
10353
1.99k
}
10354
10355
// ---------------------------------------------------------------------------
10356
10357
0
void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) {
10358
0
    assert(d->omitProjLongLatIfPossible_ ^ omit);
10359
0
    d->omitProjLongLatIfPossible_ = omit;
10360
0
}
10361
10362
// ---------------------------------------------------------------------------
10363
10364
234
bool PROJStringFormatter::omitProjLongLatIfPossible() const {
10365
234
    return d->omitProjLongLatIfPossible_;
10366
234
}
10367
10368
// ---------------------------------------------------------------------------
10369
10370
5.77k
void PROJStringFormatter::pushOmitZUnitConversion() {
10371
5.77k
    d->omitZUnitConversion_.push_back(true);
10372
5.77k
}
10373
10374
// ---------------------------------------------------------------------------
10375
10376
5.77k
void PROJStringFormatter::popOmitZUnitConversion() {
10377
5.77k
    assert(d->omitZUnitConversion_.size() > 1);
10378
5.77k
    d->omitZUnitConversion_.pop_back();
10379
5.77k
}
10380
10381
// ---------------------------------------------------------------------------
10382
10383
0
bool PROJStringFormatter::omitZUnitConversion() const {
10384
0
    return d->omitZUnitConversion_.back();
10385
0
}
10386
10387
// ---------------------------------------------------------------------------
10388
10389
0
void PROJStringFormatter::pushOmitHorizontalConversionInVertTransformation() {
10390
0
    d->omitHorizontalConversionInVertTransformation_.push_back(true);
10391
0
}
10392
10393
// ---------------------------------------------------------------------------
10394
10395
0
void PROJStringFormatter::popOmitHorizontalConversionInVertTransformation() {
10396
0
    assert(d->omitHorizontalConversionInVertTransformation_.size() > 1);
10397
0
    d->omitHorizontalConversionInVertTransformation_.pop_back();
10398
0
}
10399
10400
// ---------------------------------------------------------------------------
10401
10402
66
bool PROJStringFormatter::omitHorizontalConversionInVertTransformation() const {
10403
66
    return d->omitHorizontalConversionInVertTransformation_.back();
10404
66
}
10405
10406
// ---------------------------------------------------------------------------
10407
10408
0
void PROJStringFormatter::setLegacyCRSToCRSContext(bool legacyContext) {
10409
0
    d->legacyCRSToCRSContext_ = legacyContext;
10410
0
}
10411
10412
// ---------------------------------------------------------------------------
10413
10414
11.2k
bool PROJStringFormatter::getLegacyCRSToCRSContext() const {
10415
11.2k
    return d->legacyCRSToCRSContext_;
10416
11.2k
}
10417
10418
// ---------------------------------------------------------------------------
10419
10420
/** Asks for a "normalized" output during toString(), aimed at comparing two
10421
 * strings for equivalence.
10422
 *
10423
 * This consists for now in sorting the +key=value option in lexicographic
10424
 * order.
10425
 */
10426
0
PROJStringFormatter &PROJStringFormatter::setNormalizeOutput() {
10427
0
    d->normalizeOutput_ = true;
10428
0
    return *this;
10429
0
}
10430
10431
// ---------------------------------------------------------------------------
10432
10433
10.4k
const DatabaseContextPtr &PROJStringFormatter::databaseContext() const {
10434
10.4k
    return d->dbContext_;
10435
10.4k
}
10436
10437
//! @endcond
10438
10439
// ---------------------------------------------------------------------------
10440
10441
//! @cond Doxygen_Suppress
10442
10443
struct PROJStringParser::Private {
10444
    DatabaseContextPtr dbContext_{};
10445
    PJ_CONTEXT *ctx_{};
10446
    bool usePROJ4InitRules_ = false;
10447
    std::vector<std::string> warningList_{};
10448
10449
    std::string projString_{};
10450
10451
    std::vector<Step> steps_{};
10452
    std::vector<Step::KeyValue> globalParamValues_{};
10453
    std::string title_{};
10454
10455
    bool ignoreNadgrids_ = false;
10456
10457
    template <class T>
10458
    // cppcheck-suppress functionStatic
10459
352k
    bool hasParamValue(Step &step, const T key) {
10460
352k
        for (auto &pair : globalParamValues_) {
10461
119k
            if (ci_equal(pair.key, key)) {
10462
722
                pair.usedByParser = true;
10463
722
                return true;
10464
722
            }
10465
119k
        }
10466
859k
        for (auto &pair : step.paramValues) {
10467
859k
            if (ci_equal(pair.key, key)) {
10468
24.0k
                pair.usedByParser = true;
10469
24.0k
                return true;
10470
24.0k
            }
10471
859k
        }
10472
327k
        return false;
10473
351k
    }
10474
10475
    template <class T>
10476
    // cppcheck-suppress functionStatic
10477
5.46k
    const std::string &getGlobalParamValue(T key) {
10478
38.9k
        for (auto &pair : globalParamValues_) {
10479
38.9k
            if (ci_equal(pair.key, key)) {
10480
683
                pair.usedByParser = true;
10481
683
                return pair.value;
10482
683
            }
10483
38.9k
        }
10484
4.78k
        return emptyString;
10485
5.46k
    }
10486
10487
    template <class T>
10488
    // cppcheck-suppress functionStatic
10489
1.42M
    const std::string &getParamValue(Step &step, const T key) {
10490
1.42M
        for (auto &pair : globalParamValues_) {
10491
494k
            if (ci_equal(pair.key, key)) {
10492
2.63k
                pair.usedByParser = true;
10493
2.63k
                return pair.value;
10494
2.63k
            }
10495
494k
        }
10496
3.70M
        for (auto &pair : step.paramValues) {
10497
3.70M
            if (ci_equal(pair.key, key)) {
10498
203k
                pair.usedByParser = true;
10499
203k
                return pair.value;
10500
203k
            }
10501
3.70M
        }
10502
1.21M
        return emptyString;
10503
1.42M
    }
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
10489
274k
    const std::string &getParamValue(Step &step, const T key) {
10490
274k
        for (auto &pair : globalParamValues_) {
10491
132k
            if (ci_equal(pair.key, key)) {
10492
590
                pair.usedByParser = true;
10493
590
                return pair.value;
10494
590
            }
10495
132k
        }
10496
744k
        for (auto &pair : step.paramValues) {
10497
744k
            if (ci_equal(pair.key, key)) {
10498
47.4k
                pair.usedByParser = true;
10499
47.4k
                return pair.value;
10500
47.4k
            }
10501
744k
        }
10502
226k
        return emptyString;
10503
274k
    }
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
10489
1.15M
    const std::string &getParamValue(Step &step, const T key) {
10490
1.15M
        for (auto &pair : globalParamValues_) {
10491
362k
            if (ci_equal(pair.key, key)) {
10492
2.04k
                pair.usedByParser = true;
10493
2.04k
                return pair.value;
10494
2.04k
            }
10495
362k
        }
10496
2.96M
        for (auto &pair : step.paramValues) {
10497
2.96M
            if (ci_equal(pair.key, key)) {
10498
156k
                pair.usedByParser = true;
10499
156k
                return pair.value;
10500
156k
            }
10501
2.96M
        }
10502
992k
        return emptyString;
10503
1.14M
    }
10504
10505
9.20k
    static const std::string &getParamValueK(Step &step) {
10506
28.5k
        for (auto &pair : step.paramValues) {
10507
28.5k
            if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) {
10508
3.24k
                pair.usedByParser = true;
10509
3.24k
                return pair.value;
10510
3.24k
            }
10511
28.5k
        }
10512
5.95k
        return emptyString;
10513
9.20k
    }
10514
10515
    // cppcheck-suppress functionStatic
10516
59.7k
    bool hasUnusedParameters(const Step &step) const {
10517
59.7k
        if (steps_.size() == 1) {
10518
118k
            for (const auto &pair : step.paramValues) {
10519
118k
                if (pair.key != "no_defs" && !pair.usedByParser) {
10520
8.92k
                    return true;
10521
8.92k
                }
10522
118k
            }
10523
59.3k
        }
10524
50.8k
        return false;
10525
59.7k
    }
10526
10527
    // cppcheck-suppress functionStatic
10528
    std::string guessBodyName(double a);
10529
10530
    PrimeMeridianNNPtr buildPrimeMeridian(Step &step);
10531
    GeodeticReferenceFrameNNPtr buildDatum(Step &step,
10532
                                           const std::string &title);
10533
    GeodeticCRSNNPtr buildGeodeticCRS(int iStep, int iUnitConvert,
10534
                                      int iAxisSwap, bool ignorePROJAxis);
10535
    GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert);
10536
    CRSNNPtr buildProjectedCRS(int iStep, const GeodeticCRSNNPtr &geogCRS,
10537
                               int iUnitConvert, int iAxisSwap);
10538
    CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs);
10539
    UnitOfMeasure buildUnit(Step &step, const std::string &unitsParamName,
10540
                            const std::string &toMeterParamName);
10541
10542
    enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE };
10543
10544
    std::vector<CoordinateSystemAxisNNPtr>
10545
    processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap,
10546
                    AxisType axisType, bool ignorePROJAxis);
10547
10548
    EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert,
10549
                                          int iAxisSwap, bool ignorePROJAxis);
10550
10551
    SphericalCSNNPtr buildSphericalCS(int iStep, int iUnitConvert,
10552
                                      int iAxisSwap, bool ignorePROJAxis);
10553
};
10554
10555
//! @endcond
10556
10557
// ---------------------------------------------------------------------------
10558
10559
45.4k
PROJStringParser::PROJStringParser() : d(std::make_unique<Private>()) {}
10560
10561
// ---------------------------------------------------------------------------
10562
10563
//! @cond Doxygen_Suppress
10564
45.4k
PROJStringParser::~PROJStringParser() = default;
10565
//! @endcond
10566
10567
// ---------------------------------------------------------------------------
10568
10569
/** \brief Attach a database context, to allow queries in it if needed.
10570
 */
10571
PROJStringParser &
10572
45.4k
PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
10573
45.4k
    d->dbContext_ = dbContext;
10574
45.4k
    return *this;
10575
45.4k
}
10576
10577
// ---------------------------------------------------------------------------
10578
10579
//! @cond Doxygen_Suppress
10580
45.4k
PROJStringParser &PROJStringParser::attachContext(PJ_CONTEXT *ctx) {
10581
45.4k
    d->ctx_ = ctx;
10582
45.4k
    return *this;
10583
45.4k
}
10584
//! @endcond
10585
10586
// ---------------------------------------------------------------------------
10587
10588
/** \brief Set how init=epsg:XXXX syntax should be interpreted.
10589
 *
10590
 * @param enable When set to true,
10591
 * init=epsg:XXXX syntax will be allowed and will be interpreted according to
10592
 * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
10593
 * order and will expect/output coordinates in radians. ProjectedCRS will have
10594
 * easting, northing axis order (except the ones with Transverse Mercator South
10595
 * Orientated projection).
10596
 */
10597
45.4k
PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) {
10598
45.4k
    d->usePROJ4InitRules_ = enable;
10599
45.4k
    return *this;
10600
45.4k
}
10601
10602
// ---------------------------------------------------------------------------
10603
10604
/** \brief Return the list of warnings found during parsing.
10605
 */
10606
0
std::vector<std::string> PROJStringParser::warningList() const {
10607
0
    return d->warningList_;
10608
0
}
10609
10610
// ---------------------------------------------------------------------------
10611
10612
//! @cond Doxygen_Suppress
10613
10614
// ---------------------------------------------------------------------------
10615
10616
static const struct LinearUnitDesc {
10617
    const char *projName;
10618
    const char *convToMeter;
10619
    const char *name;
10620
    int epsgCode;
10621
} linearUnitDescs[] = {
10622
    {"mm", "0.001", "millimetre", 1025},
10623
    {"cm", "0.01", "centimetre", 1033},
10624
    {"m", "1.0", "metre", 9001},
10625
    {"meter", "1.0", "metre", 9001}, // alternative
10626
    {"metre", "1.0", "metre", 9001}, // alternative
10627
    {"ft", "0.3048", "foot", 9002},
10628
    {"us-ft", "0.3048006096012192", "US survey foot", 9003},
10629
    {"fath", "1.8288", "fathom", 9014},
10630
    {"kmi", "1852", "nautical mile", 9030},
10631
    {"us-ch", "20.11684023368047", "US survey chain", 9033},
10632
    {"us-mi", "1609.347218694437", "US survey mile", 9035},
10633
    {"km", "1000.0", "kilometre", 9036},
10634
    {"ind-ft", "0.30479841", "Indian foot (1937)", 9081},
10635
    {"ind-yd", "0.91439523", "Indian yard (1937)", 9085},
10636
    {"mi", "1609.344", "Statute mile", 9093},
10637
    {"yd", "0.9144", "yard", 9096},
10638
    {"ch", "20.1168", "chain", 9097},
10639
    {"link", "0.201168", "link", 9098},
10640
    {"dm", "0.1", "decimetre", 0},                       // no EPSG equivalent
10641
    {"in", "0.0254", "inch", 0},                         // no EPSG equivalent
10642
    {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent
10643
    {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent
10644
    {"ind-ch", "20.11669506", "Indian chain", 0},        // no EPSG equivalent
10645
};
10646
10647
14.8k
static const LinearUnitDesc *getLinearUnits(const std::string &projName) {
10648
56.9k
    for (const auto &desc : linearUnitDescs) {
10649
56.9k
        if (desc.projName == projName)
10650
14.5k
            return &desc;
10651
56.9k
    }
10652
297
    return nullptr;
10653
14.8k
}
10654
10655
1.60k
static const LinearUnitDesc *getLinearUnits(double toMeter) {
10656
33.5k
    for (const auto &desc : linearUnitDescs) {
10657
33.5k
        if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) <
10658
33.5k
            1e-10 * toMeter) {
10659
175
            return &desc;
10660
175
        }
10661
33.5k
    }
10662
1.42k
    return nullptr;
10663
1.60k
}
10664
10665
// ---------------------------------------------------------------------------
10666
10667
14.7k
static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) {
10668
14.7k
    std::string unitsCode;
10669
14.7k
    if (unitsMatch->epsgCode) {
10670
14.5k
        std::ostringstream buffer;
10671
14.5k
        buffer.imbue(std::locale::classic());
10672
14.5k
        buffer << unitsMatch->epsgCode;
10673
14.5k
        unitsCode = buffer.str();
10674
14.5k
    }
10675
14.7k
    return UnitOfMeasure(
10676
14.7k
        unitsMatch->name, c_locale_stod(unitsMatch->convToMeter),
10677
14.7k
        UnitOfMeasure::Type::LINEAR,
10678
14.7k
        unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode);
10679
14.7k
}
10680
10681
// ---------------------------------------------------------------------------
10682
10683
1.42k
static UnitOfMeasure _buildUnit(double to_meter_value) {
10684
    // TODO: look-up in EPSG catalog
10685
1.42k
    if (to_meter_value == 0) {
10686
1
        throw ParsingException("invalid unit value");
10687
1
    }
10688
1.42k
    return UnitOfMeasure("unknown", to_meter_value,
10689
1.42k
                         UnitOfMeasure::Type::LINEAR);
10690
1.42k
}
10691
10692
// ---------------------------------------------------------------------------
10693
10694
UnitOfMeasure
10695
PROJStringParser::Private::buildUnit(Step &step,
10696
                                     const std::string &unitsParamName,
10697
92.8k
                                     const std::string &toMeterParamName) {
10698
92.8k
    UnitOfMeasure unit = UnitOfMeasure::METRE;
10699
92.8k
    const LinearUnitDesc *unitsMatch = nullptr;
10700
92.8k
    const auto &projUnits = getParamValue(step, unitsParamName);
10701
92.8k
    if (!projUnits.empty()) {
10702
14.5k
        unitsMatch = getLinearUnits(projUnits);
10703
14.5k
        if (unitsMatch == nullptr) {
10704
15
            throw ParsingException("unhandled " + unitsParamName + "=" +
10705
15
                                   projUnits);
10706
15
        }
10707
14.5k
    }
10708
10709
92.7k
    const auto &toMeter = getParamValue(step, toMeterParamName);
10710
92.7k
    if (!toMeter.empty()) {
10711
1.61k
        double to_meter_value;
10712
1.61k
        try {
10713
1.61k
            to_meter_value = c_locale_stod(toMeter);
10714
1.61k
        } catch (const std::invalid_argument &) {
10715
15
            throw ParsingException("invalid value for " + toMeterParamName);
10716
15
        }
10717
1.59k
        unitsMatch = getLinearUnits(to_meter_value);
10718
1.59k
        if (unitsMatch == nullptr) {
10719
1.42k
            unit = _buildUnit(to_meter_value);
10720
1.42k
        }
10721
1.59k
    }
10722
10723
92.7k
    if (unitsMatch) {
10724
14.7k
        unit = _buildUnit(unitsMatch);
10725
14.7k
    }
10726
10727
92.7k
    return unit;
10728
92.7k
}
10729
10730
// ---------------------------------------------------------------------------
10731
10732
static const struct DatumDesc {
10733
    const char *projName;
10734
    const char *gcsName;
10735
    int gcsCode;
10736
    const char *datumName;
10737
    int datumCode;
10738
    const char *ellipsoidName;
10739
    int ellipsoidCode;
10740
    double a;
10741
    double rf;
10742
} datumDescs[] = {
10743
    {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121,
10744
     "GRS 1980", 7019, 6378137, 298.257222101},
10745
    {"potsdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314,
10746
     "Bessel 1841", 7004, 6377397.155, 299.1528128},
10747
    {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011,
10748
     6378249.2, 293.4660213},
10749
    {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312,
10750
     "Bessel 1841", 7004, 6377397.155, 299.1528128},
10751
    {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002,
10752
     6377340.189, 299.3249646},
10753
    {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272,
10754
     "International 1924", 7022, 6378388, 297},
10755
    {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001,
10756
     6377563.396, 299.3249646},
10757
};
10758
10759
// ---------------------------------------------------------------------------
10760
10761
100k
static bool isGeographicStep(const std::string &name) {
10762
100k
    return name == "longlat" || name == "lonlat" || name == "latlong" ||
10763
100k
           name == "latlon";
10764
100k
}
10765
10766
// ---------------------------------------------------------------------------
10767
10768
39.1k
static bool isGeocentricStep(const std::string &name) {
10769
39.1k
    return name == "geocent" || name == "cart";
10770
39.1k
}
10771
10772
// ---------------------------------------------------------------------------
10773
10774
80.4k
static bool isTopocentricStep(const std::string &name) {
10775
80.4k
    return name == "topocentric";
10776
80.4k
}
10777
10778
// ---------------------------------------------------------------------------
10779
10780
28.6k
static bool isProjectedStep(const std::string &name) {
10781
28.6k
    if (name == "etmerc" || name == "utm" ||
10782
28.6k
        !getMappingsFromPROJName(name).empty()) {
10783
13.6k
        return true;
10784
13.6k
    }
10785
    // IMPROVE ME: have a better way of distinguishing projections from
10786
    // other
10787
    // transformations.
10788
15.0k
    if (name == "pipeline" || name == "geoc" || name == "deformation" ||
10789
15.0k
        name == "helmert" || name == "hgridshift" || name == "molodensky" ||
10790
15.0k
        name == "vgridshift") {
10791
1.01k
        return false;
10792
1.01k
    }
10793
14.0k
    const auto *operations = proj_list_operations();
10794
1.47M
    for (int i = 0; operations[i].id != nullptr; ++i) {
10795
1.47M
        if (name == operations[i].id) {
10796
11.1k
            return true;
10797
11.1k
        }
10798
1.47M
    }
10799
2.81k
    return false;
10800
14.0k
}
10801
10802
// ---------------------------------------------------------------------------
10803
10804
50.4k
static PropertyMap createMapWithUnknownName() {
10805
50.4k
    return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown");
10806
50.4k
}
10807
10808
// ---------------------------------------------------------------------------
10809
10810
90.7k
PrimeMeridianNNPtr PROJStringParser::Private::buildPrimeMeridian(Step &step) {
10811
10812
90.7k
    PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH;
10813
90.7k
    const auto &pmStr = getParamValue(step, "pm");
10814
90.7k
    if (!pmStr.empty()) {
10815
3.32k
        char *end;
10816
3.32k
        double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG;
10817
3.32k
        if (pmValue != HUGE_VAL && *end == '\0') {
10818
2.80k
            pm = PrimeMeridian::create(createMapWithUnknownName(),
10819
2.80k
                                       Angle(pmValue));
10820
2.80k
        } else {
10821
522
            bool found = false;
10822
522
            if (pmStr == "paris") {
10823
73
                found = true;
10824
73
                pm = PrimeMeridian::PARIS;
10825
73
            }
10826
522
            auto proj_prime_meridians = proj_list_prime_meridians();
10827
3.25k
            for (int i = 0; !found && proj_prime_meridians[i].id != nullptr;
10828
3.18k
                 i++) {
10829
3.18k
                if (pmStr == proj_prime_meridians[i].id) {
10830
446
                    found = true;
10831
446
                    std::string name = static_cast<char>(::toupper(pmStr[0])) +
10832
446
                                       pmStr.substr(1);
10833
446
                    pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) *
10834
446
                              RAD_TO_DEG;
10835
446
                    pm = PrimeMeridian::create(
10836
446
                        PropertyMap().set(IdentifiedObject::NAME_KEY, name),
10837
446
                        Angle(pmValue));
10838
446
                    break;
10839
446
                }
10840
3.18k
            }
10841
522
            if (!found) {
10842
3
                throw ParsingException("unknown pm " + pmStr);
10843
3
            }
10844
522
        }
10845
3.32k
    }
10846
90.7k
    return pm;
10847
90.7k
}
10848
10849
// ---------------------------------------------------------------------------
10850
10851
6.30k
std::string PROJStringParser::Private::guessBodyName(double a) {
10852
10853
6.30k
    auto ret = Ellipsoid::guessBodyName(dbContext_, a);
10854
6.30k
    if (ret == NON_EARTH_BODY && dbContext_ == nullptr && ctx_ != nullptr) {
10855
7
        dbContext_ =
10856
7
            ctx_->get_cpp_context()->getDatabaseContext().as_nullable();
10857
7
        if (dbContext_) {
10858
7
            ret = Ellipsoid::guessBodyName(dbContext_, a);
10859
7
        }
10860
7
    }
10861
6.30k
    return ret;
10862
6.30k
}
10863
10864
// ---------------------------------------------------------------------------
10865
10866
GeodeticReferenceFrameNNPtr
10867
60.0k
PROJStringParser::Private::buildDatum(Step &step, const std::string &title) {
10868
10869
60.0k
    std::string ellpsStr = getParamValue(step, "ellps");
10870
60.0k
    const auto &datumStr = getParamValue(step, "datum");
10871
60.0k
    const auto &RStr = getParamValue(step, "R");
10872
60.0k
    const auto &aStr = getParamValue(step, "a");
10873
60.0k
    const auto &bStr = getParamValue(step, "b");
10874
60.0k
    const auto &rfStr = getParamValue(step, "rf");
10875
60.0k
    const auto &fStr = getParamValue(step, "f");
10876
60.0k
    const auto &esStr = getParamValue(step, "es");
10877
60.0k
    const auto &eStr = getParamValue(step, "e");
10878
60.0k
    double a = -1.0;
10879
60.0k
    double b = -1.0;
10880
60.0k
    double rf = -1.0;
10881
60.0k
    const util::optional<std::string> optionalEmptyString{};
10882
60.0k
    const bool numericParamPresent =
10883
60.0k
        !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() ||
10884
60.0k
        !fStr.empty() || !esStr.empty() || !eStr.empty();
10885
10886
60.0k
    if (!numericParamPresent && ellpsStr.empty() && datumStr.empty() &&
10887
60.0k
        (step.name == "krovak" || step.name == "mod_krovak")) {
10888
1.19k
        ellpsStr = "bessel";
10889
1.19k
    }
10890
10891
60.0k
    PrimeMeridianNNPtr pm(buildPrimeMeridian(step));
10892
60.0k
    PropertyMap grfMap;
10893
10894
60.0k
    const auto &nadgrids = getParamValue(step, "nadgrids");
10895
60.0k
    const auto &towgs84 = getParamValue(step, "towgs84");
10896
60.0k
    std::string datumNameSuffix;
10897
60.0k
    if (!nadgrids.empty()) {
10898
2.83k
        datumNameSuffix = " using nadgrids=" + nadgrids;
10899
57.2k
    } else if (!towgs84.empty()) {
10900
2.89k
        datumNameSuffix = " using towgs84=" + towgs84;
10901
2.89k
    }
10902
10903
    // It is arguable that we allow the prime meridian of a datum defined by
10904
    // its name to be overridden, but this is found at least in a regression
10905
    // test
10906
    // of GDAL. So let's keep the ellipsoid part of the datum in that case and
10907
    // use the specified prime meridian.
10908
60.0k
    const auto overridePmIfNeeded =
10909
60.0k
        [&pm, &datumNameSuffix](const GeodeticReferenceFrameNNPtr &grf) {
10910
23.5k
            if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
10911
22.7k
                return grf;
10912
22.7k
            } else {
10913
819
                return GeodeticReferenceFrame::create(
10914
819
                    PropertyMap().set(IdentifiedObject::NAME_KEY,
10915
819
                                      UNKNOWN_BASED_ON +
10916
819
                                          grf->ellipsoid()->nameStr() +
10917
819
                                          " ellipsoid" + datumNameSuffix),
10918
819
                    grf->ellipsoid(), grf->anchorDefinition(), pm);
10919
819
            }
10920
23.5k
        };
10921
10922
    // R take precedence
10923
60.0k
    if (!RStr.empty()) {
10924
541
        double R;
10925
541
        try {
10926
541
            R = c_locale_stod(RStr);
10927
541
        } catch (const std::invalid_argument &) {
10928
7
            throw ParsingException("Invalid R value");
10929
7
        }
10930
534
        auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
10931
534
                                                 Length(R), guessBodyName(R));
10932
534
        return GeodeticReferenceFrame::create(
10933
534
            grfMap.set(IdentifiedObject::NAME_KEY,
10934
534
                       title.empty() ? "unknown" + datumNameSuffix : title),
10935
534
            ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
10936
541
    }
10937
10938
59.5k
    if (!datumStr.empty()) {
10939
5.12k
        auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap,
10940
5.12k
                        &optionalEmptyString, &pm]() {
10941
5.12k
            if (datumStr == "WGS84") {
10942
4.03k
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
10943
4.03k
            } else if (datumStr == "NAD83") {
10944
20
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269);
10945
1.06k
            } else if (datumStr == "NAD27") {
10946
239
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267);
10947
829
            } else {
10948
10949
4.13k
                for (const auto &datumDesc : datumDescs) {
10950
4.13k
                    if (datumStr == datumDesc.projName) {
10951
812
                        (void)datumDesc.gcsName; // to please cppcheck
10952
812
                        (void)datumDesc.gcsCode; // to please cppcheck
10953
812
                        auto ellipsoid = Ellipsoid::createFlattenedSphere(
10954
812
                            grfMap
10955
812
                                .set(IdentifiedObject::NAME_KEY,
10956
812
                                     datumDesc.ellipsoidName)
10957
812
                                .set(Identifier::CODESPACE_KEY,
10958
812
                                     Identifier::EPSG)
10959
812
                                .set(Identifier::CODE_KEY,
10960
812
                                     datumDesc.ellipsoidCode),
10961
812
                            Length(datumDesc.a), Scale(datumDesc.rf));
10962
812
                        return GeodeticReferenceFrame::create(
10963
812
                            grfMap
10964
812
                                .set(IdentifiedObject::NAME_KEY,
10965
812
                                     datumDesc.datumName)
10966
812
                                .set(Identifier::CODESPACE_KEY,
10967
812
                                     Identifier::EPSG)
10968
812
                                .set(Identifier::CODE_KEY, datumDesc.datumCode),
10969
812
                            ellipsoid, optionalEmptyString, pm);
10970
812
                    }
10971
4.13k
                }
10972
829
            }
10973
17
            throw ParsingException("unknown datum " + datumStr);
10974
5.12k
        }();
10975
5.12k
        if (!numericParamPresent) {
10976
4.45k
            return l_datum;
10977
4.45k
        }
10978
670
        a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
10979
670
        rf = l_datum->ellipsoid()->computedInverseFlattening();
10980
670
    }
10981
10982
54.3k
    else if (!ellpsStr.empty()) {
10983
30.9k
        auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString, &pm,
10984
30.9k
                        &datumNameSuffix]() {
10985
30.9k
            if (ellpsStr == "WGS84") {
10986
26.2k
                return GeodeticReferenceFrame::create(
10987
26.2k
                    grfMap.set(IdentifiedObject::NAME_KEY,
10988
26.2k
                               title.empty() ? std::string(UNKNOWN_BASED_ON)
10989
26.1k
                                                   .append("WGS 84 ellipsoid")
10990
26.1k
                                                   .append(datumNameSuffix)
10991
26.2k
                                             : title),
10992
26.2k
                    Ellipsoid::WGS84, optionalEmptyString, pm);
10993
26.2k
            } else if (ellpsStr == "GRS80") {
10994
146
                return GeodeticReferenceFrame::create(
10995
146
                    grfMap.set(IdentifiedObject::NAME_KEY,
10996
146
                               title.empty() ? std::string(UNKNOWN_BASED_ON)
10997
128
                                                   .append("GRS 1980 ellipsoid")
10998
128
                                                   .append(datumNameSuffix)
10999
146
                                             : title),
11000
146
                    Ellipsoid::GRS1980, optionalEmptyString, pm);
11001
4.57k
            } else {
11002
4.57k
                auto proj_ellps = proj_list_ellps();
11003
114k
                for (int i = 0; proj_ellps[i].id != nullptr; i++) {
11004
114k
                    if (ellpsStr == proj_ellps[i].id) {
11005
4.56k
                        assert(strncmp(proj_ellps[i].major, "a=", 2) == 0);
11006
4.56k
                        const double a_iter =
11007
4.56k
                            c_locale_stod(proj_ellps[i].major + 2);
11008
4.56k
                        EllipsoidPtr ellipsoid;
11009
4.56k
                        PropertyMap ellpsMap;
11010
4.56k
                        if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) {
11011
791
                            const double b_iter =
11012
791
                                c_locale_stod(proj_ellps[i].ell + 2);
11013
791
                            ellipsoid =
11014
791
                                Ellipsoid::createTwoAxis(
11015
791
                                    ellpsMap.set(IdentifiedObject::NAME_KEY,
11016
791
                                                 proj_ellps[i].name),
11017
791
                                    Length(a_iter), Length(b_iter))
11018
791
                                    .as_nullable();
11019
3.77k
                        } else {
11020
3.77k
                            assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0);
11021
3.77k
                            const double rf_iter =
11022
3.77k
                                c_locale_stod(proj_ellps[i].ell + 3);
11023
3.77k
                            ellipsoid =
11024
3.77k
                                Ellipsoid::createFlattenedSphere(
11025
3.77k
                                    ellpsMap.set(IdentifiedObject::NAME_KEY,
11026
3.77k
                                                 proj_ellps[i].name),
11027
3.77k
                                    Length(a_iter), Scale(rf_iter))
11028
3.77k
                                    .as_nullable();
11029
3.77k
                        }
11030
4.56k
                        return GeodeticReferenceFrame::create(
11031
4.56k
                            grfMap.set(IdentifiedObject::NAME_KEY,
11032
4.56k
                                       title.empty()
11033
4.56k
                                           ? std::string(UNKNOWN_BASED_ON)
11034
4.47k
                                                 .append(proj_ellps[i].name)
11035
4.47k
                                                 .append(" ellipsoid")
11036
4.47k
                                                 .append(datumNameSuffix)
11037
4.56k
                                           : title),
11038
4.56k
                            NN_NO_CHECK(ellipsoid), optionalEmptyString, pm);
11039
4.56k
                    }
11040
114k
                }
11041
10
                throw ParsingException("unknown ellipsoid " + ellpsStr);
11042
4.57k
            }
11043
30.9k
        }();
11044
30.9k
        if (!numericParamPresent) {
11045
29.9k
            return l_datum;
11046
29.9k
        }
11047
1.02k
        a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
11048
1.02k
        if (l_datum->ellipsoid()->semiMinorAxis().has_value()) {
11049
679
            b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue();
11050
679
        } else {
11051
341
            rf = l_datum->ellipsoid()->computedInverseFlattening();
11052
341
        }
11053
1.02k
    }
11054
11055
25.1k
    if (!aStr.empty()) {
11056
4.42k
        try {
11057
4.42k
            a = c_locale_stod(aStr);
11058
4.42k
        } catch (const std::invalid_argument &) {
11059
19
            throw ParsingException("Invalid a value");
11060
19
        }
11061
4.42k
    }
11062
11063
25.1k
    const auto createGRF = [&grfMap, &title, &optionalEmptyString,
11064
25.1k
                            &datumNameSuffix,
11065
25.1k
                            &pm](const EllipsoidNNPtr &ellipsoid) {
11066
5.77k
        std::string datumName(title);
11067
5.77k
        if (title.empty()) {
11068
5.60k
            if (ellipsoid->nameStr() != "unknown") {
11069
1.51k
                datumName = UNKNOWN_BASED_ON;
11070
1.51k
                datumName += ellipsoid->nameStr();
11071
1.51k
                datumName += " ellipsoid";
11072
4.08k
            } else {
11073
4.08k
                datumName = "unknown";
11074
4.08k
            }
11075
5.60k
            datumName += datumNameSuffix;
11076
5.60k
        }
11077
5.77k
        return GeodeticReferenceFrame::create(
11078
5.77k
            grfMap.set(IdentifiedObject::NAME_KEY, datumName), ellipsoid,
11079
5.77k
            optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
11080
5.77k
    };
11081
11082
25.1k
    if (a > 0 && (b > 0 || !bStr.empty())) {
11083
1.91k
        if (!bStr.empty()) {
11084
1.23k
            try {
11085
1.23k
                b = c_locale_stod(bStr);
11086
1.23k
            } catch (const std::invalid_argument &) {
11087
1
                throw ParsingException("Invalid b value");
11088
1
            }
11089
1.23k
        }
11090
1.91k
        auto ellipsoid =
11091
1.91k
            Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a),
11092
1.91k
                                     Length(b), guessBodyName(a))
11093
1.91k
                ->identify();
11094
1.91k
        return createGRF(ellipsoid);
11095
1.91k
    }
11096
11097
23.2k
    else if (a > 0 && (rf >= 0 || !rfStr.empty())) {
11098
1.15k
        if (!rfStr.empty()) {
11099
286
            try {
11100
286
                rf = c_locale_stod(rfStr);
11101
286
            } catch (const std::invalid_argument &) {
11102
0
                throw ParsingException("Invalid rf value");
11103
0
            }
11104
286
        }
11105
1.15k
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11106
1.15k
                             createMapWithUnknownName(), Length(a), Scale(rf),
11107
1.15k
                             guessBodyName(a))
11108
1.15k
                             ->identify();
11109
1.15k
        return createGRF(ellipsoid);
11110
1.15k
    }
11111
11112
22.0k
    else if (a > 0 && !fStr.empty()) {
11113
14
        double f;
11114
14
        try {
11115
14
            f = c_locale_stod(fStr);
11116
14
        } catch (const std::invalid_argument &) {
11117
7
            throw ParsingException("Invalid f value");
11118
7
        }
11119
7
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11120
7
                             createMapWithUnknownName(), Length(a),
11121
7
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11122
7
                             ->identify();
11123
7
        return createGRF(ellipsoid);
11124
14
    }
11125
11126
22.0k
    else if (a > 0 && !eStr.empty()) {
11127
80
        double e;
11128
80
        try {
11129
80
            e = c_locale_stod(eStr);
11130
80
        } catch (const std::invalid_argument &) {
11131
8
            throw ParsingException("Invalid e value");
11132
8
        }
11133
72
        double alpha = asin(e);    /* angular eccentricity */
11134
72
        double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */
11135
72
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11136
72
                             createMapWithUnknownName(), Length(a),
11137
72
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11138
72
                             ->identify();
11139
72
        return createGRF(ellipsoid);
11140
80
    }
11141
11142
21.9k
    else if (a > 0 && !esStr.empty()) {
11143
0
        double es;
11144
0
        try {
11145
0
            es = c_locale_stod(esStr);
11146
0
        } catch (const std::invalid_argument &) {
11147
0
            throw ParsingException("Invalid es value");
11148
0
        }
11149
0
        double f = 1 - sqrt(1 - es);
11150
0
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11151
0
                             createMapWithUnknownName(), Length(a),
11152
0
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11153
0
                             ->identify();
11154
0
        return createGRF(ellipsoid);
11155
0
    }
11156
11157
    // If only a is specified, create a sphere
11158
21.9k
    if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() &&
11159
21.9k
        esStr.empty()) {
11160
2.62k
        auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
11161
2.62k
                                                 Length(a), guessBodyName(a));
11162
2.62k
        return createGRF(ellipsoid);
11163
2.62k
    }
11164
11165
19.3k
    if (!bStr.empty() && aStr.empty()) {
11166
6
        throw ParsingException("b found, but a missing");
11167
6
    }
11168
11169
19.3k
    if (!rfStr.empty() && aStr.empty()) {
11170
0
        throw ParsingException("rf found, but a missing");
11171
0
    }
11172
11173
19.3k
    if (!fStr.empty() && aStr.empty()) {
11174
4
        throw ParsingException("f found, but a missing");
11175
4
    }
11176
11177
19.3k
    if (!eStr.empty() && aStr.empty()) {
11178
4
        throw ParsingException("e found, but a missing");
11179
4
    }
11180
11181
19.3k
    if (!esStr.empty() && aStr.empty()) {
11182
1
        throw ParsingException("es found, but a missing");
11183
1
    }
11184
11185
19.3k
    return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
11186
19.3k
}
11187
11188
// ---------------------------------------------------------------------------
11189
11190
static const MeridianPtr nullMeridian{};
11191
11192
static CoordinateSystemAxisNNPtr
11193
createAxis(const std::string &name, const std::string &abbreviation,
11194
           const AxisDirection &direction, const common::UnitOfMeasure &unit,
11195
347k
           const MeridianPtr &meridian = nullMeridian) {
11196
347k
    return CoordinateSystemAxis::create(
11197
347k
        PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation,
11198
347k
        direction, unit, meridian);
11199
347k
}
11200
11201
std::vector<CoordinateSystemAxisNNPtr>
11202
PROJStringParser::Private::processAxisSwap(Step &step,
11203
                                           const UnitOfMeasure &unit,
11204
                                           int iAxisSwap, AxisType axisType,
11205
86.8k
                                           bool ignorePROJAxis) {
11206
86.8k
    assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap"));
11207
11208
86.8k
    const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR;
11209
86.8k
    const bool isSpherical = isGeographic && hasParamValue(step, "geoc");
11210
86.8k
    const auto &eastName = isSpherical    ? "Planetocentric longitude"
11211
86.8k
                           : isGeographic ? AxisName::Longitude
11212
85.3k
                                          : AxisName::Easting;
11213
86.8k
    const auto &eastAbbev = isSpherical    ? "V"
11214
86.8k
                            : isGeographic ? AxisAbbreviation::lon
11215
85.3k
                                           : AxisAbbreviation::E;
11216
86.8k
    const auto &eastDir =
11217
86.8k
        isGeographic                         ? AxisDirection::EAST
11218
86.8k
        : (axisType == AxisType::NORTH_POLE) ? AxisDirection::SOUTH
11219
30.2k
        : (axisType == AxisType::SOUTH_POLE) ? AxisDirection::NORTH
11220
29.9k
                                             : AxisDirection::EAST;
11221
86.8k
    CoordinateSystemAxisNNPtr east = createAxis(
11222
86.8k
        eastName, eastAbbev, eastDir, unit,
11223
86.8k
        (!isGeographic &&
11224
86.8k
         (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE))
11225
86.8k
            ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable()
11226
86.8k
            : nullMeridian);
11227
11228
86.8k
    const auto &northName = isSpherical    ? "Planetocentric latitude"
11229
86.8k
                            : isGeographic ? AxisName::Latitude
11230
85.3k
                                           : AxisName::Northing;
11231
86.8k
    const auto &northAbbev = isSpherical    ? "U"
11232
86.8k
                             : isGeographic ? AxisAbbreviation::lat
11233
85.3k
                                            : AxisAbbreviation::N;
11234
86.8k
    const auto &northDir = isGeographic ? AxisDirection::NORTH
11235
86.8k
                           : (axisType == AxisType::NORTH_POLE)
11236
30.2k
                               ? AxisDirection::SOUTH
11237
                               /*: (axisType == AxisType::SOUTH_POLE)
11238
                                     ? AxisDirection::NORTH*/
11239
30.2k
                               : AxisDirection::NORTH;
11240
86.8k
    const CoordinateSystemAxisNNPtr north = createAxis(
11241
86.8k
        northName, northAbbev, northDir, unit,
11242
86.8k
        isGeographic ? nullMeridian
11243
86.8k
        : (axisType == AxisType::NORTH_POLE)
11244
30.2k
            ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable()
11245
30.2k
        : (axisType == AxisType::SOUTH_POLE)
11246
29.9k
            ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE)).as_nullable()
11247
29.9k
            : nullMeridian);
11248
11249
86.8k
    CoordinateSystemAxisNNPtr west =
11250
86.8k
        createAxis(isSpherical    ? "Planetocentric longitude"
11251
86.8k
                   : isGeographic ? AxisName::Longitude
11252
85.3k
                                  : AxisName::Westing,
11253
86.8k
                   isSpherical    ? "V"
11254
86.8k
                   : isGeographic ? AxisAbbreviation::lon
11255
85.3k
                                  : std::string(),
11256
86.8k
                   AxisDirection::WEST, unit);
11257
11258
86.8k
    CoordinateSystemAxisNNPtr south =
11259
86.8k
        createAxis(isSpherical    ? "Planetocentric latitude"
11260
86.8k
                   : isGeographic ? AxisName::Latitude
11261
85.3k
                                  : AxisName::Southing,
11262
86.8k
                   isSpherical    ? "U"
11263
86.8k
                   : isGeographic ? AxisAbbreviation::lat
11264
85.3k
                                  : std::string(),
11265
86.8k
                   AxisDirection::SOUTH, unit);
11266
11267
86.8k
    std::vector<CoordinateSystemAxisNNPtr> axis{east, north};
11268
11269
86.8k
    const auto &axisStr = getParamValue(step, "axis");
11270
86.8k
    if (!ignorePROJAxis && !axisStr.empty()) {
11271
819
        if (axisStr.size() == 3) {
11272
2.44k
            for (int i = 0; i < 2; i++) {
11273
1.63k
                if (axisStr[i] == 'n') {
11274
20
                    axis[i] = north;
11275
1.61k
                } else if (axisStr[i] == 's') {
11276
799
                    axis[i] = south;
11277
819
                } else if (axisStr[i] == 'e') {
11278
32
                    axis[i] = east;
11279
787
                } else if (axisStr[i] == 'w') {
11280
778
                    axis[i] = west;
11281
778
                } else {
11282
9
                    throw ParsingException("Unhandled axis=" + axisStr);
11283
9
                }
11284
1.63k
            }
11285
819
        } else {
11286
0
            throw ParsingException("Unhandled axis=" + axisStr);
11287
0
        }
11288
86.0k
    } else if (iAxisSwap >= 0) {
11289
85
        auto &stepAxisSwap = steps_[iAxisSwap];
11290
85
        const auto &orderStr = getParamValue(stepAxisSwap, "order");
11291
85
        auto orderTab = split(orderStr, ',');
11292
85
        if (orderTab.size() != 2) {
11293
20
            throw ParsingException("Unhandled order=" + orderStr);
11294
20
        }
11295
65
        if (stepAxisSwap.inverted) {
11296
2
            throw ParsingException("Unhandled +inv for +proj=axisswap");
11297
2
        }
11298
11299
158
        for (size_t i = 0; i < 2; i++) {
11300
114
            if (orderTab[i] == "1") {
11301
4
                axis[i] = east;
11302
110
            } else if (orderTab[i] == "-1") {
11303
32
                axis[i] = west;
11304
78
            } else if (orderTab[i] == "2") {
11305
2
                axis[i] = north;
11306
76
            } else if (orderTab[i] == "-2") {
11307
57
                axis[i] = south;
11308
57
            } else {
11309
19
                throw ParsingException("Unhandled order=" + orderStr);
11310
19
            }
11311
114
        }
11312
85.9k
    } else if ((step.name == "krovak" || step.name == "mod_krovak") &&
11313
85.9k
               hasParamValue(step, "czech")) {
11314
409
        axis[0] = std::move(west);
11315
409
        axis[1] = std::move(south);
11316
409
    }
11317
86.7k
    return axis;
11318
86.8k
}
11319
11320
// ---------------------------------------------------------------------------
11321
11322
EllipsoidalCSNNPtr PROJStringParser::Private::buildEllipsoidalCS(
11323
55.0k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11324
55.0k
    auto &step = steps_[iStep];
11325
55.0k
    assert(iUnitConvert < 0 ||
11326
55.0k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11327
11328
55.0k
    UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
11329
55.0k
    if (iUnitConvert >= 0) {
11330
14
        auto &stepUnitConvert = steps_[iUnitConvert];
11331
14
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11332
14
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11333
14
        if (stepUnitConvert.inverted) {
11334
4
            std::swap(xy_in, xy_out);
11335
4
        }
11336
14
        if (iUnitConvert < iStep) {
11337
1
            std::swap(xy_in, xy_out);
11338
1
        }
11339
14
        if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
11340
14
            (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
11341
14
            throw ParsingException("unhandled values for xy_in and/or xy_out");
11342
14
        }
11343
0
        if (*xy_out == "rad") {
11344
0
            angularUnit = UnitOfMeasure::RADIAN;
11345
0
        } else if (*xy_out == "grad") {
11346
0
            angularUnit = UnitOfMeasure::GRAD;
11347
0
        }
11348
0
    }
11349
11350
55.0k
    std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
11351
55.0k
        step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
11352
55.0k
    CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create(
11353
55.0k
        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
11354
55.0k
                                AxisName::Ellipsoidal_height),
11355
55.0k
        AxisAbbreviation::h, AxisDirection::UP,
11356
55.0k
        buildUnit(step, "vunits", "vto_meter"));
11357
11358
55.0k
    return (!hasParamValue(step, "geoidgrids") &&
11359
55.0k
            (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter")))
11360
55.0k
               ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up)
11361
55.0k
               : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]);
11362
55.0k
}
11363
11364
// ---------------------------------------------------------------------------
11365
11366
SphericalCSNNPtr PROJStringParser::Private::buildSphericalCS(
11367
1.51k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11368
1.51k
    auto &step = steps_[iStep];
11369
1.51k
    assert(iUnitConvert < 0 ||
11370
1.51k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11371
11372
1.51k
    UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
11373
1.51k
    if (iUnitConvert >= 0) {
11374
5
        auto &stepUnitConvert = steps_[iUnitConvert];
11375
5
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11376
5
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11377
5
        if (stepUnitConvert.inverted) {
11378
0
            std::swap(xy_in, xy_out);
11379
0
        }
11380
5
        if (iUnitConvert < iStep) {
11381
0
            std::swap(xy_in, xy_out);
11382
0
        }
11383
5
        if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
11384
5
            (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
11385
5
            throw ParsingException("unhandled values for xy_in and/or xy_out");
11386
5
        }
11387
0
        if (*xy_out == "rad") {
11388
0
            angularUnit = UnitOfMeasure::RADIAN;
11389
0
        } else if (*xy_out == "grad") {
11390
0
            angularUnit = UnitOfMeasure::GRAD;
11391
0
        }
11392
0
    }
11393
11394
1.51k
    std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
11395
1.51k
        step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
11396
11397
1.51k
    return SphericalCS::create(emptyPropertyMap, axis[0], axis[1]);
11398
1.51k
}
11399
11400
// ---------------------------------------------------------------------------
11401
11402
static double getNumericValue(const std::string &paramValue,
11403
45.5k
                              bool *pHasError = nullptr) {
11404
45.5k
    bool success;
11405
45.5k
    double value = c_locale_stod(paramValue, success);
11406
45.5k
    if (pHasError)
11407
18.9k
        *pHasError = !success;
11408
45.5k
    return value;
11409
45.5k
}
11410
11411
// ---------------------------------------------------------------------------
11412
namespace {
11413
56.6k
template <class T> inline void ignoreRetVal(T) {}
11414
} // namespace
11415
11416
GeodeticCRSNNPtr PROJStringParser::Private::buildGeodeticCRS(
11417
56.6k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11418
56.6k
    auto &step = steps_[iStep];
11419
11420
56.6k
    const bool l_isGeographicStep = isGeographicStep(step.name);
11421
56.6k
    const auto &title = l_isGeographicStep ? title_ : emptyString;
11422
11423
    // units=m is often found in the wild.
11424
    // No need to create a extension string for this
11425
56.6k
    ignoreRetVal(hasParamValue(step, "units"));
11426
11427
56.6k
    auto datum = buildDatum(step, title);
11428
11429
56.6k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
11430
56.6k
                                   title.empty() ? "unknown" : title);
11431
11432
56.6k
    if (l_isGeographicStep &&
11433
56.6k
        (hasUnusedParameters(step) ||
11434
26.1k
         getNumericValue(getParamValue(step, "lon_0")) != 0.0)) {
11435
1.23k
        props.set("EXTENSION_PROJ4", projString_);
11436
1.23k
    }
11437
56.6k
    props.set("IMPLICIT_CS", true);
11438
11439
56.6k
    if (!hasParamValue(step, "geoc")) {
11440
55.0k
        auto cs =
11441
55.0k
            buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
11442
11443
55.0k
        return GeographicCRS::create(props, datum, cs);
11444
55.0k
    } else {
11445
1.59k
        auto cs =
11446
1.59k
            buildSphericalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
11447
11448
1.59k
        return GeodeticCRS::create(props, datum, cs);
11449
1.59k
    }
11450
56.6k
}
11451
11452
// ---------------------------------------------------------------------------
11453
11454
GeodeticCRSNNPtr
11455
3.43k
PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) {
11456
3.43k
    auto &step = steps_[iStep];
11457
11458
3.43k
    assert(isGeocentricStep(step.name) || isTopocentricStep(step.name));
11459
3.43k
    assert(iUnitConvert < 0 ||
11460
3.43k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11461
11462
3.43k
    const auto &title = title_;
11463
11464
3.43k
    auto datum = buildDatum(step, title);
11465
11466
3.43k
    UnitOfMeasure unit = buildUnit(step, "units", "");
11467
3.43k
    if (iUnitConvert >= 0) {
11468
0
        auto &stepUnitConvert = steps_[iUnitConvert];
11469
0
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11470
0
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11471
0
        const std::string *z_in = &getParamValue(stepUnitConvert, "z_in");
11472
0
        const std::string *z_out = &getParamValue(stepUnitConvert, "z_out");
11473
0
        if (stepUnitConvert.inverted) {
11474
0
            std::swap(xy_in, xy_out);
11475
0
            std::swap(z_in, z_out);
11476
0
        }
11477
0
        if (xy_in->empty() || xy_out->empty() || *xy_in != "m" ||
11478
0
            *z_in != "m" || *xy_out != *z_out) {
11479
0
            throw ParsingException(
11480
0
                "unhandled values for xy_in, z_in, xy_out or z_out");
11481
0
        }
11482
11483
0
        const LinearUnitDesc *unitsMatch = nullptr;
11484
0
        try {
11485
0
            double to_meter_value = c_locale_stod(*xy_out);
11486
0
            unitsMatch = getLinearUnits(to_meter_value);
11487
0
            if (unitsMatch == nullptr) {
11488
0
                unit = _buildUnit(to_meter_value);
11489
0
            }
11490
0
        } catch (const std::invalid_argument &) {
11491
0
            unitsMatch = getLinearUnits(*xy_out);
11492
0
            if (!unitsMatch) {
11493
0
                throw ParsingException(
11494
0
                    "unhandled values for xy_in, z_in, xy_out or z_out");
11495
0
            }
11496
0
            unit = _buildUnit(unitsMatch);
11497
0
        }
11498
0
    }
11499
11500
3.43k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
11501
3.43k
                                   title.empty() ? "unknown" : title);
11502
3.43k
    auto cs = CartesianCS::createGeocentric(unit);
11503
11504
3.43k
    if (hasUnusedParameters(step)) {
11505
1.32k
        props.set("EXTENSION_PROJ4", projString_);
11506
1.32k
    }
11507
11508
3.43k
    return GeodeticCRS::create(props, datum, cs);
11509
3.43k
}
11510
11511
// ---------------------------------------------------------------------------
11512
11513
CRSNNPtr
11514
PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep,
11515
59.5k
                                                           CRSNNPtr crs) {
11516
59.5k
    auto &step = steps_[iStep];
11517
59.5k
    const auto &nadgrids = getParamValue(step, "nadgrids");
11518
59.5k
    const auto &towgs84 = getParamValue(step, "towgs84");
11519
    // nadgrids has the priority over towgs84
11520
59.5k
    if (!ignoreNadgrids_ && !nadgrids.empty()) {
11521
2.08k
        crs = BoundCRS::createFromNadgrids(crs, nadgrids);
11522
57.4k
    } else if (!towgs84.empty()) {
11523
2.88k
        std::vector<double> towgs84Values;
11524
2.88k
        const auto tokens = split(towgs84, ',');
11525
13.4k
        for (const auto &str : tokens) {
11526
13.4k
            try {
11527
13.4k
                towgs84Values.push_back(c_locale_stod(str));
11528
13.4k
            } catch (const std::invalid_argument &) {
11529
78
                throw ParsingException("Non numerical value in towgs84 clause");
11530
78
            }
11531
13.4k
        }
11532
11533
2.81k
        if (towgs84Values.size() == 7 && dbContext_) {
11534
1.22k
            if (dbContext_->toWGS84AutocorrectWrongValues(
11535
1.22k
                    towgs84Values[0], towgs84Values[1], towgs84Values[2],
11536
1.22k
                    towgs84Values[3], towgs84Values[4], towgs84Values[5],
11537
1.22k
                    towgs84Values[6])) {
11538
0
                for (auto &pair : step.paramValues) {
11539
0
                    if (ci_equal(pair.key, "towgs84")) {
11540
0
                        pair.value.clear();
11541
0
                        for (int i = 0; i < 7; ++i) {
11542
0
                            if (i > 0)
11543
0
                                pair.value += ',';
11544
0
                            pair.value += internal::toString(towgs84Values[i]);
11545
0
                        }
11546
0
                        break;
11547
0
                    }
11548
0
                }
11549
0
            }
11550
1.22k
        }
11551
11552
2.81k
        crs = BoundCRS::createFromTOWGS84(crs, towgs84Values);
11553
2.81k
    }
11554
11555
59.4k
    const auto &geoidgrids = getParamValue(step, "geoidgrids");
11556
59.4k
    if (!geoidgrids.empty()) {
11557
3.40k
        auto vdatum = VerticalReferenceFrame::create(
11558
3.40k
            PropertyMap().set(common::IdentifiedObject::NAME_KEY,
11559
3.40k
                              "unknown using geoidgrids=" + geoidgrids));
11560
11561
3.40k
        const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter");
11562
11563
3.40k
        auto vcrs =
11564
3.40k
            VerticalCRS::create(createMapWithUnknownName(), vdatum,
11565
3.40k
                                VerticalCS::createGravityRelatedHeight(unit));
11566
11567
3.40k
        CRSNNPtr geogCRS = GeographicCRS::EPSG_4979; // default
11568
3.40k
        const auto &geoid_crs = getParamValue(step, "geoid_crs");
11569
3.40k
        if (!geoid_crs.empty()) {
11570
1.95k
            if (geoid_crs == "WGS84") {
11571
                // nothing to do
11572
1.25k
            } else if (geoid_crs == "horizontal_crs") {
11573
660
                auto geogCRSOfCompoundCRS = crs->extractGeographicCRS();
11574
660
                if (geogCRSOfCompoundCRS &&
11575
660
                    geogCRSOfCompoundCRS->primeMeridian()
11576
660
                            ->longitude()
11577
660
                            .getSIValue() == 0 &&
11578
660
                    geogCRSOfCompoundCRS->coordinateSystem()
11579
646
                            ->axisList()[0]
11580
646
                            ->unit() == UnitOfMeasure::DEGREE) {
11581
646
                    geogCRS = geogCRSOfCompoundCRS->promoteTo3D(std::string(),
11582
646
                                                                nullptr);
11583
646
                } else if (geogCRSOfCompoundCRS) {
11584
14
                    auto geogCRSOfCompoundCRSDatum =
11585
14
                        geogCRSOfCompoundCRS->datumNonNull(nullptr);
11586
14
                    geogCRS = GeographicCRS::create(
11587
14
                        createMapWithUnknownName(),
11588
14
                        datum::GeodeticReferenceFrame::create(
11589
14
                            util::PropertyMap().set(
11590
14
                                common::IdentifiedObject::NAME_KEY,
11591
14
                                geogCRSOfCompoundCRSDatum->nameStr() +
11592
14
                                    " (with Greenwich prime meridian)"),
11593
14
                            geogCRSOfCompoundCRSDatum->ellipsoid(),
11594
14
                            util::optional<std::string>(),
11595
14
                            datum::PrimeMeridian::GREENWICH),
11596
14
                        EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight(
11597
14
                            UnitOfMeasure::DEGREE, UnitOfMeasure::METRE));
11598
14
                }
11599
660
            } else {
11600
37
                throw ParsingException("Unsupported value for geoid_crs: "
11601
37
                                       "should be 'WGS84' or 'horizontal_crs'");
11602
37
            }
11603
1.95k
        }
11604
3.36k
        auto transformation =
11605
3.36k
            Transformation::createGravityRelatedHeightToGeographic3D(
11606
3.36k
                PropertyMap().set(IdentifiedObject::NAME_KEY,
11607
3.36k
                                  "unknown to " + geogCRS->nameStr() +
11608
3.36k
                                      " ellipsoidal height"),
11609
3.36k
                VerticalCRS::create(createMapWithUnknownName(), vdatum,
11610
3.36k
                                    VerticalCS::createGravityRelatedHeight(
11611
3.36k
                                        common::UnitOfMeasure::METRE)),
11612
3.36k
                geogCRS, nullptr, geoidgrids,
11613
3.36k
                std::vector<PositionalAccuracyNNPtr>());
11614
3.36k
        auto boundvcrs = BoundCRS::create(vcrs, geogCRS, transformation);
11615
11616
3.36k
        crs = CompoundCRS::create(createMapWithUnknownName(),
11617
3.36k
                                  std::vector<CRSNNPtr>{crs, boundvcrs});
11618
3.36k
    }
11619
11620
59.4k
    return crs;
11621
59.4k
}
11622
11623
// ---------------------------------------------------------------------------
11624
11625
static double getAngularValue(const std::string &paramValue,
11626
27.3k
                              bool *pHasError = nullptr) {
11627
27.3k
    char *endptr = nullptr;
11628
27.3k
    double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG;
11629
27.3k
    if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) {
11630
290
        if (pHasError)
11631
269
            *pHasError = true;
11632
290
        return 0.0;
11633
290
    }
11634
27.0k
    if (pHasError)
11635
22.1k
        *pHasError = false;
11636
27.0k
    return value;
11637
27.3k
}
11638
11639
// ---------------------------------------------------------------------------
11640
11641
48.6k
static bool is_in_stringlist(const std::string &str, const char *stringlist) {
11642
48.6k
    if (str.empty())
11643
0
        return false;
11644
48.6k
    const char *haystack = stringlist;
11645
239k
    while (true) {
11646
239k
        const char *res = strstr(haystack, str.c_str());
11647
239k
        if (res == nullptr)
11648
38.6k
            return false;
11649
200k
        if ((res == stringlist || res[-1] == ',') &&
11650
200k
            (res[str.size()] == ',' || res[str.size()] == '\0'))
11651
9.96k
            return true;
11652
190k
        haystack += str.size();
11653
190k
    }
11654
48.6k
}
11655
11656
// ---------------------------------------------------------------------------
11657
11658
CRSNNPtr
11659
PROJStringParser::Private::buildProjectedCRS(int iStep,
11660
                                             const GeodeticCRSNNPtr &geodCRS,
11661
30.6k
                                             int iUnitConvert, int iAxisSwap) {
11662
30.6k
    auto &step = steps_[iStep];
11663
30.6k
    const auto mappings = getMappingsFromPROJName(step.name);
11664
30.6k
    const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0];
11665
11666
30.6k
    bool foundStrictlyMatchingMapping = false;
11667
30.6k
    if (mappings.size() >= 2) {
11668
        // To distinguish for example +ortho from +ortho +f=0
11669
9.64k
        bool allMappingsHaveAuxParam = true;
11670
25.3k
        for (const auto *mappingIter : mappings) {
11671
25.3k
            if (mappingIter->proj_name_aux == nullptr) {
11672
16.3k
                allMappingsHaveAuxParam = false;
11673
16.3k
            }
11674
25.3k
            if (mappingIter->proj_name_aux != nullptr &&
11675
25.3k
                strchr(mappingIter->proj_name_aux, '=') == nullptr &&
11676
25.3k
                hasParamValue(step, mappingIter->proj_name_aux)) {
11677
231
                foundStrictlyMatchingMapping = true;
11678
231
                mapping = mappingIter;
11679
231
                break;
11680
25.1k
            } else if (mappingIter->proj_name_aux != nullptr &&
11681
25.1k
                       strchr(mappingIter->proj_name_aux, '=') != nullptr) {
11682
4.96k
                const auto tokens = split(mappingIter->proj_name_aux, '=');
11683
4.96k
                if (tokens.size() == 2 &&
11684
4.96k
                    getParamValue(step, tokens[0]) == tokens[1]) {
11685
450
                    foundStrictlyMatchingMapping = true;
11686
450
                    mapping = mappingIter;
11687
450
                    break;
11688
450
                }
11689
4.96k
            }
11690
25.3k
        }
11691
9.64k
        if (allMappingsHaveAuxParam && !foundStrictlyMatchingMapping) {
11692
61
            mapping = nullptr;
11693
61
        }
11694
9.64k
    }
11695
11696
30.6k
    if (mapping && !foundStrictlyMatchingMapping) {
11697
20.2k
        mapping = selectSphericalOrEllipsoidal(mapping, geodCRS);
11698
20.2k
    }
11699
11700
30.6k
    assert(isProjectedStep(step.name));
11701
30.6k
    assert(iUnitConvert < 0 ||
11702
30.6k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11703
11704
30.6k
    const auto &title = title_;
11705
11706
30.6k
    if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo(
11707
30.6k
            geodCRS->primeMeridian()->longitude(),
11708
30.6k
            util::IComparable::Criterion::EQUIVALENT)) {
11709
0
        throw ParsingException("inconsistent pm values between projectedCRS "
11710
0
                               "and its base geographicalCRS");
11711
0
    }
11712
11713
30.6k
    auto axisType = AxisType::REGULAR;
11714
30.6k
    bool bWebMercator = false;
11715
30.6k
    std::string webMercatorName("WGS 84 / Pseudo-Mercator");
11716
11717
30.6k
    if (step.name == "tmerc" &&
11718
30.6k
        ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) ||
11719
2.13k
         (iAxisSwap > 0 &&
11720
2.13k
          getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) {
11721
20
        mapping =
11722
20
            getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED);
11723
30.6k
    } else if (step.name == "etmerc") {
11724
312
        mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
11725
30.3k
    } else if (step.name == "lcc") {
11726
313
        const auto &lat_0 = getParamValue(step, "lat_0");
11727
313
        const auto &lat_1 = getParamValue(step, "lat_1");
11728
313
        const auto &lat_2 = getParamValue(step, "lat_2");
11729
313
        const auto &k = getParamValueK(step);
11730
313
        if (lat_2.empty() && !lat_0.empty() && !lat_1.empty()) {
11731
107
            if (lat_0 == lat_1 ||
11732
                // For some reason with gcc 5.3.1-14ubuntu2 32bit, the following
11733
                // comparison returns false even if lat_0 == lat_1. Smells like
11734
                // a compiler bug
11735
107
                getAngularValue(lat_0) == getAngularValue(lat_1)) {
11736
33
                mapping =
11737
33
                    getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
11738
74
            } else {
11739
74
                mapping = getMapping(
11740
74
                    EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B);
11741
74
            }
11742
206
        } else if (!k.empty() && getNumericValue(k) != 1.0) {
11743
2
            mapping = getMapping(
11744
2
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
11745
204
        } else {
11746
204
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
11747
204
        }
11748
30.0k
    } else if (step.name == "aeqd" && hasParamValue(step, "guam")) {
11749
53
        mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION);
11750
29.9k
    } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") {
11751
26
        mapping =
11752
26
            getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X);
11753
29.9k
    } else if (step.name == "geos") {
11754
231
        mapping =
11755
231
            getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y);
11756
29.7k
    } else if (step.name == "omerc") {
11757
685
        if (hasParamValue(step, "no_rot")) {
11758
0
            mapping = nullptr;
11759
685
        } else if (hasParamValue(step, "no_uoff") ||
11760
685
                   hasParamValue(step, "no_off")) {
11761
252
            mapping =
11762
252
                getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A);
11763
433
        } else if (hasParamValue(step, "lat_1") &&
11764
433
                   hasParamValue(step, "lon_1") &&
11765
433
                   hasParamValue(step, "lat_2") &&
11766
433
                   hasParamValue(step, "lon_2")) {
11767
0
            mapping = getMapping(
11768
0
                PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN);
11769
433
        } else {
11770
433
            mapping =
11771
433
                getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
11772
433
        }
11773
29.0k
    } else if (step.name == "somerc") {
11774
597
        mapping =
11775
597
            getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
11776
597
        if (!hasParamValue(step, "alpha") && !hasParamValue(step, "gamma") &&
11777
597
            !hasParamValue(step, "lonc")) {
11778
299
            step.paramValues.emplace_back(Step::KeyValue("alpha", "90"));
11779
299
            step.paramValues.emplace_back(Step::KeyValue("gamma", "90"));
11780
299
            step.paramValues.emplace_back(
11781
299
                Step::KeyValue("lonc", getParamValue(step, "lon_0")));
11782
299
        }
11783
28.4k
    } else if (step.name == "krovak" &&
11784
28.4k
               ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
11785
1.97k
                 !hasParamValue(step, "czech")) ||
11786
1.97k
                (iAxisSwap > 0 &&
11787
1.67k
                 getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
11788
1.67k
                 !hasParamValue(step, "czech")))) {
11789
295
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
11790
28.1k
    } else if (step.name == "krovak" && iAxisSwap < 0 &&
11791
28.1k
               hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
11792
116
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
11793
28.0k
    } else if (step.name == "mod_krovak" &&
11794
28.0k
               ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
11795
142
                 !hasParamValue(step, "czech")) ||
11796
142
                (iAxisSwap > 0 &&
11797
134
                 getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
11798
134
                 !hasParamValue(step, "czech")))) {
11799
8
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED);
11800
28.0k
    } else if (step.name == "mod_krovak" && iAxisSwap < 0 &&
11801
28.0k
               hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
11802
28
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED);
11803
27.9k
    } else if (step.name == "merc") {
11804
1.54k
        if (hasParamValue(step, "a") && hasParamValue(step, "b") &&
11805
1.54k
            getParamValue(step, "a") == getParamValue(step, "b") &&
11806
1.54k
            (!hasParamValue(step, "lat_ts") ||
11807
786
             getAngularValue(getParamValue(step, "lat_ts")) == 0.0) &&
11808
1.54k
            getNumericValue(getParamValueK(step)) == 1.0 &&
11809
1.54k
            getParamValue(step, "nadgrids") == "@null") {
11810
734
            mapping = getMapping(
11811
734
                EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR);
11812
6.64k
            for (size_t i = 0; i < step.paramValues.size(); ++i) {
11813
6.64k
                if (ci_equal(step.paramValues[i].key, "nadgrids")) {
11814
734
                    ignoreNadgrids_ = true;
11815
734
                    break;
11816
734
                }
11817
6.64k
            }
11818
734
            if (getNumericValue(getParamValue(step, "a")) == 6378137 &&
11819
734
                getAngularValue(getParamValue(step, "lon_0")) == 0.0 &&
11820
734
                getAngularValue(getParamValue(step, "lat_0")) == 0.0 &&
11821
734
                getAngularValue(getParamValue(step, "x_0")) == 0.0 &&
11822
734
                getAngularValue(getParamValue(step, "y_0")) == 0.0) {
11823
678
                bWebMercator = true;
11824
678
                if (hasParamValue(step, "units") &&
11825
678
                    getParamValue(step, "units") != "m") {
11826
26
                    webMercatorName +=
11827
26
                        " (unit " + getParamValue(step, "units") + ')';
11828
26
                }
11829
678
            }
11830
807
        } else if (hasParamValue(step, "lat_ts")) {
11831
222
            if (hasParamValue(step, "R_C") &&
11832
222
                !geodCRS->ellipsoid()->isSphere() &&
11833
222
                getAngularValue(getParamValue(step, "lat_ts")) != 0) {
11834
0
                throw ParsingException("lat_ts != 0 not supported for "
11835
0
                                       "spherical Mercator on an ellipsoid");
11836
0
            }
11837
222
            mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
11838
585
        } else if (hasParamValue(step, "R_C")) {
11839
0
            const auto &k = getParamValueK(step);
11840
0
            if (!k.empty() && getNumericValue(k) != 1.0) {
11841
0
                if (geodCRS->ellipsoid()->isSphere()) {
11842
0
                    mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
11843
0
                } else {
11844
0
                    throw ParsingException(
11845
0
                        "k_0 != 1 not supported for spherical Mercator on an "
11846
0
                        "ellipsoid");
11847
0
                }
11848
0
            } else {
11849
0
                mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_SPHERICAL);
11850
0
            }
11851
585
        } else {
11852
585
            mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
11853
585
        }
11854
26.4k
    } else if (step.name == "stere") {
11855
387
        if (hasParamValue(step, "lat_0") &&
11856
387
            std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
11857
204
                      90.0) < 1e-10) {
11858
153
            const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
11859
153
            if (lat_0 > 0) {
11860
151
                axisType = AxisType::NORTH_POLE;
11861
151
            } else {
11862
2
                axisType = AxisType::SOUTH_POLE;
11863
2
            }
11864
153
            const auto &lat_ts = getParamValue(step, "lat_ts");
11865
153
            const auto &k = getParamValueK(step);
11866
153
            if (!lat_ts.empty() &&
11867
153
                std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 &&
11868
153
                !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) {
11869
4
                throw ParsingException("lat_ts != lat_0 and k != 1 not "
11870
4
                                       "supported for Polar Stereographic");
11871
4
            }
11872
149
            if (!lat_ts.empty() &&
11873
149
                (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) {
11874
24
                mapping =
11875
24
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B);
11876
125
            } else {
11877
125
                mapping =
11878
125
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
11879
125
            }
11880
234
        } else {
11881
234
            mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC);
11882
234
        }
11883
26.0k
    } else if (step.name == "laea") {
11884
329
        if (hasParamValue(step, "lat_0") &&
11885
329
            std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
11886
226
                      90.0) < 1e-10) {
11887
136
            const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
11888
136
            if (lat_0 > 0) {
11889
122
                axisType = AxisType::NORTH_POLE;
11890
122
            } else {
11891
14
                axisType = AxisType::SOUTH_POLE;
11892
14
            }
11893
136
        }
11894
25.7k
    } else if (step.name == "ortho") {
11895
330
        const std::string &k = getParamValueK(step);
11896
330
        if ((!k.empty() && getNumericValue(k) != 1.0) ||
11897
330
            (hasParamValue(step, "alpha") &&
11898
312
             getNumericValue(getParamValue(step, "alpha")) != 0.0)) {
11899
18
            mapping = getMapping(EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC);
11900
18
        }
11901
330
    }
11902
11903
30.6k
    UnitOfMeasure unit = buildUnit(step, "units", "to_meter");
11904
30.6k
    if (iUnitConvert >= 0) {
11905
291
        auto &stepUnitConvert = steps_[iUnitConvert];
11906
291
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11907
291
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11908
291
        if (stepUnitConvert.inverted) {
11909
0
            std::swap(xy_in, xy_out);
11910
0
        }
11911
291
        if (xy_in->empty() || xy_out->empty() || *xy_in != "m") {
11912
291
            if (step.name != "ob_tran") {
11913
5
                throw ParsingException(
11914
5
                    "unhandled values for xy_in and/or xy_out");
11915
5
            }
11916
291
        }
11917
11918
286
        const LinearUnitDesc *unitsMatch = nullptr;
11919
286
        try {
11920
286
            double to_meter_value = c_locale_stod(*xy_out);
11921
286
            unitsMatch = getLinearUnits(to_meter_value);
11922
286
            if (unitsMatch == nullptr) {
11923
2
                unit = _buildUnit(to_meter_value);
11924
2
            }
11925
286
        } catch (const std::invalid_argument &) {
11926
284
            unitsMatch = getLinearUnits(*xy_out);
11927
284
            if (!unitsMatch) {
11928
282
                if (step.name != "ob_tran") {
11929
0
                    throw ParsingException(
11930
0
                        "unhandled values for xy_in and/or xy_out");
11931
0
                }
11932
282
            } else {
11933
2
                unit = _buildUnit(unitsMatch);
11934
2
            }
11935
284
        }
11936
286
    }
11937
11938
30.6k
    ConversionPtr conv;
11939
11940
30.6k
    auto mapWithUnknownName = createMapWithUnknownName();
11941
11942
30.6k
    if (step.name == "utm") {
11943
1.20k
        const int zone = std::atoi(getParamValue(step, "zone").c_str());
11944
1.20k
        const bool north = !hasParamValue(step, "south");
11945
1.20k
        conv =
11946
1.20k
            Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable();
11947
29.4k
    } else if (mapping) {
11948
11949
21.7k
        auto methodMap =
11950
21.7k
            PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name);
11951
21.7k
        if (mapping->epsg_code) {
11952
11.9k
            methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG)
11953
11.9k
                .set(Identifier::CODE_KEY, mapping->epsg_code);
11954
11.9k
        }
11955
21.7k
        std::vector<OperationParameterNNPtr> parameters;
11956
21.7k
        std::vector<ParameterValueNNPtr> values;
11957
115k
        for (int i = 0; mapping->params[i] != nullptr; i++) {
11958
93.8k
            const auto *param = mapping->params[i];
11959
93.8k
            std::string proj_name(param->proj_name ? param->proj_name : "");
11960
93.8k
            const std::string *paramValue =
11961
93.8k
                (proj_name == "k" || proj_name == "k_0") ? &getParamValueK(step)
11962
93.8k
                : !proj_name.empty() ? &getParamValue(step, proj_name)
11963
86.8k
                                     : &emptyString;
11964
93.8k
            double value = 0;
11965
93.8k
            if (!paramValue->empty()) {
11966
32.7k
                bool hasError = false;
11967
32.7k
                if (param->unit_type == UnitOfMeasure::Type::ANGULAR) {
11968
14.9k
                    value = getAngularValue(*paramValue, &hasError);
11969
17.7k
                } else {
11970
17.7k
                    value = getNumericValue(*paramValue, &hasError);
11971
17.7k
                }
11972
32.7k
                if (hasError) {
11973
104
                    throw ParsingException("invalid value for " + proj_name);
11974
104
                }
11975
32.7k
            }
11976
            // For omerc, if gamma is missing, the default value is
11977
            // alpha
11978
61.1k
            else if (step.name == "omerc" && proj_name == "gamma") {
11979
589
                paramValue = &getParamValue(step, "alpha");
11980
589
                if (!paramValue->empty()) {
11981
377
                    value = getAngularValue(*paramValue);
11982
377
                }
11983
60.5k
            } else if (step.name == "krovak" || step.name == "mod_krovak") {
11984
                // Keep it in sync with defaults of krovak.cpp
11985
9.24k
                if (param->epsg_code ==
11986
9.24k
                    EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE) {
11987
1.19k
                    value = 49.5;
11988
8.05k
                } else if (param->epsg_code ==
11989
8.05k
                           EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) {
11990
1.18k
                    value = 24.833333333333333333;
11991
6.86k
                } else if (param->epsg_code ==
11992
6.86k
                           EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) {
11993
1.20k
                    value = 30.28813975277777776;
11994
5.65k
                } else if (
11995
5.65k
                    param->epsg_code ==
11996
5.65k
                    EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) {
11997
2.10k
                    value = 78.5;
11998
3.55k
                } else if (
11999
3.55k
                    param->epsg_code ==
12000
3.55k
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL) {
12001
1.18k
                    value = 0.9999;
12002
1.18k
                }
12003
51.2k
            } else if (step.name == "cea" && proj_name == "lat_ts") {
12004
649
                paramValue = &getParamValueK(step);
12005
649
                if (!paramValue->empty()) {
12006
7
                    bool hasError = false;
12007
7
                    const double k = getNumericValue(*paramValue, &hasError);
12008
7
                    if (hasError) {
12009
5
                        throw ParsingException("invalid value for k/k_0");
12010
5
                    }
12011
2
                    if (k >= 0 && k <= 1) {
12012
2
                        const double es =
12013
2
                            geodCRS->ellipsoid()->squaredEccentricity();
12014
2
                        if (es < 0 || es == 1) {
12015
0
                            throw ParsingException("Invalid flattening");
12016
0
                        }
12017
2
                        value =
12018
2
                            Angle(acos(k * sqrt((1 - es) / (1 - k * k * es))),
12019
2
                                  UnitOfMeasure::RADIAN)
12020
2
                                .convertToUnit(UnitOfMeasure::DEGREE);
12021
2
                    } else {
12022
0
                        throw ParsingException("k/k_0 should be in [0,1]");
12023
0
                    }
12024
2
                }
12025
50.6k
            } else if (param->unit_type == UnitOfMeasure::Type::SCALE) {
12026
3.54k
                value = 1;
12027
47.0k
            } else if (step.name == "peirce_q" && proj_name == "lat_0") {
12028
0
                value = 90;
12029
0
            }
12030
12031
93.7k
            PropertyMap propertiesParameter;
12032
93.7k
            propertiesParameter.set(IdentifiedObject::NAME_KEY,
12033
93.7k
                                    param->wkt2_name);
12034
93.7k
            if (param->epsg_code) {
12035
91.9k
                propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code);
12036
91.9k
                propertiesParameter.set(Identifier::CODESPACE_KEY,
12037
91.9k
                                        Identifier::EPSG);
12038
91.9k
            }
12039
93.7k
            parameters.push_back(
12040
93.7k
                OperationParameter::create(propertiesParameter));
12041
            // In PROJ convention, angular parameters are always in degree
12042
            // and linear parameters always in metre.
12043
93.7k
            double valRounded =
12044
93.7k
                param->unit_type == UnitOfMeasure::Type::LINEAR
12045
93.7k
                    ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit)
12046
93.7k
                    : value;
12047
93.7k
            if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) {
12048
83.0k
                valRounded = std::round(valRounded);
12049
83.0k
            }
12050
93.7k
            values.push_back(ParameterValue::create(
12051
93.7k
                Measure(valRounded,
12052
93.7k
                        param->unit_type == UnitOfMeasure::Type::ANGULAR
12053
93.7k
                            ? UnitOfMeasure::DEGREE
12054
93.7k
                        : param->unit_type == UnitOfMeasure::Type::LINEAR ? unit
12055
50.7k
                        : param->unit_type == UnitOfMeasure::Type::SCALE
12056
6.96k
                            ? UnitOfMeasure::SCALE_UNITY
12057
6.96k
                            : UnitOfMeasure::NONE)));
12058
93.7k
        }
12059
12060
21.6k
        if (step.name == "tmerc" && hasParamValue(step, "approx")) {
12061
300
            methodMap.set("proj_method", "tmerc approx");
12062
21.3k
        } else if (step.name == "utm" && hasParamValue(step, "approx")) {
12063
0
            methodMap.set("proj_method", "utm approx");
12064
0
        }
12065
12066
21.6k
        conv = Conversion::create(mapWithUnknownName, methodMap, parameters,
12067
21.6k
                                  values)
12068
21.6k
                   .as_nullable();
12069
21.6k
    } else {
12070
7.64k
        std::vector<OperationParameterNNPtr> parameters;
12071
7.64k
        std::vector<ParameterValueNNPtr> values;
12072
7.64k
        std::string methodName = "PROJ " + step.name;
12073
31.9k
        for (const auto &param : step.paramValues) {
12074
31.9k
            if (is_in_stringlist(param.key,
12075
31.9k
                                 "wktext,no_defs,datum,ellps,a,b,R,f,rf,"
12076
31.9k
                                 "towgs84,nadgrids,geoidgrids,"
12077
31.9k
                                 "units,to_meter,vunits,vto_meter,type")) {
12078
8.71k
                continue;
12079
8.71k
            }
12080
23.2k
            if (param.value.empty()) {
12081
9.39k
                methodName += " " + param.key;
12082
13.8k
            } else if (isalpha(param.value[0])) {
12083
5.18k
                methodName += " " + param.key + "=" + param.value;
12084
8.68k
            } else {
12085
8.68k
                parameters.push_back(OperationParameter::create(
12086
8.68k
                    PropertyMap().set(IdentifiedObject::NAME_KEY, param.key)));
12087
8.68k
                bool hasError = false;
12088
8.68k
                if (is_in_stringlist(param.key, "x_0,y_0,h,h_0")) {
12089
690
                    double value = getNumericValue(param.value, &hasError);
12090
690
                    values.push_back(ParameterValue::create(
12091
690
                        Measure(value, UnitOfMeasure::METRE)));
12092
7.99k
                } else if (is_in_stringlist(
12093
7.99k
                               param.key,
12094
7.99k
                               "k,k_0,"
12095
7.99k
                               "north_square,south_square," // rhealpix
12096
7.99k
                               "n,m,"                       // sinu
12097
7.99k
                               "q,"                         // urm5
12098
7.99k
                               "path,lsat,"                 // lsat
12099
7.99k
                               "W,M,"                       // hammer
12100
7.99k
                               "aperture,resolution,"       // isea
12101
7.99k
                               )) {
12102
565
                    double value = getNumericValue(param.value, &hasError);
12103
565
                    values.push_back(ParameterValue::create(
12104
565
                        Measure(value, UnitOfMeasure::SCALE_UNITY)));
12105
7.42k
                } else {
12106
7.42k
                    double value = getAngularValue(param.value, &hasError);
12107
7.42k
                    values.push_back(ParameterValue::create(
12108
7.42k
                        Measure(value, UnitOfMeasure::DEGREE)));
12109
7.42k
                }
12110
8.68k
                if (hasError) {
12111
197
                    throw ParsingException("invalid value for " + param.key);
12112
197
                }
12113
8.68k
            }
12114
23.2k
        }
12115
7.45k
        conv = Conversion::create(
12116
7.45k
                   mapWithUnknownName,
12117
7.45k
                   PropertyMap().set(IdentifiedObject::NAME_KEY, methodName),
12118
7.45k
                   parameters, values)
12119
7.45k
                   .as_nullable();
12120
12121
7.45k
        for (const char *substr :
12122
7.45k
             {"PROJ ob_tran o_proj=longlat", "PROJ ob_tran o_proj=lonlat",
12123
29.7k
              "PROJ ob_tran o_proj=latlon", "PROJ ob_tran o_proj=latlong"}) {
12124
29.7k
            if (starts_with(methodName, substr)) {
12125
68
                auto geogCRS =
12126
68
                    util::nn_dynamic_pointer_cast<GeographicCRS>(geodCRS);
12127
68
                if (geogCRS) {
12128
68
                    return DerivedGeographicCRS::create(
12129
68
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
12130
68
                                          "unnamed"),
12131
68
                        NN_NO_CHECK(geogCRS), NN_NO_CHECK(conv),
12132
68
                        buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap,
12133
68
                                           false));
12134
68
                }
12135
68
            }
12136
29.7k
        }
12137
7.45k
    }
12138
12139
30.2k
    std::vector<CoordinateSystemAxisNNPtr> axis =
12140
30.2k
        processAxisSwap(step, unit, iAxisSwap, axisType, false);
12141
12142
30.2k
    auto csGeodCRS = geodCRS->coordinateSystem();
12143
30.2k
    auto cs = csGeodCRS->axisList().size() == 2
12144
30.2k
                  ? CartesianCS::create(emptyPropertyMap, axis[0], axis[1])
12145
30.2k
                  : CartesianCS::create(emptyPropertyMap, axis[0], axis[1],
12146
1.04k
                                        csGeodCRS->axisList()[2]);
12147
30.2k
    if (isTopocentricStep(step.name)) {
12148
187
        cs = CartesianCS::create(
12149
187
            emptyPropertyMap,
12150
187
            createAxis("topocentric East", "U", AxisDirection::EAST, unit),
12151
187
            createAxis("topocentric North", "V", AxisDirection::NORTH, unit),
12152
187
            createAxis("topocentric Up", "W", AxisDirection::UP, unit));
12153
187
    }
12154
12155
30.2k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
12156
30.2k
                                   title.empty() ? "unknown" : title);
12157
30.2k
    if (hasUnusedParameters(step)) {
12158
6.36k
        props.set("EXTENSION_PROJ4", projString_);
12159
6.36k
    }
12160
12161
30.2k
    props.set("IMPLICIT_CS", true);
12162
12163
30.2k
    CRSNNPtr crs =
12164
30.2k
        bWebMercator
12165
30.2k
            ? createPseudoMercator(
12166
676
                  props.set(IdentifiedObject::NAME_KEY, webMercatorName), cs)
12167
30.2k
            : ProjectedCRS::create(props, geodCRS, NN_NO_CHECK(conv), cs);
12168
12169
30.2k
    return crs;
12170
30.6k
}
12171
12172
//! @endcond
12173
12174
// ---------------------------------------------------------------------------
12175
12176
//! @cond Doxygen_Suppress
12177
static const metadata::ExtentPtr nullExtent{};
12178
12179
718
static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) {
12180
718
    const auto &domains = crs->domains();
12181
718
    if (!domains.empty()) {
12182
208
        return domains[0]->domainOfValidity();
12183
208
    }
12184
510
    return nullExtent;
12185
718
}
12186
12187
//! @endcond
12188
12189
namespace {
12190
struct PJContextHolder {
12191
    PJ_CONTEXT *ctx_;
12192
    bool bFree_;
12193
12194
11.9k
    PJContextHolder(PJ_CONTEXT *ctx, bool bFree) : ctx_(ctx), bFree_(bFree) {}
12195
11.9k
    ~PJContextHolder() {
12196
11.9k
        if (bFree_)
12197
442
            proj_context_destroy(ctx_);
12198
11.9k
    }
12199
    PJContextHolder(const PJContextHolder &) = delete;
12200
    PJContextHolder &operator=(const PJContextHolder &) = delete;
12201
};
12202
} // namespace
12203
12204
// ---------------------------------------------------------------------------
12205
12206
/** \brief Instantiate a sub-class of BaseObject from a PROJ string.
12207
 *
12208
 * The projString must contain +type=crs for the object to be detected as a
12209
 * CRS instead of a CoordinateOperation.
12210
 *
12211
 * @throw ParsingException if the string cannot be parsed.
12212
 */
12213
BaseObjectNNPtr
12214
54.8k
PROJStringParser::createFromPROJString(const std::string &projString) {
12215
12216
    // In some abnormal situations involving init=epsg:XXXX syntax, we could
12217
    // have infinite loop
12218
54.8k
    if (d->ctx_ &&
12219
54.8k
        d->ctx_->projStringParserCreateFromPROJStringRecursionCounter == 2) {
12220
9
        throw ParsingException(
12221
9
            "Infinite recursion in PROJStringParser::createFromPROJString()");
12222
9
    }
12223
12224
54.8k
    d->steps_.clear();
12225
54.8k
    d->title_.clear();
12226
54.8k
    d->globalParamValues_.clear();
12227
54.8k
    d->projString_ = projString;
12228
54.8k
    PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_,
12229
54.8k
                           d->title_);
12230
12231
54.8k
    if (d->steps_.empty()) {
12232
943
        const auto &vunits = d->getGlobalParamValue("vunits");
12233
943
        const auto &vto_meter = d->getGlobalParamValue("vto_meter");
12234
943
        if (!vunits.empty() || !vto_meter.empty()) {
12235
258
            Step fakeStep;
12236
258
            if (!vunits.empty()) {
12237
104
                fakeStep.paramValues.emplace_back(
12238
104
                    Step::KeyValue("vunits", vunits));
12239
104
            }
12240
258
            if (!vto_meter.empty()) {
12241
154
                fakeStep.paramValues.emplace_back(
12242
154
                    Step::KeyValue("vto_meter", vto_meter));
12243
154
            }
12244
258
            auto vdatum =
12245
258
                VerticalReferenceFrame::create(createMapWithUnknownName());
12246
258
            auto vcrs = VerticalCRS::create(
12247
258
                createMapWithUnknownName(), vdatum,
12248
258
                VerticalCS::createGravityRelatedHeight(
12249
258
                    d->buildUnit(fakeStep, "vunits", "vto_meter")));
12250
258
            return vcrs;
12251
258
        }
12252
943
    }
12253
12254
54.5k
    const bool isGeocentricCRS =
12255
54.5k
        ((d->steps_.size() == 1 &&
12256
54.5k
          d->getParamValue(d->steps_[0], "type") == "crs") ||
12257
54.5k
         (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) &&
12258
54.5k
        !d->steps_[0].inverted && isGeocentricStep(d->steps_[0].name);
12259
12260
54.5k
    const bool isTopocentricCRS =
12261
54.5k
        (d->steps_.size() == 1 && isTopocentricStep(d->steps_[0].name) &&
12262
54.5k
         d->getParamValue(d->steps_[0], "type") == "crs");
12263
12264
    // +init=xxxx:yyyy syntax
12265
54.5k
    if (d->steps_.size() == 1 && d->steps_[0].isInit &&
12266
54.5k
        !d->steps_[0].inverted) {
12267
12268
11.9k
        auto ctx = d->ctx_ ? d->ctx_ : proj_context_create();
12269
11.9k
        if (!ctx) {
12270
0
            throw ParsingException("out of memory");
12271
0
        }
12272
11.9k
        PJContextHolder contextHolder(ctx, ctx != d->ctx_);
12273
12274
        // Those used to come from a text init file
12275
        // We only support them in compatibility mode
12276
11.9k
        const std::string &stepName = d->steps_[0].name;
12277
11.9k
        if (ci_starts_with(stepName, "epsg:") ||
12278
11.9k
            ci_starts_with(stepName, "IGNF:")) {
12279
12280
11.6k
            struct BackupContextErrno {
12281
11.6k
                PJ_CONTEXT *m_ctxt = nullptr;
12282
11.6k
                int m_last_errno = 0;
12283
12284
11.6k
                explicit BackupContextErrno(PJ_CONTEXT *ctxtIn)
12285
11.6k
                    : m_ctxt(ctxtIn), m_last_errno(m_ctxt->last_errno) {
12286
11.6k
                    m_ctxt->debug_level = PJ_LOG_ERROR;
12287
11.6k
                }
12288
12289
11.6k
                ~BackupContextErrno() { m_ctxt->last_errno = m_last_errno; }
12290
12291
11.6k
                BackupContextErrno(const BackupContextErrno &) = delete;
12292
11.6k
                BackupContextErrno &
12293
11.6k
                operator=(const BackupContextErrno &) = delete;
12294
11.6k
            };
12295
12296
11.6k
            BackupContextErrno backupContextErrno(ctx);
12297
12298
11.6k
            bool usePROJ4InitRules = d->usePROJ4InitRules_;
12299
11.6k
            if (!usePROJ4InitRules) {
12300
0
                usePROJ4InitRules =
12301
0
                    proj_context_get_use_proj4_init_rules(ctx, FALSE) == TRUE;
12302
0
            }
12303
11.6k
            if (!usePROJ4InitRules) {
12304
0
                throw ParsingException("init=epsg:/init=IGNF: syntax not "
12305
0
                                       "supported in non-PROJ4 emulation mode");
12306
0
            }
12307
12308
11.6k
            char unused[256];
12309
11.6k
            std::string initname(stepName);
12310
11.6k
            initname.resize(initname.find(':'));
12311
11.6k
            int file_found =
12312
11.6k
                pj_find_file(ctx, initname.c_str(), unused, sizeof(unused));
12313
12314
11.6k
            if (!file_found) {
12315
11.6k
                auto obj = createFromUserInput(stepName, d->dbContext_, true);
12316
11.6k
                auto crs = dynamic_cast<CRS *>(obj.get());
12317
12318
11.6k
                bool hasSignificantParamValues = false;
12319
11.6k
                bool hasOver = false;
12320
11.6k
                for (const auto &kv : d->steps_[0].paramValues) {
12321
10.4k
                    if (kv.key == "over") {
12322
79
                        hasOver = true;
12323
10.3k
                    } else if (!((kv.key == "type" && kv.value == "crs") ||
12324
10.3k
                                 kv.key == "wktext" || kv.key == "no_defs")) {
12325
9.55k
                        hasSignificantParamValues = true;
12326
9.55k
                        break;
12327
9.55k
                    }
12328
10.4k
                }
12329
12330
11.6k
                if (crs && !hasSignificantParamValues) {
12331
718
                    PropertyMap properties;
12332
718
                    properties.set(IdentifiedObject::NAME_KEY,
12333
718
                                   d->title_.empty() ? crs->nameStr()
12334
718
                                                     : d->title_);
12335
718
                    if (hasOver) {
12336
43
                        properties.set("OVER", true);
12337
43
                    }
12338
718
                    const auto &extent = getExtent(crs);
12339
718
                    if (extent) {
12340
208
                        properties.set(
12341
208
                            common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
12342
208
                            NN_NO_CHECK(extent));
12343
208
                    }
12344
718
                    auto geogCRS = dynamic_cast<GeographicCRS *>(crs);
12345
718
                    if (geogCRS) {
12346
159
                        const auto &cs = geogCRS->coordinateSystem();
12347
                        // Override with longitude latitude in degrees
12348
159
                        return GeographicCRS::create(
12349
159
                            properties, geogCRS->datum(),
12350
159
                            geogCRS->datumEnsemble(),
12351
159
                            cs->axisList().size() == 2
12352
159
                                ? EllipsoidalCS::createLongitudeLatitude(
12353
146
                                      UnitOfMeasure::DEGREE)
12354
159
                                : EllipsoidalCS::
12355
13
                                      createLongitudeLatitudeEllipsoidalHeight(
12356
13
                                          UnitOfMeasure::DEGREE,
12357
13
                                          cs->axisList()[2]->unit()));
12358
159
                    }
12359
559
                    auto projCRS = dynamic_cast<ProjectedCRS *>(crs);
12360
559
                    if (projCRS) {
12361
                        // Override with easting northing order
12362
452
                        const auto conv = projCRS->derivingConversion();
12363
452
                        if (conv->method()->getEPSGCode() !=
12364
452
                            EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
12365
450
                            return ProjectedCRS::create(
12366
450
                                properties, projCRS->baseCRS(), conv,
12367
450
                                CartesianCS::createEastingNorthing(
12368
450
                                    projCRS->coordinateSystem()
12369
450
                                        ->axisList()[0]
12370
450
                                        ->unit()));
12371
450
                        }
12372
452
                    }
12373
109
                    return obj;
12374
559
                }
12375
10.8k
                auto projStringExportable =
12376
10.8k
                    dynamic_cast<IPROJStringExportable *>(crs);
12377
10.8k
                if (projStringExportable) {
12378
9.10k
                    std::string expanded;
12379
9.10k
                    if (!d->title_.empty()) {
12380
155
                        expanded = "title=";
12381
155
                        expanded +=
12382
155
                            pj_double_quote_string_param_if_needed(d->title_);
12383
155
                    }
12384
39.6k
                    for (const auto &pair : d->steps_[0].paramValues) {
12385
39.6k
                        if (!expanded.empty())
12386
30.6k
                            expanded += ' ';
12387
39.6k
                        expanded += '+';
12388
39.6k
                        expanded += pair.key;
12389
39.6k
                        if (!pair.value.empty()) {
12390
16.5k
                            expanded += '=';
12391
16.5k
                            expanded += pj_double_quote_string_param_if_needed(
12392
16.5k
                                pair.value);
12393
16.5k
                        }
12394
39.6k
                    }
12395
9.10k
                    expanded += ' ';
12396
9.10k
                    expanded += projStringExportable->exportToPROJString(
12397
9.10k
                        PROJStringFormatter::create().get());
12398
9.10k
                    return createFromPROJString(expanded);
12399
9.10k
                }
12400
10.8k
            }
12401
11.6k
        }
12402
12403
2.15k
        paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str());
12404
2.15k
        if (!init) {
12405
0
            throw ParsingException("out of memory");
12406
0
        }
12407
2.15k
        ctx->projStringParserCreateFromPROJStringRecursionCounter++;
12408
2.15k
        paralist *list = pj_expand_init(ctx, init);
12409
2.15k
        ctx->projStringParserCreateFromPROJStringRecursionCounter--;
12410
2.15k
        if (!list) {
12411
574
            free(init);
12412
574
            throw ParsingException("cannot expand " + projString);
12413
574
        }
12414
1.58k
        std::string expanded;
12415
1.58k
        if (!d->title_.empty()) {
12416
68
            expanded =
12417
68
                "title=" + pj_double_quote_string_param_if_needed(d->title_);
12418
68
        }
12419
1.58k
        bool first = true;
12420
1.58k
        bool has_init_term = false;
12421
2.65k
        for (auto t = list; t;) {
12422
1.07k
            if (!expanded.empty()) {
12423
599
                expanded += ' ';
12424
599
            }
12425
1.07k
            if (first) {
12426
                // first parameter is the init= itself
12427
305
                first = false;
12428
768
            } else if (starts_with(t->param, "init=")) {
12429
68
                has_init_term = true;
12430
700
            } else {
12431
700
                expanded += t->param;
12432
700
            }
12433
12434
1.07k
            auto n = t->next;
12435
1.07k
            free(t);
12436
1.07k
            t = n;
12437
1.07k
        }
12438
2.17k
        for (const auto &pair : d->steps_[0].paramValues) {
12439
2.17k
            expanded += " +";
12440
2.17k
            expanded += pair.key;
12441
2.17k
            if (!pair.value.empty()) {
12442
653
                expanded += '=';
12443
653
                expanded += pj_double_quote_string_param_if_needed(pair.value);
12444
653
            }
12445
2.17k
        }
12446
12447
1.58k
        if (!has_init_term) {
12448
237
            return createFromPROJString(expanded);
12449
237
        }
12450
1.58k
    }
12451
12452
43.9k
    int iFirstGeogStep = -1;
12453
43.9k
    int iSecondGeogStep = -1;
12454
43.9k
    int iProjStep = -1;
12455
43.9k
    int iFirstUnitConvert = -1;
12456
43.9k
    int iSecondUnitConvert = -1;
12457
43.9k
    int iFirstAxisSwap = -1;
12458
43.9k
    int iSecondAxisSwap = -1;
12459
43.9k
    bool unexpectedStructure = d->steps_.empty();
12460
83.3k
    for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) {
12461
43.8k
        const auto &stepName = d->steps_[i].name;
12462
43.8k
        if (isGeographicStep(stepName)) {
12463
13.2k
            if (iFirstGeogStep < 0) {
12464
13.2k
                iFirstGeogStep = i;
12465
13.2k
            } else if (iSecondGeogStep < 0) {
12466
30
                iSecondGeogStep = i;
12467
30
            } else {
12468
2
                unexpectedStructure = true;
12469
2
                break;
12470
2
            }
12471
30.6k
        } else if (ci_equal(stepName, "unitconvert")) {
12472
1.10k
            if (iFirstUnitConvert < 0) {
12473
691
                iFirstUnitConvert = i;
12474
691
            } else if (iSecondUnitConvert < 0) {
12475
266
                iSecondUnitConvert = i;
12476
266
            } else {
12477
150
                unexpectedStructure = true;
12478
150
                break;
12479
150
            }
12480
29.5k
        } else if (ci_equal(stepName, "axisswap")) {
12481
875
            if (iFirstAxisSwap < 0) {
12482
583
                iFirstAxisSwap = i;
12483
583
            } else if (iSecondAxisSwap < 0) {
12484
175
                iSecondAxisSwap = i;
12485
175
            } else {
12486
117
                unexpectedStructure = true;
12487
117
                break;
12488
117
            }
12489
28.6k
        } else if (isProjectedStep(stepName)) {
12490
24.8k
            if (iProjStep >= 0) {
12491
279
                unexpectedStructure = true;
12492
279
                break;
12493
279
            }
12494
24.5k
            iProjStep = i;
12495
24.5k
        } else {
12496
3.83k
            unexpectedStructure = true;
12497
3.83k
            break;
12498
3.83k
        }
12499
43.8k
    }
12500
12501
43.9k
    if (!d->steps_.empty()) {
12502
        // CRS candidate
12503
41.9k
        if ((d->steps_.size() == 1 &&
12504
41.9k
             d->getParamValue(d->steps_[0], "type") != "crs") ||
12505
41.9k
            (d->steps_.size() > 1 && d->getGlobalParamValue("type") != "crs")) {
12506
8.97k
            unexpectedStructure = true;
12507
8.97k
        }
12508
41.9k
    }
12509
12510
43.9k
    struct Logger {
12511
43.9k
        std::string msg{};
12512
12513
        // cppcheck-suppress functionStatic
12514
43.9k
        void setMessage(const char *msgIn) noexcept {
12515
6.27k
            try {
12516
6.27k
                msg = msgIn;
12517
6.27k
            } catch (const std::exception &) {
12518
0
            }
12519
6.27k
        }
12520
12521
43.9k
        static void log(void *user_data, int level, const char *msg) {
12522
6.27k
            if (level == PJ_LOG_ERROR) {
12523
6.27k
                static_cast<Logger *>(user_data)->setMessage(msg);
12524
6.27k
            }
12525
6.27k
        }
12526
43.9k
    };
12527
12528
    // If the structure is not recognized, then try to instantiate the
12529
    // pipeline, and if successful, wrap it in a PROJBasedOperation
12530
43.9k
    Logger logger;
12531
43.9k
    bool valid;
12532
12533
43.9k
    auto pj_context = d->ctx_ ? d->ctx_ : proj_context_create();
12534
43.9k
    if (!pj_context) {
12535
0
        throw ParsingException("out of memory");
12536
0
    }
12537
12538
    // Backup error logger and level, and install temporary handler
12539
43.9k
    auto old_logger = pj_context->logger;
12540
43.9k
    auto old_logger_app_data = pj_context->logger_app_data;
12541
43.9k
    auto log_level = proj_log_level(pj_context, PJ_LOG_ERROR);
12542
43.9k
    proj_log_func(pj_context, &logger, Logger::log);
12543
12544
43.9k
    if (pj_context != d->ctx_) {
12545
10.6k
        proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_);
12546
10.6k
    }
12547
43.9k
    pj_context->projStringParserCreateFromPROJStringRecursionCounter++;
12548
43.9k
    auto pj = pj_create_internal(
12549
43.9k
        pj_context, (projString.find("type=crs") != std::string::npos
12550
43.9k
                         ? projString + " +disable_grid_presence_check"
12551
43.9k
                         : projString)
12552
43.9k
                        .c_str());
12553
43.9k
    pj_context->projStringParserCreateFromPROJStringRecursionCounter--;
12554
43.9k
    valid = pj != nullptr;
12555
12556
    // Restore initial error logger and level
12557
43.9k
    proj_log_level(pj_context, log_level);
12558
43.9k
    pj_context->logger = old_logger;
12559
43.9k
    pj_context->logger_app_data = old_logger_app_data;
12560
12561
    // Remove parameters not understood by PROJ.
12562
43.9k
    if (valid && d->steps_.size() == 1) {
12563
37.0k
        std::vector<Step::KeyValue> newParamValues{};
12564
37.0k
        std::set<std::string> foundKeys;
12565
37.0k
        auto &step = d->steps_[0];
12566
12567
264k
        for (auto &kv : step.paramValues) {
12568
264k
            bool recognizedByPROJ = false;
12569
264k
            if (foundKeys.find(kv.key) != foundKeys.end()) {
12570
34.1k
                continue;
12571
34.1k
            }
12572
230k
            foundKeys.insert(kv.key);
12573
230k
            if ((step.name == "krovak" || step.name == "mod_krovak") &&
12574
230k
                kv.key == "alpha") {
12575
                // We recognize it in our CRS parsing code
12576
451
                recognizedByPROJ = true;
12577
230k
            } else {
12578
1.83M
                for (auto cur = pj->params; cur; cur = cur->next) {
12579
1.83M
                    const char *equal = strchr(cur->param, '=');
12580
1.83M
                    if (equal && static_cast<size_t>(equal - cur->param) ==
12581
804k
                                     kv.key.size()) {
12582
213k
                        if (memcmp(cur->param, kv.key.c_str(), kv.key.size()) ==
12583
213k
                            0) {
12584
123k
                            recognizedByPROJ = (cur->used == 1);
12585
123k
                            break;
12586
123k
                        }
12587
1.62M
                    } else if (strcmp(cur->param, kv.key.c_str()) == 0) {
12588
106k
                        recognizedByPROJ = (cur->used == 1);
12589
106k
                        break;
12590
106k
                    }
12591
1.83M
                }
12592
230k
            }
12593
230k
            if (!recognizedByPROJ && kv.key == "geoid_crs") {
12594
8.95k
                for (auto &pair : step.paramValues) {
12595
8.95k
                    if (ci_equal(pair.key, "geoidgrids")) {
12596
1.03k
                        recognizedByPROJ = true;
12597
1.03k
                        break;
12598
1.03k
                    }
12599
8.95k
                }
12600
1.08k
            }
12601
230k
            if (recognizedByPROJ) {
12602
90.3k
                newParamValues.emplace_back(kv);
12603
90.3k
            }
12604
230k
        }
12605
37.0k
        step.paramValues = std::move(newParamValues);
12606
12607
37.0k
        d->projString_.clear();
12608
37.0k
        if (!step.name.empty()) {
12609
36.8k
            d->projString_ += step.isInit ? "+init=" : "+proj=";
12610
36.8k
            d->projString_ += step.name;
12611
36.8k
        }
12612
90.3k
        for (const auto &paramValue : step.paramValues) {
12613
90.3k
            if (!d->projString_.empty()) {
12614
90.3k
                d->projString_ += ' ';
12615
90.3k
            }
12616
90.3k
            d->projString_ += '+';
12617
90.3k
            d->projString_ += paramValue.key;
12618
90.3k
            if (!paramValue.value.empty()) {
12619
71.9k
                d->projString_ += '=';
12620
71.9k
                d->projString_ +=
12621
71.9k
                    pj_double_quote_string_param_if_needed(paramValue.value);
12622
71.9k
            }
12623
90.3k
        }
12624
37.0k
    }
12625
12626
43.9k
    proj_destroy(pj);
12627
12628
43.9k
    if (!valid) {
12629
1.93k
        const int l_errno = proj_context_errno(pj_context);
12630
1.93k
        std::string msg("Error " + toString(l_errno) + " (" +
12631
1.93k
                        proj_errno_string(l_errno) + ")");
12632
1.93k
        if (!logger.msg.empty()) {
12633
1.82k
            msg += ": ";
12634
1.82k
            msg += logger.msg;
12635
1.82k
        }
12636
1.93k
        logger.msg = std::move(msg);
12637
1.93k
    }
12638
12639
43.9k
    if (pj_context != d->ctx_) {
12640
10.6k
        proj_context_destroy(pj_context);
12641
10.6k
    }
12642
12643
43.9k
    if (!valid) {
12644
1.93k
        throw ParsingException(logger.msg);
12645
1.93k
    }
12646
12647
41.9k
    if (isGeocentricCRS) {
12648
        // First run is dry run to mark all recognized/unrecognized tokens
12649
3.27k
        for (int iter = 0; iter < 2; iter++) {
12650
3.24k
            auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12651
3.24k
                0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
12652
3.24k
                                             d->steps_[1].name == "unitconvert")
12653
3.24k
                                                ? 1
12654
3.24k
                                                : -1));
12655
3.24k
            if (iter == 1) {
12656
1.60k
                return nn_static_pointer_cast<BaseObject>(obj);
12657
1.60k
            }
12658
3.24k
        }
12659
1.63k
    }
12660
12661
40.3k
    if (isTopocentricCRS) {
12662
        // First run is dry run to mark all recognized/unrecognized tokens
12663
190
        for (int iter = 0; iter < 2; iter++) {
12664
187
            auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12665
187
                0,
12666
187
                d->buildProjectedCRS(0, d->buildGeocentricCRS(0, -1), -1, -1));
12667
187
            if (iter == 1) {
12668
92
                return nn_static_pointer_cast<BaseObject>(obj);
12669
92
            }
12670
187
        }
12671
95
    }
12672
12673
40.2k
    if (!unexpectedStructure) {
12674
30.2k
        if (iFirstGeogStep == 0 && !d->steps_[iFirstGeogStep].inverted &&
12675
30.2k
            iSecondGeogStep < 0 && iProjStep < 0 &&
12676
30.2k
            (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) &&
12677
30.2k
            (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) {
12678
            // First run is dry run to mark all recognized/unrecognized tokens
12679
26.0k
            for (int iter = 0; iter < 2; iter++) {
12680
26.0k
                auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12681
26.0k
                    0, d->buildGeodeticCRS(iFirstGeogStep, iFirstUnitConvert,
12682
26.0k
                                           iFirstAxisSwap, false));
12683
26.0k
                if (iter == 1) {
12684
13.0k
                    return nn_static_pointer_cast<BaseObject>(obj);
12685
13.0k
                }
12686
26.0k
            }
12687
13.0k
        }
12688
17.2k
        if (iProjStep >= 0 && !d->steps_[iProjStep].inverted &&
12689
17.2k
            (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) &&
12690
17.2k
            iSecondGeogStep < 0) {
12691
15.5k
            if (iFirstGeogStep < 0)
12692
15.5k
                iFirstGeogStep = iProjStep;
12693
            // First run is dry run to mark all recognized/unrecognized tokens
12694
31.1k
            for (int iter = 0; iter < 2; iter++) {
12695
30.5k
                auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12696
30.5k
                    iProjStep,
12697
30.5k
                    d->buildProjectedCRS(
12698
30.5k
                        iProjStep,
12699
30.5k
                        d->buildGeodeticCRS(iFirstGeogStep,
12700
30.5k
                                            iFirstUnitConvert < iFirstGeogStep
12701
30.5k
                                                ? iFirstUnitConvert
12702
30.5k
                                                : -1,
12703
30.5k
                                            iFirstAxisSwap < iFirstGeogStep
12704
30.5k
                                                ? iFirstAxisSwap
12705
30.5k
                                                : -1,
12706
30.5k
                                            true),
12707
30.5k
                        iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert
12708
30.5k
                                                           : iFirstUnitConvert,
12709
30.5k
                        iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap
12710
30.5k
                                                        : iFirstAxisSwap));
12711
30.5k
                if (iter == 1) {
12712
14.9k
                    return nn_static_pointer_cast<BaseObject>(obj);
12713
14.9k
                }
12714
30.5k
            }
12715
15.5k
        }
12716
17.2k
    }
12717
12718
12.2k
    auto props = PropertyMap();
12719
12.2k
    if (!d->title_.empty()) {
12720
66
        props.set(IdentifiedObject::NAME_KEY, d->title_);
12721
66
    }
12722
12.2k
    return operation::SingleOperation::createPROJBased(props, projString,
12723
12.2k
                                                       nullptr, nullptr, {});
12724
40.2k
}
12725
12726
// ---------------------------------------------------------------------------
12727
12728
//! @cond Doxygen_Suppress
12729
struct JSONFormatter::Private {
12730
    CPLJSonStreamingWriter writer_{nullptr, nullptr};
12731
    DatabaseContextPtr dbContext_{};
12732
12733
    std::vector<bool> stackHasId_{false};
12734
    std::vector<bool> outputIdStack_{true};
12735
    bool allowIDInImmediateChild_ = false;
12736
    bool omitTypeInImmediateChild_ = false;
12737
    bool abridgedTransformation_ = false;
12738
    bool abridgedTransformationWriteSourceCRS_ = false;
12739
    std::string schema_ = PROJJSON_DEFAULT_VERSION;
12740
12741
    // cppcheck-suppress functionStatic
12742
0
    void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); }
12743
12744
    // cppcheck-suppress functionStatic
12745
0
    void popOutputId() { outputIdStack_.pop_back(); }
12746
};
12747
//! @endcond
12748
12749
// ---------------------------------------------------------------------------
12750
12751
/** \brief Constructs a new formatter.
12752
 *
12753
 * A formatter can be used only once (its internal state is mutated)
12754
 *
12755
 * @return new formatter.
12756
 */
12757
JSONFormatterNNPtr JSONFormatter::create( // cppcheck-suppress passedByValue
12758
0
    DatabaseContextPtr dbContext) {
12759
0
    auto ret = NN_NO_CHECK(JSONFormatter::make_unique<JSONFormatter>());
12760
0
    ret->d->dbContext_ = std::move(dbContext);
12761
0
    return ret;
12762
0
}
12763
12764
// ---------------------------------------------------------------------------
12765
12766
/** \brief Whether to use multi line output or not. */
12767
0
JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept {
12768
0
    d->writer_.SetPrettyFormatting(multiLine);
12769
0
    return *this;
12770
0
}
12771
12772
// ---------------------------------------------------------------------------
12773
12774
/** \brief Set number of spaces for each indentation level (defaults to 4).
12775
 */
12776
0
JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept {
12777
0
    d->writer_.SetIndentationSize(width);
12778
0
    return *this;
12779
0
}
12780
12781
// ---------------------------------------------------------------------------
12782
12783
/** \brief Set the value of the "$schema" key in the top level object.
12784
 *
12785
 * If set to empty string, it will not be written.
12786
 */
12787
0
JSONFormatter &JSONFormatter::setSchema(const std::string &schema) noexcept {
12788
0
    d->schema_ = schema;
12789
0
    return *this;
12790
0
}
12791
12792
// ---------------------------------------------------------------------------
12793
12794
//! @cond Doxygen_Suppress
12795
12796
0
JSONFormatter::JSONFormatter() : d(std::make_unique<Private>()) {}
12797
12798
// ---------------------------------------------------------------------------
12799
12800
0
JSONFormatter::~JSONFormatter() = default;
12801
12802
// ---------------------------------------------------------------------------
12803
12804
0
CPLJSonStreamingWriter *JSONFormatter::writer() const { return &(d->writer_); }
12805
12806
// ---------------------------------------------------------------------------
12807
12808
0
const DatabaseContextPtr &JSONFormatter::databaseContext() const {
12809
0
    return d->dbContext_;
12810
0
}
12811
12812
// ---------------------------------------------------------------------------
12813
12814
0
bool JSONFormatter::outputId() const { return d->outputIdStack_.back(); }
12815
12816
// ---------------------------------------------------------------------------
12817
12818
0
bool JSONFormatter::outputUsage(bool calledBeforeObjectContext) const {
12819
0
    return outputId() &&
12820
0
           d->outputIdStack_.size() == (calledBeforeObjectContext ? 1U : 2U);
12821
0
}
12822
12823
// ---------------------------------------------------------------------------
12824
12825
0
void JSONFormatter::setAllowIDInImmediateChild() {
12826
0
    d->allowIDInImmediateChild_ = true;
12827
0
}
12828
12829
// ---------------------------------------------------------------------------
12830
12831
0
void JSONFormatter::setOmitTypeInImmediateChild() {
12832
0
    d->omitTypeInImmediateChild_ = true;
12833
0
}
12834
12835
// ---------------------------------------------------------------------------
12836
12837
JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter,
12838
                                            const char *objectType, bool hasId)
12839
0
    : m_formatter(formatter) {
12840
0
    m_formatter.d->writer_.StartObj();
12841
0
    if (m_formatter.d->outputIdStack_.size() == 1 &&
12842
0
        !m_formatter.d->schema_.empty()) {
12843
0
        m_formatter.d->writer_.AddObjKey("$schema");
12844
0
        m_formatter.d->writer_.Add(m_formatter.d->schema_);
12845
0
    }
12846
0
    if (objectType && !m_formatter.d->omitTypeInImmediateChild_) {
12847
0
        m_formatter.d->writer_.AddObjKey("type");
12848
0
        m_formatter.d->writer_.Add(objectType);
12849
0
    }
12850
0
    m_formatter.d->omitTypeInImmediateChild_ = false;
12851
    // All intermediate nodes shouldn't have ID if a parent has an ID
12852
    // unless explicitly enabled.
12853
0
    if (m_formatter.d->allowIDInImmediateChild_) {
12854
0
        m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0]);
12855
0
        m_formatter.d->allowIDInImmediateChild_ = false;
12856
0
    } else {
12857
0
        m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0] &&
12858
0
                                    !m_formatter.d->stackHasId_.back());
12859
0
    }
12860
12861
0
    m_formatter.d->stackHasId_.push_back(hasId ||
12862
0
                                         m_formatter.d->stackHasId_.back());
12863
0
}
12864
12865
// ---------------------------------------------------------------------------
12866
12867
0
JSONFormatter::ObjectContext::~ObjectContext() {
12868
0
    m_formatter.d->writer_.EndObj();
12869
0
    m_formatter.d->stackHasId_.pop_back();
12870
0
    m_formatter.d->popOutputId();
12871
0
}
12872
12873
// ---------------------------------------------------------------------------
12874
12875
0
void JSONFormatter::setAbridgedTransformation(bool outputIn) {
12876
0
    d->abridgedTransformation_ = outputIn;
12877
0
}
12878
12879
// ---------------------------------------------------------------------------
12880
12881
0
bool JSONFormatter::abridgedTransformation() const {
12882
0
    return d->abridgedTransformation_;
12883
0
}
12884
12885
// ---------------------------------------------------------------------------
12886
12887
0
void JSONFormatter::setAbridgedTransformationWriteSourceCRS(bool writeCRS) {
12888
0
    d->abridgedTransformationWriteSourceCRS_ = writeCRS;
12889
0
}
12890
12891
// ---------------------------------------------------------------------------
12892
12893
0
bool JSONFormatter::abridgedTransformationWriteSourceCRS() const {
12894
0
    return d->abridgedTransformationWriteSourceCRS_;
12895
0
}
12896
12897
//! @endcond
12898
12899
// ---------------------------------------------------------------------------
12900
12901
/** \brief Return the serialized JSON.
12902
 */
12903
0
const std::string &JSONFormatter::toString() const {
12904
0
    return d->writer_.GetString();
12905
0
}
12906
12907
// ---------------------------------------------------------------------------
12908
12909
//! @cond Doxygen_Suppress
12910
3.11M
IJSONExportable::~IJSONExportable() = default;
12911
12912
// ---------------------------------------------------------------------------
12913
12914
0
std::string IJSONExportable::exportToJSON(JSONFormatter *formatter) const {
12915
0
    _exportToJSON(formatter);
12916
0
    return formatter->toString();
12917
0
}
12918
12919
//! @endcond
12920
12921
} // namespace io
12922
NS_PROJ_END