Coverage Report

Created: 2026-01-17 06:39

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/iso19111/io.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#include <algorithm>
34
#include <cassert>
35
#include <cctype>
36
#include <cmath>
37
#include <cstring>
38
#include <limits>
39
#include <list>
40
#include <locale>
41
#include <map>
42
#include <set>
43
#include <sstream> // std::istringstream
44
#include <string>
45
#include <utility>
46
#include <vector>
47
48
#include "proj/common.hpp"
49
#include "proj/coordinateoperation.hpp"
50
#include "proj/coordinates.hpp"
51
#include "proj/coordinatesystem.hpp"
52
#include "proj/crs.hpp"
53
#include "proj/datum.hpp"
54
#include "proj/io.hpp"
55
#include "proj/metadata.hpp"
56
#include "proj/util.hpp"
57
58
#include "operation/coordinateoperation_internal.hpp"
59
#include "operation/esriparammappings.hpp"
60
#include "operation/oputils.hpp"
61
#include "operation/parammappings.hpp"
62
63
#include "proj/internal/coordinatesystem_internal.hpp"
64
#include "proj/internal/datum_internal.hpp"
65
#include "proj/internal/internal.hpp"
66
#include "proj/internal/io_internal.hpp"
67
68
#include "proj/internal/include_nlohmann_json.hpp"
69
70
#include "proj_constants.h"
71
72
#include "proj_json_streaming_writer.hpp"
73
#include "wkt1_parser.h"
74
#include "wkt2_parser.h"
75
76
// PROJ include order is sensitive
77
// clang-format off
78
#include "proj.h"
79
#include "proj_internal.h"
80
// clang-format on
81
82
using namespace NS_PROJ::common;
83
using namespace NS_PROJ::coordinates;
84
using namespace NS_PROJ::crs;
85
using namespace NS_PROJ::cs;
86
using namespace NS_PROJ::datum;
87
using namespace NS_PROJ::internal;
88
using namespace NS_PROJ::metadata;
89
using namespace NS_PROJ::operation;
90
using namespace NS_PROJ::util;
91
92
using json = nlohmann::json;
93
94
//! @cond Doxygen_Suppress
95
static const std::string emptyString{};
96
//! @endcond
97
98
#if 0
99
namespace dropbox{ namespace oxygen {
100
template<> nn<NS_PROJ::io::DatabaseContextPtr>::~nn() = default;
101
template<> nn<NS_PROJ::io::AuthorityFactoryPtr>::~nn() = default;
102
template<> nn<std::shared_ptr<NS_PROJ::io::IPROJStringExportable>>::~nn() = default;
103
template<> nn<std::unique_ptr<NS_PROJ::io::PROJStringFormatter, std::default_delete<NS_PROJ::io::PROJStringFormatter> > >::~nn() = default;
104
template<> nn<std::unique_ptr<NS_PROJ::io::WKTFormatter, std::default_delete<NS_PROJ::io::WKTFormatter> > >::~nn() = default;
105
template<> nn<std::unique_ptr<NS_PROJ::io::WKTNode, std::default_delete<NS_PROJ::io::WKTNode> > >::~nn() = default;
106
}}
107
#endif
108
109
NS_PROJ_START
110
namespace io {
111
112
//! @cond Doxygen_Suppress
113
const char *JSONFormatter::PROJJSON_v0_7 =
114
    "https://proj.org/schemas/v0.7/projjson.schema.json";
115
116
#define PROJJSON_DEFAULT_VERSION JSONFormatter::PROJJSON_v0_7
117
118
//! @endcond
119
120
// ---------------------------------------------------------------------------
121
122
//! @cond Doxygen_Suppress
123
14.8M
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
883k
static inline std::string normalizeExponent(const std::string &in) {
581
883k
    return in;
582
883k
}
583
#endif
584
585
883k
static inline std::string normalizeSerializedString(const std::string &in) {
586
883k
    auto ret(normalizeExponent(in));
587
883k
    return ret;
588
883k
}
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
153k
static inline bool isNull(const WKTNodeNNPtr &node) {
901
153k
    return &node == &null_node;
902
153k
}
903
904
struct WKTNode::Private {
905
    std::string value_{};
906
    std::vector<WKTNodeNNPtr> children_{};
907
908
63.6k
    explicit Private(const std::string &valueIn) : value_(valueIn) {}
909
910
    // cppcheck-suppress functionStatic
911
787k
    inline const std::string &value() PROJ_PURE_DEFN { return value_; }
912
913
    // cppcheck-suppress functionStatic
914
54.5k
    inline const std::vector<WKTNodeNNPtr> &children() PROJ_PURE_DEFN {
915
54.5k
        return children_;
916
54.5k
    }
917
918
    // cppcheck-suppress functionStatic
919
9.94k
    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
850k
#define GP() getPrivate()
945
946
// ---------------------------------------------------------------------------
947
948
const WKTNodeNNPtr &
949
WKTNode::Private::lookForChild(const std::string &childName,
950
171
                               int occurrence) const noexcept {
951
171
    int occCount = 0;
952
404
    for (const auto &child : children_) {
953
404
        if (ci_equal(child->GP()->value(), childName)) {
954
171
            if (occurrence == occCount) {
955
171
                return child;
956
171
            }
957
0
            occCount++;
958
0
        }
959
404
    }
960
0
    return null_node;
961
171
}
962
963
const WKTNodeNNPtr &
964
141k
WKTNode::Private::lookForChild(const std::string &name) const noexcept {
965
578k
    for (const auto &child : children_) {
966
578k
        const auto &v = child->GP()->value();
967
578k
        if (ci_equal(v, name)) {
968
3.72k
            return child;
969
3.72k
        }
970
578k
    }
971
138k
    return null_node;
972
141k
}
973
974
const WKTNodeNNPtr &
975
WKTNode::Private::lookForChild(const std::string &name,
976
5.18k
                               const std::string &name2) const noexcept {
977
17.1k
    for (const auto &child : children_) {
978
17.1k
        const auto &v = child->GP()->value();
979
17.1k
        if (ci_equal(v, name) || ci_equal(v, name2)) {
980
2.01k
            return child;
981
2.01k
        }
982
17.1k
    }
983
3.16k
    return null_node;
984
5.18k
}
985
986
const WKTNodeNNPtr &
987
WKTNode::Private::lookForChild(const std::string &name,
988
                               const std::string &name2,
989
1.82k
                               const std::string &name3) const noexcept {
990
3.71k
    for (const auto &child : children_) {
991
3.71k
        const auto &v = child->GP()->value();
992
3.71k
        if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3)) {
993
1.63k
            return child;
994
1.63k
        }
995
3.71k
    }
996
193
    return null_node;
997
1.82k
}
998
999
const WKTNodeNNPtr &WKTNode::Private::lookForChild(
1000
    const std::string &name, const std::string &name2, const std::string &name3,
1001
644
    const std::string &name4) const noexcept {
1002
1.31k
    for (const auto &child : children_) {
1003
1.31k
        const auto &v = child->GP()->value();
1004
1.31k
        if (ci_equal(v, name) || ci_equal(v, name2) || ci_equal(v, name3) ||
1005
942
            ci_equal(v, name4)) {
1006
555
            return child;
1007
555
        }
1008
1.31k
    }
1009
89
    return null_node;
1010
644
}
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
63.6k
    : d(std::make_unique<Private>(valueIn)) {}
1022
1023
// ---------------------------------------------------------------------------
1024
1025
//! @cond Doxygen_Suppress
1026
63.6k
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
60.3k
void WKTNode::addChild(WKTNodeNNPtr &&child) {
1036
60.3k
    d->children_.push_back(std::move(child));
1037
60.3k
}
1038
1039
// ---------------------------------------------------------------------------
1040
1041
/** \brief Return the (occurrence-1)th sub-node of name childName.
1042
 *
1043
 * @param childName name of the child.
1044
 * @param occurrence occurrence index (starting at 0)
1045
 * @return the child, or nullptr.
1046
 */
1047
const WKTNodePtr &WKTNode::lookForChild(const std::string &childName,
1048
1.46k
                                        int occurrence) const noexcept {
1049
1.46k
    int occCount = 0;
1050
9.83k
    for (const auto &child : d->children_) {
1051
9.83k
        if (ci_equal(child->GP()->value(), childName)) {
1052
117
            if (occurrence == occCount) {
1053
117
                return child;
1054
117
            }
1055
0
            occCount++;
1056
0
        }
1057
9.83k
    }
1058
1.34k
    return null_node;
1059
1.46k
}
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
2.98k
int WKTNode::countChildrenOfName(const std::string &childName) const noexcept {
1069
2.98k
    int occCount = 0;
1070
16.5k
    for (const auto &child : d->children_) {
1071
16.5k
        if (ci_equal(child->GP()->value(), childName)) {
1072
181
            occCount++;
1073
181
        }
1074
16.5k
    }
1075
2.98k
    return occCount;
1076
2.98k
}
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
220k
static size_t skipSpace(const std::string &str, size_t start) {
1096
220k
    size_t i = start;
1097
224k
    while (i < str.size() && ::isspace(static_cast<unsigned char>(str[i]))) {
1098
3.39k
        ++i;
1099
3.39k
    }
1100
220k
    return i;
1101
220k
}
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
63.9k
                                 int recLevel, size_t &indexEnd) {
1114
63.9k
    if (recLevel == 16) {
1115
9
        throw ParsingException("too many nesting levels");
1116
9
    }
1117
63.9k
    std::string value;
1118
63.9k
    size_t i = skipSpace(wkt, indexStart);
1119
63.9k
    if (i == wkt.size()) {
1120
0
        throw ParsingException("whitespace only string");
1121
0
    }
1122
63.9k
    std::string closingStringMarker;
1123
63.9k
    bool inString = false;
1124
1125
529k
    for (; i < wkt.size() &&
1126
529k
           (inString ||
1127
392k
            (wkt[i] != '[' && wkt[i] != '(' && wkt[i] != ',' && wkt[i] != ']' &&
1128
330k
             wkt[i] != ')' && !::isspace(static_cast<unsigned char>(wkt[i]))));
1129
466k
         ++i) {
1130
466k
        if (wkt[i] == '"') {
1131
16.0k
            if (!inString) {
1132
7.76k
                inString = true;
1133
7.76k
                closingStringMarker = "\"";
1134
8.30k
            } else if (closingStringMarker == "\"") {
1135
8.12k
                if (i + 1 < wkt.size() && wkt[i + 1] == '"') {
1136
395
                    i++;
1137
7.72k
                } else {
1138
7.72k
                    inString = false;
1139
7.72k
                    closingStringMarker.clear();
1140
7.72k
                }
1141
8.12k
            }
1142
449k
        } else if (i + 3 <= wkt.size() &&
1143
449k
                   wkt.substr(i, 3) == startPrintedQuote) {
1144
96
            if (!inString) {
1145
64
                inString = true;
1146
64
                closingStringMarker = endPrintedQuote;
1147
64
                value += '"';
1148
64
                i += 2;
1149
64
                continue;
1150
64
            }
1151
449k
        } else if (i + 3 <= wkt.size() &&
1152
449k
                   closingStringMarker == endPrintedQuote &&
1153
1.27k
                   wkt.substr(i, 3) == endPrintedQuote) {
1154
19
            inString = false;
1155
19
            closingStringMarker.clear();
1156
19
            value += '"';
1157
19
            i += 2;
1158
19
            continue;
1159
19
        }
1160
465k
        value += wkt[i];
1161
465k
    }
1162
63.9k
    i = skipSpace(wkt, i);
1163
63.9k
    if (i == wkt.size()) {
1164
261
        if (indexStart == 0) {
1165
0
            throw ParsingException("missing [");
1166
261
        } else {
1167
261
            throw ParsingException("missing , or ]");
1168
261
        }
1169
261
    }
1170
1171
63.6k
    auto node = NN_NO_CHECK(std::make_unique<WKTNode>(value));
1172
1173
63.6k
    if (indexStart > 0) {
1174
60.9k
        if (wkt[i] == ',') {
1175
22.9k
            indexEnd = i + 1;
1176
22.9k
            return node;
1177
22.9k
        }
1178
38.0k
        if (wkt[i] == ']' || wkt[i] == ')') {
1179
18.0k
            indexEnd = i;
1180
18.0k
            return node;
1181
18.0k
        }
1182
38.0k
    }
1183
22.6k
    if (wkt[i] != '[' && wkt[i] != '(') {
1184
34
        throw ParsingException("missing [");
1185
34
    }
1186
22.6k
    ++i; // skip [
1187
22.6k
    i = skipSpace(wkt, i);
1188
83.7k
    while (i < wkt.size() && wkt[i] != ']' && wkt[i] != ')') {
1189
61.0k
        size_t indexEndChild;
1190
61.0k
        node->addChild(createFrom(wkt, i, recLevel + 1, indexEndChild));
1191
61.0k
        assert(indexEndChild > i);
1192
61.0k
        i = indexEndChild;
1193
61.0k
        i = skipSpace(wkt, i);
1194
61.0k
        if (i < wkt.size() && wkt[i] == ',') {
1195
8.74k
            ++i;
1196
8.74k
            i = skipSpace(wkt, i);
1197
8.74k
        }
1198
61.0k
    }
1199
22.6k
    if (i == wkt.size() || (wkt[i] != ']' && wkt[i] != ')')) {
1200
51
        throw ParsingException("missing ]");
1201
51
    }
1202
22.6k
    indexEnd = i + 1;
1203
22.6k
    return node;
1204
22.6k
}
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
17.6k
                        const std::string &rhs) const noexcept {
1262
17.6k
            return ci_less(lhs, rhs);
1263
17.6k
        }
1264
    };
1265
1266
    bool strict_ = true;
1267
    bool unsetIdentifiersIfIncompatibleDef_ = true;
1268
    std::list<std::string> warningList_{};
1269
    std::list<std::string> grammarErrorList_{};
1270
    std::vector<double> toWGS84Parameters_{};
1271
    std::string datumPROJ4Grids_{};
1272
    bool esriStyle_ = false;
1273
    bool maybeEsriStyle_ = false;
1274
    DatabaseContextPtr dbContext_{};
1275
    crs::GeographicCRSPtr geogCRSOfCompoundCRS_{};
1276
1277
    static constexpr unsigned int MAX_PROPERTY_SIZE = 1024;
1278
    std::vector<std::unique_ptr<PropertyMap>> properties_{};
1279
1280
2.66k
    Private() = default;
1281
2.66k
    ~Private() = default;
1282
    Private(const Private &) = delete;
1283
    Private &operator=(const Private &) = delete;
1284
1285
    void emitRecoverableWarning(const std::string &warningMsg);
1286
    void emitGrammarError(const std::string &errorMsg);
1287
    void emitRecoverableMissingUNIT(const std::string &parentNodeName,
1288
                                    const UnitOfMeasure &fallbackUnit);
1289
1290
    BaseObjectNNPtr build(const WKTNodeNNPtr &node);
1291
1292
    IdentifierPtr buildId(const WKTNodeNNPtr &parentNode,
1293
                          const WKTNodeNNPtr &node, bool tolerant,
1294
                          bool removeInverseOf);
1295
1296
    PropertyMap &buildProperties(const WKTNodeNNPtr &node,
1297
                                 bool removeInverseOf = false,
1298
                                 bool hasName = true);
1299
1300
    ObjectDomainPtr buildObjectDomain(const WKTNodeNNPtr &node);
1301
1302
    static std::string stripQuotes(const WKTNodeNNPtr &node);
1303
1304
    static double asDouble(const WKTNodeNNPtr &node);
1305
1306
    UnitOfMeasure
1307
    buildUnit(const WKTNodeNNPtr &node,
1308
              UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN);
1309
1310
    UnitOfMeasure buildUnitInSubNode(
1311
        const WKTNodeNNPtr &node,
1312
        common::UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN);
1313
1314
    EllipsoidNNPtr buildEllipsoid(const WKTNodeNNPtr &node);
1315
1316
    PrimeMeridianNNPtr
1317
    buildPrimeMeridian(const WKTNodeNNPtr &node,
1318
                       const UnitOfMeasure &defaultAngularUnit);
1319
1320
    static optional<std::string> getAnchor(const WKTNodeNNPtr &node);
1321
1322
    static optional<common::Measure> getAnchorEpoch(const WKTNodeNNPtr &node);
1323
1324
    static void parseDynamic(const WKTNodeNNPtr &dynamicNode,
1325
                             double &frameReferenceEpoch,
1326
                             util::optional<std::string> &modelName);
1327
1328
    GeodeticReferenceFrameNNPtr
1329
    buildGeodeticReferenceFrame(const WKTNodeNNPtr &node,
1330
                                const PrimeMeridianNNPtr &primeMeridian,
1331
                                const WKTNodeNNPtr &dynamicNode);
1332
1333
    DatumEnsembleNNPtr buildDatumEnsemble(const WKTNodeNNPtr &node,
1334
                                          const PrimeMeridianPtr &primeMeridian,
1335
                                          bool expectEllipsoid);
1336
1337
    MeridianNNPtr buildMeridian(const WKTNodeNNPtr &node);
1338
    CoordinateSystemAxisNNPtr buildAxis(const WKTNodeNNPtr &node,
1339
                                        const UnitOfMeasure &unitIn,
1340
                                        const UnitOfMeasure::Type &unitType,
1341
                                        bool isGeocentric,
1342
                                        int expectedOrderNum);
1343
1344
    CoordinateSystemNNPtr buildCS(const WKTNodeNNPtr &node, /* maybe null */
1345
                                  const WKTNodeNNPtr &parentNode,
1346
                                  const UnitOfMeasure &defaultAngularUnit);
1347
1348
    GeodeticCRSNNPtr buildGeodeticCRS(const WKTNodeNNPtr &node,
1349
                                      bool forceGeocentricIfNoCs = false);
1350
1351
    CRSNNPtr buildDerivedGeodeticCRS(const WKTNodeNNPtr &node);
1352
1353
    static UnitOfMeasure
1354
    guessUnitForParameter(const std::string &paramName,
1355
                          const UnitOfMeasure &defaultLinearUnit,
1356
                          const UnitOfMeasure &defaultAngularUnit);
1357
1358
    void consumeParameters(const WKTNodeNNPtr &node, bool isAbridged,
1359
                           std::vector<OperationParameterNNPtr> &parameters,
1360
                           std::vector<ParameterValueNNPtr> &values,
1361
                           const UnitOfMeasure &defaultLinearUnit,
1362
                           const UnitOfMeasure &defaultAngularUnit);
1363
1364
    static std::string getExtensionProj4(const WKTNode::Private *nodeP);
1365
1366
    static void addExtensionProj4ToProp(const WKTNode::Private *nodeP,
1367
                                        PropertyMap &props);
1368
1369
    ConversionNNPtr buildConversion(const WKTNodeNNPtr &node,
1370
                                    const UnitOfMeasure &defaultLinearUnit,
1371
                                    const UnitOfMeasure &defaultAngularUnit);
1372
1373
    static bool hasWebMercPROJ4String(const WKTNodeNNPtr &projCRSNode,
1374
                                      const WKTNodeNNPtr &projectionNode);
1375
1376
    static std::string projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
1377
                                              const char *paramName);
1378
1379
    ConversionNNPtr buildProjection(const GeodeticCRSNNPtr &baseGeodCRS,
1380
                                    const WKTNodeNNPtr &projCRSNode,
1381
                                    const WKTNodeNNPtr &projectionNode,
1382
                                    const UnitOfMeasure &defaultLinearUnit,
1383
                                    const UnitOfMeasure &defaultAngularUnit);
1384
1385
    ConversionNNPtr
1386
    buildProjectionStandard(const GeodeticCRSNNPtr &baseGeodCRS,
1387
                            const WKTNodeNNPtr &projCRSNode,
1388
                            const WKTNodeNNPtr &projectionNode,
1389
                            const UnitOfMeasure &defaultLinearUnit,
1390
                            const UnitOfMeasure &defaultAngularUnit);
1391
1392
    const ESRIMethodMapping *
1393
    getESRIMapping(const WKTNodeNNPtr &projCRSNode,
1394
                   const WKTNodeNNPtr &projectionNode,
1395
                   std::map<std::string, std::string, ci_less_struct>
1396
                       &mapParamNameToValue);
1397
1398
    static ConversionNNPtr
1399
    buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS,
1400
                            const WKTNodeNNPtr &projCRSNode,
1401
                            const WKTNodeNNPtr &projectionNode,
1402
                            const UnitOfMeasure &defaultLinearUnit,
1403
                            const UnitOfMeasure &defaultAngularUnit,
1404
                            const ESRIMethodMapping *esriMapping,
1405
                            std::map<std::string, std::string, ci_less_struct>
1406
                                &mapParamNameToValue);
1407
1408
    ConversionNNPtr
1409
    buildProjectionFromESRI(const GeodeticCRSNNPtr &baseGeodCRS,
1410
                            const WKTNodeNNPtr &projCRSNode,
1411
                            const WKTNodeNNPtr &projectionNode,
1412
                            const UnitOfMeasure &defaultLinearUnit,
1413
                            const UnitOfMeasure &defaultAngularUnit);
1414
1415
    ProjectedCRSNNPtr buildProjectedCRS(const WKTNodeNNPtr &node);
1416
1417
    VerticalReferenceFrameNNPtr
1418
    buildVerticalReferenceFrame(const WKTNodeNNPtr &node,
1419
                                const WKTNodeNNPtr &dynamicNode);
1420
1421
    TemporalDatumNNPtr buildTemporalDatum(const WKTNodeNNPtr &node);
1422
1423
    EngineeringDatumNNPtr buildEngineeringDatum(const WKTNodeNNPtr &node);
1424
1425
    ParametricDatumNNPtr buildParametricDatum(const WKTNodeNNPtr &node);
1426
1427
    CRSNNPtr buildVerticalCRS(const WKTNodeNNPtr &node);
1428
1429
    DerivedVerticalCRSNNPtr buildDerivedVerticalCRS(const WKTNodeNNPtr &node);
1430
1431
    CRSNNPtr buildCompoundCRS(const WKTNodeNNPtr &node);
1432
1433
    BoundCRSNNPtr buildBoundCRS(const WKTNodeNNPtr &node);
1434
1435
    TemporalCSNNPtr buildTemporalCS(const WKTNodeNNPtr &parentNode);
1436
1437
    TemporalCRSNNPtr buildTemporalCRS(const WKTNodeNNPtr &node);
1438
1439
    DerivedTemporalCRSNNPtr buildDerivedTemporalCRS(const WKTNodeNNPtr &node);
1440
1441
    EngineeringCRSNNPtr buildEngineeringCRS(const WKTNodeNNPtr &node);
1442
1443
    EngineeringCRSNNPtr
1444
    buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node);
1445
1446
    DerivedEngineeringCRSNNPtr
1447
    buildDerivedEngineeringCRS(const WKTNodeNNPtr &node);
1448
1449
    ParametricCSNNPtr buildParametricCS(const WKTNodeNNPtr &parentNode);
1450
1451
    ParametricCRSNNPtr buildParametricCRS(const WKTNodeNNPtr &node);
1452
1453
    DerivedParametricCRSNNPtr
1454
    buildDerivedParametricCRS(const WKTNodeNNPtr &node);
1455
1456
    DerivedProjectedCRSNNPtr buildDerivedProjectedCRS(const WKTNodeNNPtr &node);
1457
1458
    CRSPtr buildCRS(const WKTNodeNNPtr &node);
1459
1460
    TransformationNNPtr buildCoordinateOperation(const WKTNodeNNPtr &node);
1461
1462
    PointMotionOperationNNPtr
1463
    buildPointMotionOperation(const WKTNodeNNPtr &node);
1464
1465
    ConcatenatedOperationNNPtr
1466
    buildConcatenatedOperation(const WKTNodeNNPtr &node);
1467
1468
    CoordinateMetadataNNPtr buildCoordinateMetadata(const WKTNodeNNPtr &node);
1469
};
1470
//! @endcond
1471
1472
// ---------------------------------------------------------------------------
1473
1474
2.66k
WKTParser::WKTParser() : d(std::make_unique<Private>()) {}
1475
1476
// ---------------------------------------------------------------------------
1477
1478
//! @cond Doxygen_Suppress
1479
2.66k
WKTParser::~WKTParser() = default;
1480
//! @endcond
1481
1482
// ---------------------------------------------------------------------------
1483
1484
/** \brief Set whether parsing should be done in strict mode.
1485
 */
1486
2.66k
WKTParser &WKTParser::setStrict(bool strict) {
1487
2.66k
    d->strict_ = strict;
1488
2.66k
    return *this;
1489
2.66k
}
1490
1491
// ---------------------------------------------------------------------------
1492
1493
/** \brief Set whether object identifiers should be unset when there is
1494
 *         a contradiction between the definition from WKT and the one from
1495
 *         the database.
1496
 *
1497
 * At time of writing, this only applies to the base geographic CRS of a
1498
 * projected CRS, when comparing its coordinate system.
1499
 */
1500
0
WKTParser &WKTParser::setUnsetIdentifiersIfIncompatibleDef(bool unset) {
1501
0
    d->unsetIdentifiersIfIncompatibleDef_ = unset;
1502
0
    return *this;
1503
0
}
1504
1505
// ---------------------------------------------------------------------------
1506
1507
/** \brief Return the list of warnings found during parsing.
1508
 *
1509
 * \note The list might be non-empty only is setStrict(false) has been called.
1510
 */
1511
0
std::list<std::string> WKTParser::warningList() const {
1512
0
    return d->warningList_;
1513
0
}
1514
1515
// ---------------------------------------------------------------------------
1516
1517
/** \brief Return the list of grammar errors found during parsing.
1518
 *
1519
 * Grammar errors are non-compliance issues with respect to the WKT grammar.
1520
 *
1521
 * \note The list might be non-empty only is setStrict(false) has been called.
1522
 *
1523
 * @since PROJ 9.5
1524
 */
1525
0
std::list<std::string> WKTParser::grammarErrorList() const {
1526
0
    return d->grammarErrorList_;
1527
0
}
1528
1529
// ---------------------------------------------------------------------------
1530
1531
//! @cond Doxygen_Suppress
1532
2.19k
void WKTParser::Private::emitRecoverableWarning(const std::string &errorMsg) {
1533
2.19k
    if (strict_) {
1534
0
        throw ParsingException(errorMsg);
1535
2.19k
    } else {
1536
2.19k
        warningList_.push_back(errorMsg);
1537
2.19k
    }
1538
2.19k
}
1539
//! @endcond
1540
1541
// ---------------------------------------------------------------------------
1542
1543
//! @cond Doxygen_Suppress
1544
924
void WKTParser::Private::emitGrammarError(const std::string &errorMsg) {
1545
924
    if (strict_) {
1546
0
        throw ParsingException(errorMsg);
1547
924
    } else {
1548
924
        grammarErrorList_.push_back(errorMsg);
1549
924
    }
1550
924
}
1551
//! @endcond
1552
1553
// ---------------------------------------------------------------------------
1554
1555
9.51k
static double asDouble(const std::string &val) { return c_locale_stod(val); }
1556
1557
// ---------------------------------------------------------------------------
1558
1559
131
PROJ_NO_RETURN static void ThrowNotEnoughChildren(const std::string &nodeName) {
1560
131
    throw ParsingException(
1561
131
        concat("not enough children in ", nodeName, " node"));
1562
131
}
1563
1564
// ---------------------------------------------------------------------------
1565
1566
PROJ_NO_RETURN static void
1567
28
ThrowNotRequiredNumberOfChildren(const std::string &nodeName) {
1568
28
    throw ParsingException(
1569
28
        concat("not required number of children in ", nodeName, " node"));
1570
28
}
1571
1572
// ---------------------------------------------------------------------------
1573
1574
303
PROJ_NO_RETURN static void ThrowMissing(const std::string &nodeName) {
1575
303
    throw ParsingException(concat("missing ", nodeName, " node"));
1576
303
}
1577
1578
// ---------------------------------------------------------------------------
1579
1580
PROJ_NO_RETURN static void
1581
0
ThrowNotExpectedCSType(const std::string &expectedCSType) {
1582
0
    throw ParsingException(concat("CS node is not of type ", expectedCSType));
1583
0
}
1584
1585
// ---------------------------------------------------------------------------
1586
1587
static ParsingException buildRethrow(const char *funcName,
1588
69
                                     const std::exception &e) {
1589
69
    std::string res(funcName);
1590
69
    res += ": ";
1591
69
    res += e.what();
1592
69
    return ParsingException(res);
1593
69
}
1594
1595
// ---------------------------------------------------------------------------
1596
1597
//! @cond Doxygen_Suppress
1598
25.3k
std::string WKTParser::Private::stripQuotes(const WKTNodeNNPtr &node) {
1599
25.3k
    return ::stripQuotes(node->GP()->value());
1600
25.3k
}
1601
1602
// ---------------------------------------------------------------------------
1603
1604
7.40k
double WKTParser::Private::asDouble(const WKTNodeNNPtr &node) {
1605
7.40k
    return io::asDouble(node->GP()->value());
1606
7.40k
}
1607
1608
// ---------------------------------------------------------------------------
1609
1610
IdentifierPtr WKTParser::Private::buildId(const WKTNodeNNPtr &parentNode,
1611
                                          const WKTNodeNNPtr &node,
1612
1.67k
                                          bool tolerant, bool removeInverseOf) {
1613
1.67k
    const auto *nodeP = node->GP();
1614
1.67k
    const auto &nodeChildren = nodeP->children();
1615
1.67k
    if (nodeChildren.size() >= 2) {
1616
1.45k
        auto codeSpace = stripQuotes(nodeChildren[0]);
1617
1.45k
        if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
1618
35
            codeSpace.back() == ')') {
1619
32
            codeSpace = codeSpace.substr(strlen("INVERSE("));
1620
32
            codeSpace.resize(codeSpace.size() - 1);
1621
32
        }
1622
1623
1.45k
        PropertyMap propertiesId;
1624
1.45k
        if (nodeChildren.size() >= 3 &&
1625
709
            nodeChildren[2]->GP()->childrenSize() == 0) {
1626
520
            std::string version = stripQuotes(nodeChildren[2]);
1627
1628
            // IAU + 2015 -> IAU_2015
1629
520
            if (dbContext_) {
1630
481
                std::string codeSpaceOut;
1631
481
                if (dbContext_->getVersionedAuthority(codeSpace, version,
1632
481
                                                      codeSpaceOut)) {
1633
0
                    codeSpace = std::move(codeSpaceOut);
1634
0
                    version.clear();
1635
0
                }
1636
481
            }
1637
1638
520
            if (!version.empty()) {
1639
504
                propertiesId.set(Identifier::VERSION_KEY, version);
1640
504
            }
1641
520
        }
1642
1643
1.45k
        auto code = stripQuotes(nodeChildren[1]);
1644
1645
        // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone
1646
        // south, we generated a wrong value. Auto-fix that
1647
1.45k
        const auto &parentNodeKeyword(parentNode->GP()->value());
1648
1.45k
        if (parentNodeKeyword == WKTConstants::CONVERSION &&
1649
208
            codeSpace == Identifier::EPSG) {
1650
11
            const auto &parentNodeChildren = parentNode->GP()->children();
1651
11
            if (!parentNodeChildren.empty()) {
1652
11
                const auto parentNodeName(stripQuotes(parentNodeChildren[0]));
1653
11
                if (ci_starts_with(parentNodeName, "UTM Zone ") &&
1654
0
                    parentNodeName.find('S') != std::string::npos) {
1655
0
                    const int nZone =
1656
0
                        atoi(parentNodeName.c_str() + strlen("UTM Zone "));
1657
0
                    if (nZone >= 1 && nZone <= 60) {
1658
0
                        code = internal::toString(16100 + nZone);
1659
0
                    }
1660
0
                }
1661
11
            }
1662
11
        }
1663
1664
1.45k
        auto &citationNode = nodeP->lookForChild(WKTConstants::CITATION);
1665
1.45k
        auto &uriNode = nodeP->lookForChild(WKTConstants::URI);
1666
1667
1.45k
        propertiesId.set(Identifier::CODESPACE_KEY, codeSpace);
1668
1.45k
        bool authoritySet = false;
1669
1.45k
        /*if (!isNull(citationNode))*/ {
1670
1.45k
            const auto *citationNodeP = citationNode->GP();
1671
1.45k
            if (citationNodeP->childrenSize() == 1) {
1672
4
                authoritySet = true;
1673
4
                propertiesId.set(Identifier::AUTHORITY_KEY,
1674
4
                                 stripQuotes(citationNodeP->children()[0]));
1675
4
            }
1676
1.45k
        }
1677
1.45k
        if (!authoritySet) {
1678
1.45k
            propertiesId.set(Identifier::AUTHORITY_KEY, codeSpace);
1679
1.45k
        }
1680
1.45k
        /*if (!isNull(uriNode))*/ {
1681
1.45k
            const auto *uriNodeP = uriNode->GP();
1682
1.45k
            if (uriNodeP->childrenSize() == 1) {
1683
23
                propertiesId.set(Identifier::URI_KEY,
1684
23
                                 stripQuotes(uriNodeP->children()[0]));
1685
23
            }
1686
1.45k
        }
1687
1.45k
        return Identifier::create(code, propertiesId);
1688
1.45k
    } else if (strict_ || !tolerant) {
1689
5
        ThrowNotEnoughChildren(nodeP->value());
1690
218
    } else {
1691
218
        std::string msg("not enough children in ");
1692
218
        msg += nodeP->value();
1693
218
        msg += " node";
1694
218
        warningList_.emplace_back(std::move(msg));
1695
218
    }
1696
218
    return nullptr;
1697
1.67k
}
1698
1699
// ---------------------------------------------------------------------------
1700
1701
PropertyMap &WKTParser::Private::buildProperties(const WKTNodeNNPtr &node,
1702
                                                 bool removeInverseOf,
1703
10.5k
                                                 bool hasName) {
1704
1705
10.5k
    if (properties_.size() >= MAX_PROPERTY_SIZE) {
1706
0
        throw ParsingException("MAX_PROPERTY_SIZE reached");
1707
0
    }
1708
10.5k
    properties_.push_back(std::make_unique<PropertyMap>());
1709
10.5k
    auto properties = properties_.back().get();
1710
1711
10.5k
    std::string authNameFromAlias;
1712
10.5k
    std::string codeFromAlias;
1713
10.5k
    const auto *nodeP = node->GP();
1714
10.5k
    const auto &nodeChildren = nodeP->children();
1715
1716
10.5k
    auto identifiers = ArrayOfBaseObject::create();
1717
38.8k
    for (const auto &subNode : nodeChildren) {
1718
38.8k
        const auto &subNodeName(subNode->GP()->value());
1719
38.8k
        if (ci_equal(subNodeName, WKTConstants::ID) ||
1720
37.3k
            ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
1721
1.64k
            auto id = buildId(node, subNode, true, removeInverseOf);
1722
1.64k
            if (id) {
1723
1.42k
                identifiers->add(NN_NO_CHECK(id));
1724
1.42k
            }
1725
1.64k
        }
1726
38.8k
    }
1727
1728
10.5k
    if (hasName && !nodeChildren.empty()) {
1729
9.86k
        const auto &nodeName(nodeP->value());
1730
9.86k
        auto name(stripQuotes(nodeChildren[0]));
1731
9.86k
        if (removeInverseOf && starts_with(name, "Inverse of ")) {
1732
108
            name = name.substr(strlen("Inverse of "));
1733
108
        }
1734
1735
9.86k
        if (ends_with(name, " (deprecated)")) {
1736
1
            name.resize(name.size() - strlen(" (deprecated)"));
1737
1
            properties->set(common::IdentifiedObject::DEPRECATED_KEY, true);
1738
1
        }
1739
1740
        // Oracle WKT can contain names like
1741
        // "Reseau Geodesique Francais 1993 (EPSG ID 6171)"
1742
        // for WKT attributes to the auth_name = "IGN - Paris"
1743
        // Strip that suffix from the name and assign a true EPSG code to the
1744
        // object
1745
9.86k
        if (identifiers->empty()) {
1746
9.36k
            const auto pos = name.find(" (EPSG ID ");
1747
9.36k
            if (pos != std::string::npos && name.back() == ')') {
1748
131
                const auto code =
1749
131
                    name.substr(pos + strlen(" (EPSG ID "),
1750
131
                                name.size() - 1 - pos - strlen(" (EPSG ID "));
1751
131
                name.resize(pos);
1752
1753
131
                PropertyMap propertiesId;
1754
131
                propertiesId.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
1755
131
                propertiesId.set(Identifier::AUTHORITY_KEY, Identifier::EPSG);
1756
131
                identifiers->add(Identifier::create(code, propertiesId));
1757
131
            }
1758
9.36k
        }
1759
1760
9.86k
        const char *tableNameForAlias = nullptr;
1761
9.86k
        if (ci_equal(nodeName, WKTConstants::GEOGCS)) {
1762
1.04k
            if (starts_with(name, "GCS_")) {
1763
269
                esriStyle_ = true;
1764
269
                if (name == "GCS_WGS_1984") {
1765
137
                    name = "WGS 84";
1766
137
                } else if (name == "GCS_unknown") {
1767
0
                    name = "unknown";
1768
132
                } else {
1769
132
                    tableNameForAlias = "geodetic_crs";
1770
132
                }
1771
269
            }
1772
8.82k
        } else if (esriStyle_ && ci_equal(nodeName, WKTConstants::SPHEROID)) {
1773
282
            if (name == "WGS_1984") {
1774
137
                name = "WGS 84";
1775
137
                authNameFromAlias = Identifier::EPSG;
1776
137
                codeFromAlias = "7030";
1777
145
            } else {
1778
145
                tableNameForAlias = "ellipsoid";
1779
145
            }
1780
282
        }
1781
1782
9.86k
        if (dbContext_ && tableNameForAlias) {
1783
267
            std::string outTableName;
1784
267
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
1785
267
                                                        std::string());
1786
267
            auto officialName = authFactory->getOfficialNameFromAlias(
1787
267
                name, tableNameForAlias, "ESRI", false, outTableName,
1788
267
                authNameFromAlias, codeFromAlias);
1789
267
            if (!officialName.empty()) {
1790
118
                name = std::move(officialName);
1791
1792
                // Clearing authority for geodetic_crs because of
1793
                // potential axis order mismatch.
1794
118
                if (strcmp(tableNameForAlias, "geodetic_crs") == 0) {
1795
93
                    authNameFromAlias.clear();
1796
93
                    codeFromAlias.clear();
1797
93
                }
1798
118
            }
1799
267
        }
1800
1801
9.86k
        properties->set(IdentifiedObject::NAME_KEY, name);
1802
9.86k
    }
1803
1804
10.5k
    if (identifiers->empty() && !authNameFromAlias.empty()) {
1805
162
        identifiers->add(Identifier::create(
1806
162
            codeFromAlias,
1807
162
            PropertyMap()
1808
162
                .set(Identifier::CODESPACE_KEY, authNameFromAlias)
1809
162
                .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
1810
162
    }
1811
10.5k
    if (!identifiers->empty()) {
1812
795
        properties->set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
1813
795
    }
1814
1815
10.5k
    auto &remarkNode = nodeP->lookForChild(WKTConstants::REMARK);
1816
10.5k
    if (!isNull(remarkNode)) {
1817
48
        const auto &remarkChildren = remarkNode->GP()->children();
1818
48
        if (remarkChildren.size() == 1) {
1819
42
            properties->set(IdentifiedObject::REMARKS_KEY,
1820
42
                            stripQuotes(remarkChildren[0]));
1821
42
        } else {
1822
6
            ThrowNotRequiredNumberOfChildren(remarkNode->GP()->value());
1823
6
        }
1824
48
    }
1825
1826
10.5k
    ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
1827
38.7k
    for (const auto &subNode : nodeP->children()) {
1828
38.7k
        const auto &subNodeName(subNode->GP()->value());
1829
38.7k
        if (ci_equal(subNodeName, WKTConstants::USAGE)) {
1830
90
            auto objectDomain = buildObjectDomain(subNode);
1831
90
            if (!objectDomain) {
1832
6
                throw ParsingException(
1833
6
                    concat("missing children in ", subNodeName, " node"));
1834
6
            }
1835
84
            array->add(NN_NO_CHECK(objectDomain));
1836
84
        }
1837
38.7k
    }
1838
10.5k
    if (!array->empty()) {
1839
31
        properties->set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
1840
10.5k
    } else {
1841
10.5k
        auto objectDomain = buildObjectDomain(node);
1842
10.5k
        if (objectDomain) {
1843
243
            properties->set(ObjectUsage::OBJECT_DOMAIN_KEY,
1844
243
                            NN_NO_CHECK(objectDomain));
1845
243
        }
1846
10.5k
    }
1847
1848
10.5k
    auto &versionNode = nodeP->lookForChild(WKTConstants::VERSION);
1849
10.5k
    if (!isNull(versionNode)) {
1850
15
        const auto &versionChildren = versionNode->GP()->children();
1851
15
        if (versionChildren.size() == 1) {
1852
12
            properties->set(CoordinateOperation::OPERATION_VERSION_KEY,
1853
12
                            stripQuotes(versionChildren[0]));
1854
12
        } else {
1855
3
            ThrowNotRequiredNumberOfChildren(versionNode->GP()->value());
1856
3
        }
1857
15
    }
1858
1859
10.5k
    return *properties;
1860
10.5k
}
1861
1862
// ---------------------------------------------------------------------------
1863
1864
ObjectDomainPtr
1865
10.6k
WKTParser::Private::buildObjectDomain(const WKTNodeNNPtr &node) {
1866
1867
10.6k
    const auto *nodeP = node->GP();
1868
10.6k
    auto &scopeNode = nodeP->lookForChild(WKTConstants::SCOPE);
1869
10.6k
    auto &areaNode = nodeP->lookForChild(WKTConstants::AREA);
1870
10.6k
    auto &bboxNode = nodeP->lookForChild(WKTConstants::BBOX);
1871
10.6k
    auto &verticalExtentNode =
1872
10.6k
        nodeP->lookForChild(WKTConstants::VERTICALEXTENT);
1873
10.6k
    auto &temporalExtentNode = nodeP->lookForChild(WKTConstants::TIMEEXTENT);
1874
10.6k
    if (!isNull(scopeNode) || !isNull(areaNode) || !isNull(bboxNode) ||
1875
10.2k
        !isNull(verticalExtentNode) || !isNull(temporalExtentNode)) {
1876
359
        optional<std::string> scope;
1877
359
        const auto *scopeNodeP = scopeNode->GP();
1878
359
        const auto &scopeChildren = scopeNodeP->children();
1879
359
        if (scopeChildren.size() == 1) {
1880
0
            scope = stripQuotes(scopeChildren[0]);
1881
0
        }
1882
359
        ExtentPtr extent;
1883
359
        if (!isNull(areaNode) || !isNull(bboxNode)) {
1884
322
            util::optional<std::string> description;
1885
322
            std::vector<GeographicExtentNNPtr> geogExtent;
1886
322
            std::vector<VerticalExtentNNPtr> verticalExtent;
1887
322
            std::vector<TemporalExtentNNPtr> temporalExtent;
1888
322
            if (!isNull(areaNode)) {
1889
84
                const auto &areaChildren = areaNode->GP()->children();
1890
84
                if (areaChildren.size() == 1) {
1891
81
                    description = stripQuotes(areaChildren[0]);
1892
81
                } else {
1893
3
                    ThrowNotRequiredNumberOfChildren(areaNode->GP()->value());
1894
3
                }
1895
84
            }
1896
319
            if (!isNull(bboxNode)) {
1897
238
                const auto &bboxChildren = bboxNode->GP()->children();
1898
238
                if (bboxChildren.size() == 4) {
1899
229
                    double south, west, north, east;
1900
229
                    try {
1901
229
                        south = asDouble(bboxChildren[0]);
1902
229
                        west = asDouble(bboxChildren[1]);
1903
229
                        north = asDouble(bboxChildren[2]);
1904
229
                        east = asDouble(bboxChildren[3]);
1905
229
                    } catch (const std::exception &) {
1906
5
                        throw ParsingException(concat("not 4 double values in ",
1907
5
                                                      bboxNode->GP()->value(),
1908
5
                                                      " node"));
1909
5
                    }
1910
224
                    try {
1911
224
                        auto bbox = GeographicBoundingBox::create(west, south,
1912
224
                                                                  east, north);
1913
224
                        geogExtent.emplace_back(bbox);
1914
224
                    } catch (const std::exception &e) {
1915
9
                        throw ParsingException(concat("Invalid ",
1916
9
                                                      bboxNode->GP()->value(),
1917
9
                                                      " node: ") +
1918
9
                                               e.what());
1919
9
                    }
1920
224
                } else {
1921
9
                    ThrowNotRequiredNumberOfChildren(bboxNode->GP()->value());
1922
9
                }
1923
238
            }
1924
1925
296
            if (!isNull(verticalExtentNode)) {
1926
22
                const auto &verticalExtentChildren =
1927
22
                    verticalExtentNode->GP()->children();
1928
22
                const auto verticalExtentChildrenSize =
1929
22
                    verticalExtentChildren.size();
1930
22
                if (verticalExtentChildrenSize == 2 ||
1931
19
                    verticalExtentChildrenSize == 3) {
1932
19
                    double min;
1933
19
                    double max;
1934
19
                    try {
1935
19
                        min = asDouble(verticalExtentChildren[0]);
1936
19
                        max = asDouble(verticalExtentChildren[1]);
1937
19
                    } catch (const std::exception &) {
1938
1
                        throw ParsingException(
1939
1
                            concat("not 2 double values in ",
1940
1
                                   verticalExtentNode->GP()->value(), " node"));
1941
1
                    }
1942
18
                    UnitOfMeasure unit = UnitOfMeasure::METRE;
1943
18
                    if (verticalExtentChildrenSize == 3) {
1944
1
                        unit = buildUnit(verticalExtentChildren[2],
1945
1
                                         UnitOfMeasure::Type::LINEAR);
1946
1
                    }
1947
18
                    verticalExtent.emplace_back(VerticalExtent::create(
1948
18
                        min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
1949
18
                } else {
1950
3
                    ThrowNotRequiredNumberOfChildren(
1951
3
                        verticalExtentNode->GP()->value());
1952
3
                }
1953
22
            }
1954
1955
292
            if (!isNull(temporalExtentNode)) {
1956
49
                const auto &temporalExtentChildren =
1957
49
                    temporalExtentNode->GP()->children();
1958
49
                if (temporalExtentChildren.size() == 2) {
1959
45
                    temporalExtent.emplace_back(TemporalExtent::create(
1960
45
                        stripQuotes(temporalExtentChildren[0]),
1961
45
                        stripQuotes(temporalExtentChildren[1])));
1962
45
                } else {
1963
4
                    ThrowNotRequiredNumberOfChildren(
1964
4
                        temporalExtentNode->GP()->value());
1965
4
                }
1966
49
            }
1967
288
            extent = Extent::create(description, geogExtent, verticalExtent,
1968
288
                                    temporalExtent)
1969
288
                         .as_nullable();
1970
288
        }
1971
325
        return ObjectDomain::create(scope, extent).as_nullable();
1972
359
    }
1973
1974
10.2k
    return nullptr;
1975
10.6k
}
1976
1977
// ---------------------------------------------------------------------------
1978
1979
UnitOfMeasure WKTParser::Private::buildUnit(const WKTNodeNNPtr &node,
1980
1.92k
                                            UnitOfMeasure::Type type) {
1981
1.92k
    const auto *nodeP = node->GP();
1982
1.92k
    const auto &children = nodeP->children();
1983
1.92k
    if ((type != UnitOfMeasure::Type::TIME && children.size() < 2) ||
1984
1.91k
        (type == UnitOfMeasure::Type::TIME && children.size() < 1)) {
1985
13
        ThrowNotEnoughChildren(nodeP->value());
1986
13
    }
1987
1.91k
    try {
1988
1.91k
        std::string unitName(stripQuotes(children[0]));
1989
1.91k
        PropertyMap properties(buildProperties(node));
1990
1.91k
        auto &idNode =
1991
1.91k
            nodeP->lookForChild(WKTConstants::ID, WKTConstants::AUTHORITY);
1992
1.91k
        if (!isNull(idNode) && idNode->GP()->childrenSize() < 2) {
1993
49
            emitRecoverableWarning("not enough children in " +
1994
49
                                   idNode->GP()->value() + " node");
1995
49
        }
1996
1.91k
        const bool hasValidIdNode =
1997
1.91k
            !isNull(idNode) && idNode->GP()->childrenSize() >= 2;
1998
1999
1.91k
        const auto &idNodeChildren(idNode->GP()->children());
2000
1.91k
        std::string codeSpace(hasValidIdNode ? stripQuotes(idNodeChildren[0])
2001
1.91k
                                             : std::string());
2002
1.91k
        std::string code(hasValidIdNode ? stripQuotes(idNodeChildren[1])
2003
1.91k
                                        : std::string());
2004
2005
1.91k
        bool queryDb = true;
2006
1.91k
        if (type == UnitOfMeasure::Type::UNKNOWN) {
2007
26
            if (ci_equal(unitName, "METER") || ci_equal(unitName, "METRE")) {
2008
0
                type = UnitOfMeasure::Type::LINEAR;
2009
0
                unitName = "metre";
2010
0
                if (codeSpace.empty()) {
2011
0
                    codeSpace = Identifier::EPSG;
2012
0
                    code = "9001";
2013
0
                    queryDb = false;
2014
0
                }
2015
26
            } else if (ci_equal(unitName, "DEGREE") ||
2016
26
                       ci_equal(unitName, "GRAD")) {
2017
0
                type = UnitOfMeasure::Type::ANGULAR;
2018
0
            }
2019
26
        }
2020
2021
1.91k
        if (esriStyle_ && dbContext_ && queryDb) {
2022
1.05k
            std::string outTableName;
2023
1.05k
            std::string authNameFromAlias;
2024
1.05k
            std::string codeFromAlias;
2025
1.05k
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2026
1.05k
                                                        std::string());
2027
1.05k
            auto officialName = authFactory->getOfficialNameFromAlias(
2028
1.05k
                unitName, "unit_of_measure", "ESRI", false, outTableName,
2029
1.05k
                authNameFromAlias, codeFromAlias);
2030
1.05k
            if (!officialName.empty()) {
2031
522
                unitName = std::move(officialName);
2032
522
                codeSpace = std::move(authNameFromAlias);
2033
522
                code = std::move(codeFromAlias);
2034
522
            }
2035
1.05k
        }
2036
2037
1.91k
        double convFactor = children.size() >= 2 ? asDouble(children[1]) : 0.0;
2038
1.91k
        constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
2039
1.91k
        constexpr double REL_ERROR = 1e-10;
2040
        // Fix common rounding errors
2041
1.91k
        if (std::fabs(convFactor - UnitOfMeasure::DEGREE.conversionToSI()) <
2042
1.91k
            REL_ERROR * convFactor) {
2043
552
            convFactor = UnitOfMeasure::DEGREE.conversionToSI();
2044
1.36k
        } else if (std::fabs(convFactor - US_FOOT_CONV_FACTOR) <
2045
1.36k
                   REL_ERROR * convFactor) {
2046
2
            convFactor = US_FOOT_CONV_FACTOR;
2047
2
        }
2048
2049
1.91k
        return UnitOfMeasure(unitName, convFactor, type, codeSpace, code);
2050
1.91k
    } catch (const std::exception &e) {
2051
17
        throw buildRethrow(__FUNCTION__, e);
2052
17
    }
2053
1.91k
}
2054
2055
// ---------------------------------------------------------------------------
2056
2057
// node here is a parent node, not a UNIT/LENGTHUNIT/ANGLEUNIT/TIMEUNIT/... node
2058
UnitOfMeasure WKTParser::Private::buildUnitInSubNode(const WKTNodeNNPtr &node,
2059
6.39k
                                                     UnitOfMeasure::Type type) {
2060
6.39k
    const auto *nodeP = node->GP();
2061
6.39k
    {
2062
6.39k
        auto &unitNode = nodeP->lookForChild(WKTConstants::LENGTHUNIT);
2063
6.39k
        if (!isNull(unitNode)) {
2064
1
            return buildUnit(unitNode, UnitOfMeasure::Type::LINEAR);
2065
1
        }
2066
6.39k
    }
2067
2068
6.39k
    {
2069
6.39k
        auto &unitNode = nodeP->lookForChild(WKTConstants::ANGLEUNIT);
2070
6.39k
        if (!isNull(unitNode)) {
2071
4
            return buildUnit(unitNode, UnitOfMeasure::Type::ANGULAR);
2072
4
        }
2073
6.39k
    }
2074
2075
6.39k
    {
2076
6.39k
        auto &unitNode = nodeP->lookForChild(WKTConstants::SCALEUNIT);
2077
6.39k
        if (!isNull(unitNode)) {
2078
0
            return buildUnit(unitNode, UnitOfMeasure::Type::SCALE);
2079
0
        }
2080
6.39k
    }
2081
2082
6.39k
    {
2083
6.39k
        auto &unitNode = nodeP->lookForChild(WKTConstants::TIMEUNIT);
2084
6.39k
        if (!isNull(unitNode)) {
2085
3
            return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
2086
3
        }
2087
6.39k
    }
2088
6.38k
    {
2089
6.38k
        auto &unitNode = nodeP->lookForChild(WKTConstants::TEMPORALQUANTITY);
2090
6.38k
        if (!isNull(unitNode)) {
2091
5
            return buildUnit(unitNode, UnitOfMeasure::Type::TIME);
2092
5
        }
2093
6.38k
    }
2094
2095
6.38k
    {
2096
6.38k
        auto &unitNode = nodeP->lookForChild(WKTConstants::PARAMETRICUNIT);
2097
6.38k
        if (!isNull(unitNode)) {
2098
0
            return buildUnit(unitNode, UnitOfMeasure::Type::PARAMETRIC);
2099
0
        }
2100
6.38k
    }
2101
2102
6.38k
    {
2103
6.38k
        auto &unitNode = nodeP->lookForChild(WKTConstants::UNIT);
2104
6.38k
        if (!isNull(unitNode)) {
2105
1.90k
            return buildUnit(unitNode, type);
2106
1.90k
        }
2107
6.38k
    }
2108
2109
4.47k
    return UnitOfMeasure::NONE;
2110
6.38k
}
2111
2112
// ---------------------------------------------------------------------------
2113
2114
1.60k
EllipsoidNNPtr WKTParser::Private::buildEllipsoid(const WKTNodeNNPtr &node) {
2115
1.60k
    const auto *nodeP = node->GP();
2116
1.60k
    const auto &children = nodeP->children();
2117
1.60k
    if (children.size() < 3) {
2118
10
        ThrowNotEnoughChildren(nodeP->value());
2119
10
    }
2120
1.59k
    try {
2121
1.59k
        UnitOfMeasure unit =
2122
1.59k
            buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR);
2123
1.59k
        if (unit == UnitOfMeasure::NONE) {
2124
1.22k
            unit = UnitOfMeasure::METRE;
2125
1.22k
        }
2126
1.59k
        Length semiMajorAxis(asDouble(children[1]), unit);
2127
        // Some WKT in the wild use "inf". Cf SPHEROID["unnamed",6370997,"inf"]
2128
        // in https://zenodo.org/record/3878979#.Y_P4g4CZNH4,
2129
        // https://zenodo.org/record/5831940#.Y_P4i4CZNH5
2130
        // or https://grasswiki.osgeo.org/wiki/Marine_Science
2131
1.59k
        const auto &invFlatteningChild = children[2];
2132
1.59k
        if (invFlatteningChild->GP()->value() == "\"inf\"") {
2133
0
            emitRecoverableWarning("Inverse flattening = \"inf\" is not "
2134
0
                                   "conformant, but understood");
2135
0
        }
2136
1.59k
        Scale invFlattening(invFlatteningChild->GP()->value() == "\"inf\""
2137
1.59k
                                ? 0
2138
1.59k
                                : asDouble(invFlatteningChild));
2139
1.59k
        const auto ellpsProperties = buildProperties(node);
2140
1.59k
        std::string ellpsName;
2141
1.59k
        ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName);
2142
1.59k
        const auto celestialBody(Ellipsoid::guessBodyName(
2143
1.59k
            dbContext_, semiMajorAxis.getSIValue(), ellpsName));
2144
1.59k
        if (invFlattening.getSIValue() == 0) {
2145
722
            return Ellipsoid::createSphere(ellpsProperties, semiMajorAxis,
2146
722
                                           celestialBody);
2147
874
        } else {
2148
874
            return Ellipsoid::createFlattenedSphere(
2149
874
                ellpsProperties, semiMajorAxis, invFlattening, celestialBody);
2150
874
        }
2151
1.59k
    } catch (const std::exception &e) {
2152
41
        throw buildRethrow(__FUNCTION__, e);
2153
41
    }
2154
1.59k
}
2155
2156
// ---------------------------------------------------------------------------
2157
2158
PrimeMeridianNNPtr WKTParser::Private::buildPrimeMeridian(
2159
295
    const WKTNodeNNPtr &node, const UnitOfMeasure &defaultAngularUnit) {
2160
295
    const auto *nodeP = node->GP();
2161
295
    const auto &children = nodeP->children();
2162
295
    if (children.size() < 2) {
2163
3
        ThrowNotEnoughChildren(nodeP->value());
2164
3
    }
2165
292
    auto name = stripQuotes(children[0]);
2166
292
    UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2167
292
    if (unit == UnitOfMeasure::NONE) {
2168
292
        unit = defaultAngularUnit;
2169
292
        if (unit == UnitOfMeasure::NONE) {
2170
1
            unit = UnitOfMeasure::DEGREE;
2171
1
        }
2172
292
    }
2173
292
    try {
2174
292
        double angleValue = asDouble(children[1]);
2175
2176
        // Correct for GDAL WKT1 and WKT1-ESRI departure
2177
292
        if (name == "Paris" && std::fabs(angleValue - 2.33722917) < 1e-8 &&
2178
0
            unit._isEquivalentTo(UnitOfMeasure::GRAD,
2179
0
                                 util::IComparable::Criterion::EQUIVALENT)) {
2180
0
            angleValue = 2.5969213;
2181
292
        } else {
2182
292
            static const struct {
2183
292
                const char *name;
2184
292
                int deg;
2185
292
                int min;
2186
292
                double sec;
2187
292
            } primeMeridiansDMS[] = {
2188
292
                {"Lisbon", -9, 7, 54.862},  {"Bogota", -74, 4, 51.3},
2189
292
                {"Madrid", -3, 41, 14.55},  {"Rome", 12, 27, 8.4},
2190
292
                {"Bern", 7, 26, 22.5},      {"Jakarta", 106, 48, 27.79},
2191
292
                {"Ferro", -17, 40, 0},      {"Brussels", 4, 22, 4.71},
2192
292
                {"Stockholm", 18, 3, 29.8}, {"Athens", 23, 42, 58.815},
2193
292
                {"Oslo", 10, 43, 22.5},     {"Paris RGS", 2, 20, 13.95},
2194
292
                {"Paris_RGS", 2, 20, 13.95}};
2195
2196
            // Current epsg.org output may use the EPSG:9110 "sexagesimal DMS"
2197
            // unit and a DD.MMSSsss value, but this will likely be changed to
2198
            // use decimal degree.
2199
            // Or WKT1 may for example use the Paris RGS decimal degree value
2200
            // but with a GEOGCS with UNIT["Grad"]
2201
3.72k
            for (const auto &pmDef : primeMeridiansDMS) {
2202
3.72k
                if (name == pmDef.name) {
2203
2
                    double dmsAsDecimalValue =
2204
2
                        (pmDef.deg >= 0 ? 1 : -1) *
2205
2
                        (std::abs(pmDef.deg) + pmDef.min / 100. +
2206
2
                         pmDef.sec / 10000.);
2207
2
                    double dmsAsDecimalDegreeValue =
2208
2
                        (pmDef.deg >= 0 ? 1 : -1) *
2209
2
                        (std::abs(pmDef.deg) + pmDef.min / 60. +
2210
2
                         pmDef.sec / 3600.);
2211
2
                    if (std::fabs(angleValue - dmsAsDecimalValue) < 1e-8 ||
2212
2
                        std::fabs(angleValue - dmsAsDecimalDegreeValue) <
2213
2
                            1e-8) {
2214
0
                        angleValue = dmsAsDecimalDegreeValue;
2215
0
                        unit = UnitOfMeasure::DEGREE;
2216
0
                    }
2217
2
                    break;
2218
2
                }
2219
3.72k
            }
2220
292
        }
2221
2222
292
        auto &properties = buildProperties(node);
2223
292
        if (dbContext_ && esriStyle_) {
2224
264
            std::string outTableName;
2225
264
            std::string codeFromAlias;
2226
264
            std::string authNameFromAlias;
2227
264
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2228
264
                                                        std::string());
2229
264
            auto officialName = authFactory->getOfficialNameFromAlias(
2230
264
                name, "prime_meridian", "ESRI", false, outTableName,
2231
264
                authNameFromAlias, codeFromAlias);
2232
264
            if (!officialName.empty()) {
2233
0
                properties.set(IdentifiedObject::NAME_KEY, officialName);
2234
0
                if (!authNameFromAlias.empty()) {
2235
0
                    auto identifiers = ArrayOfBaseObject::create();
2236
0
                    identifiers->add(Identifier::create(
2237
0
                        codeFromAlias,
2238
0
                        PropertyMap()
2239
0
                            .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2240
0
                            .set(Identifier::AUTHORITY_KEY,
2241
0
                                 authNameFromAlias)));
2242
0
                    properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2243
0
                                   identifiers);
2244
0
                }
2245
0
            }
2246
264
        }
2247
2248
292
        Angle angle(angleValue, unit);
2249
292
        return PrimeMeridian::create(properties, angle);
2250
292
    } catch (const std::exception &e) {
2251
7
        throw buildRethrow(__FUNCTION__, e);
2252
7
    }
2253
292
}
2254
2255
// ---------------------------------------------------------------------------
2256
2257
1.66k
optional<std::string> WKTParser::Private::getAnchor(const WKTNodeNNPtr &node) {
2258
2259
1.66k
    auto &anchorNode = node->GP()->lookForChild(WKTConstants::ANCHOR);
2260
1.66k
    if (anchorNode->GP()->childrenSize() == 1) {
2261
0
        return optional<std::string>(
2262
0
            stripQuotes(anchorNode->GP()->children()[0]));
2263
0
    }
2264
1.66k
    return optional<std::string>();
2265
1.66k
}
2266
2267
// ---------------------------------------------------------------------------
2268
2269
optional<common::Measure>
2270
1.64k
WKTParser::Private::getAnchorEpoch(const WKTNodeNNPtr &node) {
2271
2272
1.64k
    auto &anchorEpochNode = node->GP()->lookForChild(WKTConstants::ANCHOREPOCH);
2273
1.64k
    if (anchorEpochNode->GP()->childrenSize() == 1) {
2274
0
        try {
2275
0
            double value = asDouble(anchorEpochNode->GP()->children()[0]);
2276
0
            return optional<common::Measure>(
2277
0
                common::Measure(value, common::UnitOfMeasure::YEAR));
2278
0
        } catch (const std::exception &e) {
2279
0
            throw buildRethrow(__FUNCTION__, e);
2280
0
        }
2281
0
    }
2282
1.64k
    return optional<common::Measure>();
2283
1.64k
}
2284
// ---------------------------------------------------------------------------
2285
2286
static const PrimeMeridianNNPtr &
2287
fixupPrimeMeridan(const EllipsoidNNPtr &ellipsoid,
2288
1.89k
                  const PrimeMeridianNNPtr &pm) {
2289
1.89k
    return (ellipsoid->celestialBody() != Ellipsoid::EARTH &&
2290
868
            pm.get() == PrimeMeridian::GREENWICH.get())
2291
1.89k
               ? PrimeMeridian::REFERENCE_MERIDIAN
2292
1.89k
               : pm;
2293
1.89k
}
2294
2295
// ---------------------------------------------------------------------------
2296
2297
GeodeticReferenceFrameNNPtr WKTParser::Private::buildGeodeticReferenceFrame(
2298
    const WKTNodeNNPtr &node, const PrimeMeridianNNPtr &primeMeridian,
2299
1.05k
    const WKTNodeNNPtr &dynamicNode) {
2300
1.05k
    const auto *nodeP = node->GP();
2301
1.05k
    auto &ellipsoidNode =
2302
1.05k
        nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2303
1.05k
    if (isNull(ellipsoidNode)) {
2304
74
        ThrowMissing(WKTConstants::ELLIPSOID);
2305
74
    }
2306
979
    auto &properties = buildProperties(node);
2307
2308
    // do that before buildEllipsoid() so that esriStyle_ can be set
2309
979
    auto name = stripQuotes(nodeP->children()[0]);
2310
2311
979
    const auto identifyFromName = [&](const std::string &l_name) {
2312
134
        if (dbContext_) {
2313
129
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2314
129
                                                        std::string());
2315
129
            auto res = authFactory->createObjectsFromName(
2316
129
                l_name,
2317
129
                {AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME}, true,
2318
129
                1);
2319
129
            if (!res.empty()) {
2320
77
                bool foundDatumName = false;
2321
77
                const auto &refDatum = res.front();
2322
77
                if (metadata::Identifier::isEquivalentName(
2323
77
                        l_name.c_str(), refDatum->nameStr().c_str())) {
2324
12
                    foundDatumName = true;
2325
65
                } else if (refDatum->identifiers().size() == 1) {
2326
65
                    const auto &id = refDatum->identifiers()[0];
2327
65
                    const auto aliases =
2328
65
                        authFactory->databaseContext()->getAliases(
2329
65
                            *id->codeSpace(), id->code(), refDatum->nameStr(),
2330
65
                            "geodetic_datum", "not EPSG_OLD");
2331
82
                    for (const auto &alias : aliases) {
2332
82
                        if (metadata::Identifier::isEquivalentName(
2333
82
                                l_name.c_str(), alias.c_str())) {
2334
0
                            foundDatumName = true;
2335
0
                            break;
2336
0
                        }
2337
82
                    }
2338
65
                }
2339
77
                if (foundDatumName) {
2340
12
                    properties.set(IdentifiedObject::NAME_KEY,
2341
12
                                   refDatum->nameStr());
2342
12
                    if (!properties.get(Identifier::CODESPACE_KEY) &&
2343
12
                        refDatum->identifiers().size() == 1) {
2344
12
                        const auto &id = refDatum->identifiers()[0];
2345
12
                        auto identifiers = ArrayOfBaseObject::create();
2346
12
                        identifiers->add(Identifier::create(
2347
12
                            id->code(), PropertyMap()
2348
12
                                            .set(Identifier::CODESPACE_KEY,
2349
12
                                                 *id->codeSpace())
2350
12
                                            .set(Identifier::AUTHORITY_KEY,
2351
12
                                                 *id->codeSpace())));
2352
12
                        properties.set(IdentifiedObject::IDENTIFIERS_KEY,
2353
12
                                       identifiers);
2354
12
                    }
2355
12
                    return true;
2356
12
                }
2357
77
            } else {
2358
                // Get official name from database if AUTHORITY is present
2359
52
                auto &idNode = nodeP->lookForChild(WKTConstants::AUTHORITY);
2360
52
                if (!isNull(idNode)) {
2361
2
                    try {
2362
2
                        auto id = buildId(node, idNode, false, false);
2363
2
                        auto authFactory2 = AuthorityFactory::create(
2364
2
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
2365
2
                        auto dbDatum =
2366
2
                            authFactory2->createGeodeticDatum(id->code());
2367
2
                        properties.set(IdentifiedObject::NAME_KEY,
2368
2
                                       dbDatum->nameStr());
2369
2
                        return true;
2370
2
                    } catch (const std::exception &) {
2371
2
                    }
2372
2
                }
2373
52
            }
2374
129
        }
2375
122
        return false;
2376
134
    };
2377
2378
    // Remap GDAL WGS_1984 to EPSG v9 "World Geodetic System 1984" official
2379
    // name.
2380
979
    bool nameSet = false;
2381
979
    if (name == "WGS_1984") {
2382
0
        nameSet = true;
2383
0
        properties.set(IdentifiedObject::NAME_KEY,
2384
0
                       GeodeticReferenceFrame::EPSG_6326->nameStr());
2385
0
    }
2386
    // Also remap EPSG v10 datum ensemble names to non-ensemble EPSG v9
2387
979
    else if (internal::ends_with(name, " ensemble")) {
2388
0
        auto massagedName = DatumEnsemble::ensembleNameToNonEnsembleName(name);
2389
0
        if (!massagedName.empty()) {
2390
0
            nameSet = true;
2391
0
            properties.set(IdentifiedObject::NAME_KEY, massagedName);
2392
0
        }
2393
0
    }
2394
    // If we got hints this might be a ESRI WKT, then check in the DB to
2395
    // confirm
2396
979
    std::string officialName;
2397
979
    std::string authNameFromAlias;
2398
979
    std::string codeFromAlias;
2399
979
    if (!nameSet && maybeEsriStyle_ && dbContext_ &&
2400
731
        !(starts_with(name, "D_") || esriStyle_)) {
2401
466
        std::string outTableName;
2402
466
        auto authFactory =
2403
466
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
2404
466
        officialName = authFactory->getOfficialNameFromAlias(
2405
466
            name, "geodetic_datum", "ESRI", false, outTableName,
2406
466
            authNameFromAlias, codeFromAlias);
2407
466
        if (!officialName.empty()) {
2408
0
            maybeEsriStyle_ = false;
2409
0
            esriStyle_ = true;
2410
0
        }
2411
466
    }
2412
2413
979
    if (!nameSet && (starts_with(name, "D_") || esriStyle_)) {
2414
282
        esriStyle_ = true;
2415
282
        const char *tableNameForAlias = nullptr;
2416
282
        if (name == "D_WGS_1984") {
2417
137
            name = "World Geodetic System 1984";
2418
137
            authNameFromAlias = Identifier::EPSG;
2419
137
            codeFromAlias = "6326";
2420
145
        } else if (name == "D_ETRS_1989") {
2421
0
            name = "European Terrestrial Reference System 1989";
2422
0
            authNameFromAlias = Identifier::EPSG;
2423
0
            codeFromAlias = "6258";
2424
145
        } else if (name == "D_unknown") {
2425
0
            name = "unknown";
2426
145
        } else if (name == "D_Unknown_based_on_WGS_84_ellipsoid") {
2427
0
            name = "Unknown based on WGS 84 ellipsoid";
2428
145
        } else {
2429
145
            tableNameForAlias = "geodetic_datum";
2430
145
        }
2431
2432
282
        bool setNameAndId = true;
2433
282
        if (dbContext_ && tableNameForAlias) {
2434
140
            if (officialName.empty()) {
2435
140
                std::string outTableName;
2436
140
                auto authFactory = AuthorityFactory::create(
2437
140
                    NN_NO_CHECK(dbContext_), std::string());
2438
140
                officialName = authFactory->getOfficialNameFromAlias(
2439
140
                    name, tableNameForAlias, "ESRI", false, outTableName,
2440
140
                    authNameFromAlias, codeFromAlias);
2441
140
            }
2442
140
            if (officialName.empty()) {
2443
48
                if (starts_with(name, "D_")) {
2444
                    // For the case of "D_GDA2020" where there is no D_GDA2020
2445
                    // ESRI alias, so just try without the D_ prefix.
2446
36
                    const auto nameWithoutDPrefix = name.substr(2);
2447
36
                    if (identifyFromName(nameWithoutDPrefix)) {
2448
0
                        setNameAndId =
2449
0
                            false; // already done in identifyFromName()
2450
0
                    }
2451
36
                }
2452
92
            } else {
2453
92
                if (primeMeridian->nameStr() !=
2454
92
                    PrimeMeridian::GREENWICH->nameStr()) {
2455
0
                    auto nameWithPM =
2456
0
                        officialName + " (" + primeMeridian->nameStr() + ")";
2457
0
                    if (dbContext_->isKnownName(nameWithPM, "geodetic_datum")) {
2458
0
                        officialName = std::move(nameWithPM);
2459
0
                    }
2460
0
                }
2461
92
                name = std::move(officialName);
2462
92
            }
2463
140
        }
2464
2465
282
        if (setNameAndId) {
2466
282
            properties.set(IdentifiedObject::NAME_KEY, name);
2467
282
            if (!authNameFromAlias.empty()) {
2468
229
                auto identifiers = ArrayOfBaseObject::create();
2469
229
                identifiers->add(Identifier::create(
2470
229
                    codeFromAlias,
2471
229
                    PropertyMap()
2472
229
                        .set(Identifier::CODESPACE_KEY, authNameFromAlias)
2473
229
                        .set(Identifier::AUTHORITY_KEY, authNameFromAlias)));
2474
229
                properties.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
2475
229
            }
2476
282
        }
2477
697
    } else if (!nameSet && name.find('_') != std::string::npos) {
2478
        // Likely coming from WKT1
2479
98
        identifyFromName(name);
2480
98
    }
2481
2482
979
    auto ellipsoid = buildEllipsoid(ellipsoidNode);
2483
979
    const auto &primeMeridianModified =
2484
979
        fixupPrimeMeridan(ellipsoid, primeMeridian);
2485
2486
979
    auto &TOWGS84Node = nodeP->lookForChild(WKTConstants::TOWGS84);
2487
979
    if (!isNull(TOWGS84Node)) {
2488
1
        const auto &TOWGS84Children = TOWGS84Node->GP()->children();
2489
1
        const size_t TOWGS84Size = TOWGS84Children.size();
2490
1
        if (TOWGS84Size == 3 || TOWGS84Size == 7) {
2491
1
            try {
2492
2
                for (const auto &child : TOWGS84Children) {
2493
2
                    toWGS84Parameters_.push_back(asDouble(child));
2494
2
                }
2495
2496
1
                if (TOWGS84Size == 7 && dbContext_) {
2497
0
                    dbContext_->toWGS84AutocorrectWrongValues(
2498
0
                        toWGS84Parameters_[0], toWGS84Parameters_[1],
2499
0
                        toWGS84Parameters_[2], toWGS84Parameters_[3],
2500
0
                        toWGS84Parameters_[4], toWGS84Parameters_[5],
2501
0
                        toWGS84Parameters_[6]);
2502
0
                }
2503
2504
1
                for (size_t i = TOWGS84Size; i < 7; ++i) {
2505
0
                    toWGS84Parameters_.push_back(0.0);
2506
0
                }
2507
1
            } catch (const std::exception &) {
2508
1
                throw ParsingException("Invalid TOWGS84 node");
2509
1
            }
2510
1
        } else {
2511
0
            throw ParsingException("Invalid TOWGS84 node");
2512
0
        }
2513
1
    }
2514
2515
978
    auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
2516
978
    const auto &extensionChildren = extensionNode->GP()->children();
2517
978
    if (extensionChildren.size() == 2) {
2518
6
        if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
2519
0
            datumPROJ4Grids_ = stripQuotes(extensionChildren[1]);
2520
0
        }
2521
6
    }
2522
2523
978
    if (!isNull(dynamicNode)) {
2524
11
        double frameReferenceEpoch = 0.0;
2525
11
        util::optional<std::string> modelName;
2526
11
        parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
2527
11
        return DynamicGeodeticReferenceFrame::create(
2528
11
            properties, ellipsoid, getAnchor(node), primeMeridianModified,
2529
11
            common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
2530
11
            modelName);
2531
11
    }
2532
2533
967
    return GeodeticReferenceFrame::create(properties, ellipsoid,
2534
967
                                          getAnchor(node), getAnchorEpoch(node),
2535
967
                                          primeMeridianModified);
2536
978
}
2537
2538
// ---------------------------------------------------------------------------
2539
2540
DatumEnsembleNNPtr
2541
WKTParser::Private::buildDatumEnsemble(const WKTNodeNNPtr &node,
2542
                                       const PrimeMeridianPtr &primeMeridian,
2543
216
                                       bool expectEllipsoid) {
2544
216
    const auto *nodeP = node->GP();
2545
216
    auto &ellipsoidNode =
2546
216
        nodeP->lookForChild(WKTConstants::ELLIPSOID, WKTConstants::SPHEROID);
2547
216
    if (expectEllipsoid && isNull(ellipsoidNode)) {
2548
21
        ThrowMissing(WKTConstants::ELLIPSOID);
2549
21
    }
2550
2551
195
    auto properties = buildProperties(node);
2552
2553
195
    std::vector<DatumNNPtr> datums;
2554
3.46k
    for (const auto &subNode : nodeP->children()) {
2555
3.46k
        if (ci_equal(subNode->GP()->value(), WKTConstants::MEMBER)) {
2556
886
            if (subNode->GP()->childrenSize() == 0) {
2557
6
                throw ParsingException("Invalid MEMBER node");
2558
6
            }
2559
880
            if (expectEllipsoid) {
2560
627
                datums.emplace_back(GeodeticReferenceFrame::create(
2561
627
                    buildProperties(subNode), buildEllipsoid(ellipsoidNode),
2562
627
                    optional<std::string>(),
2563
627
                    primeMeridian ? NN_NO_CHECK(primeMeridian)
2564
627
                                  : PrimeMeridian::GREENWICH));
2565
627
            } else {
2566
253
                datums.emplace_back(
2567
253
                    VerticalReferenceFrame::create(buildProperties(subNode)));
2568
253
            }
2569
880
        }
2570
3.46k
    }
2571
2572
189
    if (datums.empty() && !nodeP->children().empty()) {
2573
18
        auto name = stripQuotes(nodeP->children()[0]);
2574
18
        if (dbContext_) {
2575
15
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
2576
15
                                                        std::string());
2577
15
            auto res = authFactory->createObjectsFromName(
2578
15
                name, {AuthorityFactory::ObjectType::DATUM_ENSEMBLE}, true, 1);
2579
15
            if (res.size() == 1) {
2580
1
                auto datumEnsemble =
2581
1
                    dynamic_cast<const DatumEnsemble *>(res.front().get());
2582
1
                if (datumEnsemble) {
2583
1
                    datums = datumEnsemble->datums();
2584
1
                }
2585
14
            } else {
2586
14
                throw ParsingException(
2587
14
                    "No entry for datum ensemble '" + name +
2588
14
                    "' in database, and no explicit member specified");
2589
14
            }
2590
15
        } else {
2591
3
            throw ParsingException("Datum ensemble '" + name +
2592
3
                                   "' has no explicit member specified and no "
2593
3
                                   "connection to database");
2594
3
        }
2595
18
    }
2596
2597
172
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::ENSEMBLEACCURACY);
2598
172
    auto &accuracyNodeChildren = accuracyNode->GP()->children();
2599
172
    if (accuracyNodeChildren.empty()) {
2600
96
        ThrowMissing(WKTConstants::ENSEMBLEACCURACY);
2601
96
    }
2602
76
    auto accuracy =
2603
76
        PositionalAccuracy::create(accuracyNodeChildren[0]->GP()->value());
2604
2605
76
    try {
2606
76
        return DatumEnsemble::create(properties, datums, accuracy);
2607
76
    } catch (const util::Exception &e) {
2608
4
        throw buildRethrow(__FUNCTION__, e);
2609
4
    }
2610
76
}
2611
2612
// ---------------------------------------------------------------------------
2613
2614
0
MeridianNNPtr WKTParser::Private::buildMeridian(const WKTNodeNNPtr &node) {
2615
0
    const auto *nodeP = node->GP();
2616
0
    const auto &children = nodeP->children();
2617
0
    if (children.size() < 2) {
2618
0
        ThrowNotEnoughChildren(nodeP->value());
2619
0
    }
2620
0
    UnitOfMeasure unit = buildUnitInSubNode(node, UnitOfMeasure::Type::ANGULAR);
2621
0
    try {
2622
0
        double angleValue = asDouble(children[0]);
2623
0
        Angle angle(angleValue, unit);
2624
0
        return Meridian::create(angle);
2625
0
    } catch (const std::exception &e) {
2626
0
        throw buildRethrow(__FUNCTION__, e);
2627
0
    }
2628
0
}
2629
2630
// ---------------------------------------------------------------------------
2631
2632
7
PROJ_NO_RETURN static void ThrowParsingExceptionMissingUNIT() {
2633
7
    throw ParsingException("buildCS: missing UNIT");
2634
7
}
2635
2636
// ---------------------------------------------------------------------------
2637
2638
CoordinateSystemAxisNNPtr
2639
WKTParser::Private::buildAxis(const WKTNodeNNPtr &node,
2640
                              const UnitOfMeasure &unitIn,
2641
                              const UnitOfMeasure::Type &unitType,
2642
171
                              bool isGeocentric, int expectedOrderNum) {
2643
171
    const auto *nodeP = node->GP();
2644
171
    const auto &children = nodeP->children();
2645
171
    if (children.size() < 2) {
2646
7
        ThrowNotEnoughChildren(nodeP->value());
2647
7
    }
2648
2649
164
    auto &orderNode = nodeP->lookForChild(WKTConstants::ORDER);
2650
164
    if (!isNull(orderNode)) {
2651
6
        const auto &orderNodeChildren = orderNode->GP()->children();
2652
6
        if (orderNodeChildren.size() != 1) {
2653
1
            ThrowNotEnoughChildren(WKTConstants::ORDER);
2654
1
        }
2655
5
        const auto &order = orderNodeChildren[0]->GP()->value();
2656
5
        int orderNum;
2657
5
        try {
2658
5
            orderNum = std::stoi(order);
2659
5
        } catch (const std::exception &) {
2660
2
            throw ParsingException(
2661
2
                concat("buildAxis: invalid ORDER value: ", order));
2662
2
        }
2663
3
        if (orderNum != expectedOrderNum) {
2664
2
            throw ParsingException(
2665
2
                concat("buildAxis: did not get expected ORDER value: ", order));
2666
2
        }
2667
3
    }
2668
2669
    // The axis designation in WK2 can be: "name", "(abbrev)" or "name
2670
    // (abbrev)"
2671
159
    std::string axisDesignation(stripQuotes(children[0]));
2672
159
    size_t sepPos = axisDesignation.find(" (");
2673
159
    std::string axisName;
2674
159
    std::string abbreviation;
2675
159
    if (sepPos != std::string::npos && axisDesignation.back() == ')') {
2676
13
        axisName = CoordinateSystemAxis::normalizeAxisName(
2677
13
            axisDesignation.substr(0, sepPos));
2678
13
        abbreviation = axisDesignation.substr(sepPos + 2);
2679
13
        abbreviation.resize(abbreviation.size() - 1);
2680
146
    } else if (!axisDesignation.empty() && axisDesignation[0] == '(' &&
2681
41
               axisDesignation.back() == ')') {
2682
30
        abbreviation = axisDesignation.substr(1, axisDesignation.size() - 2);
2683
30
        if (abbreviation == AxisAbbreviation::E) {
2684
2
            axisName = AxisName::Easting;
2685
28
        } else if (abbreviation == AxisAbbreviation::N) {
2686
0
            axisName = AxisName::Northing;
2687
28
        } else if (abbreviation == AxisAbbreviation::lat) {
2688
0
            axisName = AxisName::Latitude;
2689
28
        } else if (abbreviation == AxisAbbreviation::lon) {
2690
0
            axisName = AxisName::Longitude;
2691
0
        }
2692
116
    } else {
2693
116
        axisName = CoordinateSystemAxis::normalizeAxisName(axisDesignation);
2694
116
        if (axisName == AxisName::Latitude) {
2695
0
            abbreviation = AxisAbbreviation::lat;
2696
116
        } else if (axisName == AxisName::Longitude) {
2697
0
            abbreviation = AxisAbbreviation::lon;
2698
116
        } else if (axisName == AxisName::Ellipsoidal_height) {
2699
0
            abbreviation = AxisAbbreviation::h;
2700
0
        }
2701
116
    }
2702
159
    const std::string &dirString = children[1]->GP()->value();
2703
159
    const AxisDirection *direction = AxisDirection::valueOf(dirString);
2704
2705
    // WKT2, geocentric CS: axis names are omitted
2706
159
    if (axisName.empty()) {
2707
29
        if (direction == &AxisDirection::GEOCENTRIC_X &&
2708
0
            abbreviation == AxisAbbreviation::X) {
2709
0
            axisName = AxisName::Geocentric_X;
2710
29
        } else if (direction == &AxisDirection::GEOCENTRIC_Y &&
2711
5
                   abbreviation == AxisAbbreviation::Y) {
2712
0
            axisName = AxisName::Geocentric_Y;
2713
29
        } else if (direction == &AxisDirection::GEOCENTRIC_Z &&
2714
0
                   abbreviation == AxisAbbreviation::Z) {
2715
0
            axisName = AxisName::Geocentric_Z;
2716
0
        }
2717
29
    }
2718
2719
    // WKT1
2720
159
    if (!direction && isGeocentric && axisName == AxisName::Geocentric_X) {
2721
15
        abbreviation = AxisAbbreviation::X;
2722
15
        direction = &AxisDirection::GEOCENTRIC_X;
2723
144
    } else if (!direction && isGeocentric &&
2724
17
               axisName == AxisName::Geocentric_Y) {
2725
1
        abbreviation = AxisAbbreviation::Y;
2726
1
        direction = &AxisDirection::GEOCENTRIC_Y;
2727
143
    } else if (isGeocentric && axisName == AxisName::Geocentric_Z &&
2728
6
               (dirString == AxisDirectionWKT1::NORTH.toString() ||
2729
6
                dirString == AxisDirectionWKT1::OTHER.toString())) {
2730
0
        abbreviation = AxisAbbreviation::Z;
2731
0
        direction = &AxisDirection::GEOCENTRIC_Z;
2732
143
    } else if (dirString == AxisDirectionWKT1::OTHER.toString()) {
2733
0
        direction = &AxisDirection::UNSPECIFIED;
2734
143
    } else if (dirString == "UNKNOWN") {
2735
        // Found in WKT1 of NSIDC's EASE-Grid Sea Ice Age datasets.
2736
        // Cf https://github.com/OSGeo/gdal/issues/7210
2737
0
        emitRecoverableWarning("UNKNOWN is not a valid direction name.");
2738
0
        direction = &AxisDirection::UNSPECIFIED;
2739
0
    }
2740
2741
159
    if (!direction) {
2742
88
        throw ParsingException(
2743
88
            concat("unhandled axis direction: ", children[1]->GP()->value()));
2744
88
    }
2745
71
    UnitOfMeasure unit(buildUnitInSubNode(node));
2746
71
    if (unit == UnitOfMeasure::NONE) {
2747
        // If no unit in the AXIS node, use the one potentially coming from
2748
        // the CS.
2749
59
        unit = unitIn;
2750
59
        if (unit == UnitOfMeasure::NONE &&
2751
7
            unitType != UnitOfMeasure::Type::NONE &&
2752
7
            unitType != UnitOfMeasure::Type::TIME) {
2753
7
            ThrowParsingExceptionMissingUNIT();
2754
7
        }
2755
59
    }
2756
2757
64
    auto &meridianNode = nodeP->lookForChild(WKTConstants::MERIDIAN);
2758
2759
64
    util::optional<double> minVal;
2760
64
    auto &axisMinValueNode = nodeP->lookForChild(WKTConstants::AXISMINVALUE);
2761
64
    if (!isNull(axisMinValueNode)) {
2762
0
        const auto &axisMinValueNodeChildren =
2763
0
            axisMinValueNode->GP()->children();
2764
0
        if (axisMinValueNodeChildren.size() != 1) {
2765
0
            ThrowNotEnoughChildren(WKTConstants::AXISMINVALUE);
2766
0
        }
2767
0
        const auto &val = axisMinValueNodeChildren[0];
2768
0
        try {
2769
0
            minVal = asDouble(val);
2770
0
        } catch (const std::exception &) {
2771
0
            throw ParsingException(concat(
2772
0
                "buildAxis: invalid AXISMINVALUE value: ", val->GP()->value()));
2773
0
        }
2774
0
    }
2775
2776
64
    util::optional<double> maxVal;
2777
64
    auto &axisMaxValueNode = nodeP->lookForChild(WKTConstants::AXISMAXVALUE);
2778
64
    if (!isNull(axisMaxValueNode)) {
2779
1
        const auto &axisMaxValueNodeChildren =
2780
1
            axisMaxValueNode->GP()->children();
2781
1
        if (axisMaxValueNodeChildren.size() != 1) {
2782
1
            ThrowNotEnoughChildren(WKTConstants::AXISMAXVALUE);
2783
1
        }
2784
0
        const auto &val = axisMaxValueNodeChildren[0];
2785
0
        try {
2786
0
            maxVal = asDouble(val);
2787
0
        } catch (const std::exception &) {
2788
0
            throw ParsingException(concat(
2789
0
                "buildAxis: invalid AXISMAXVALUE value: ", val->GP()->value()));
2790
0
        }
2791
0
    }
2792
2793
63
    util::optional<RangeMeaning> rangeMeaning;
2794
63
    auto &rangeMeaningNode = nodeP->lookForChild(WKTConstants::RANGEMEANING);
2795
63
    if (!isNull(rangeMeaningNode)) {
2796
6
        const auto &rangeMeaningNodeChildren =
2797
6
            rangeMeaningNode->GP()->children();
2798
6
        if (rangeMeaningNodeChildren.size() != 1) {
2799
1
            ThrowNotEnoughChildren(WKTConstants::RANGEMEANING);
2800
1
        }
2801
5
        const std::string &val = rangeMeaningNodeChildren[0]->GP()->value();
2802
5
        const RangeMeaning *meaning = RangeMeaning::valueOf(val);
2803
5
        if (meaning == nullptr) {
2804
5
            throw ParsingException(
2805
5
                concat("buildAxis: invalid RANGEMEANING value: ", val));
2806
5
        }
2807
0
        rangeMeaning = util::optional<RangeMeaning>(*meaning);
2808
0
    }
2809
2810
57
    return CoordinateSystemAxis::create(
2811
57
        buildProperties(node).set(IdentifiedObject::NAME_KEY, axisName),
2812
57
        abbreviation, *direction, unit, minVal, maxVal, rangeMeaning,
2813
57
        !isNull(meridianNode) ? buildMeridian(meridianNode).as_nullable()
2814
57
                              : nullptr);
2815
63
}
2816
2817
// ---------------------------------------------------------------------------
2818
2819
static const PropertyMap emptyPropertyMap{};
2820
2821
// ---------------------------------------------------------------------------
2822
2823
5
PROJ_NO_RETURN static void ThrowParsingException(const std::string &msg) {
2824
5
    throw ParsingException(msg);
2825
5
}
2826
2827
// ---------------------------------------------------------------------------
2828
2829
static ParsingException
2830
20
buildParsingExceptionInvalidAxisCount(const std::string &csType) {
2831
20
    return ParsingException(
2832
20
        concat("buildCS: invalid CS axis count for ", csType));
2833
20
}
2834
2835
// ---------------------------------------------------------------------------
2836
2837
void WKTParser::Private::emitRecoverableMissingUNIT(
2838
1.29k
    const std::string &parentNodeName, const UnitOfMeasure &fallbackUnit) {
2839
1.29k
    std::string msg("buildCS: missing UNIT in ");
2840
1.29k
    msg += parentNodeName;
2841
1.29k
    if (!strict_ && fallbackUnit == UnitOfMeasure::METRE) {
2842
872
        msg += ". Assuming metre";
2843
872
    } else if (!strict_ && fallbackUnit == UnitOfMeasure::DEGREE) {
2844
427
        msg += ". Assuming degree";
2845
427
    }
2846
1.29k
    emitRecoverableWarning(msg);
2847
1.29k
}
2848
2849
// ---------------------------------------------------------------------------
2850
2851
CoordinateSystemNNPtr
2852
WKTParser::Private::buildCS(const WKTNodeNNPtr &node, /* maybe null */
2853
                            const WKTNodeNNPtr &parentNode,
2854
2.47k
                            const UnitOfMeasure &defaultAngularUnit) {
2855
2.47k
    bool isGeocentric = false;
2856
2.47k
    std::string csType;
2857
2.47k
    const int numberOfAxis =
2858
2.47k
        parentNode->countChildrenOfName(WKTConstants::AXIS);
2859
2.47k
    int axisCount = numberOfAxis;
2860
2.47k
    const auto &parentNodeName = parentNode->GP()->value();
2861
2.47k
    if (!isNull(node)) {
2862
27
        const auto *nodeP = node->GP();
2863
27
        const auto &children = nodeP->children();
2864
27
        if (children.size() < 2) {
2865
6
            ThrowNotEnoughChildren(nodeP->value());
2866
6
        }
2867
21
        csType = children[0]->GP()->value();
2868
21
        try {
2869
21
            axisCount = std::stoi(children[1]->GP()->value());
2870
21
        } catch (const std::exception &) {
2871
5
            ThrowParsingException(concat("buildCS: invalid CS axis count: ",
2872
5
                                         children[1]->GP()->value()));
2873
5
        }
2874
2.44k
    } else {
2875
2.44k
        const char *csTypeCStr = CartesianCS::WKT2_TYPE;
2876
2.44k
        if (ci_equal(parentNodeName, WKTConstants::GEOCCS)) {
2877
            // csTypeCStr = CartesianCS::WKT2_TYPE;
2878
83
            isGeocentric = true;
2879
83
            if (axisCount == 0) {
2880
51
                auto unit =
2881
51
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2882
51
                if (unit == UnitOfMeasure::NONE) {
2883
42
                    unit = UnitOfMeasure::METRE;
2884
42
                    emitRecoverableMissingUNIT(parentNodeName, unit);
2885
42
                }
2886
51
                return CartesianCS::createGeocentric(unit);
2887
51
            }
2888
2.36k
        } else if (ci_equal(parentNodeName, WKTConstants::GEOGCS)) {
2889
851
            csTypeCStr = EllipsoidalCS::WKT2_TYPE;
2890
851
            if (axisCount == 0) {
2891
                // Missing axis with GEOGCS ? Presumably Long/Lat order
2892
                // implied
2893
851
                auto unit = buildUnitInSubNode(parentNode,
2894
851
                                               UnitOfMeasure::Type::ANGULAR);
2895
851
                if (unit == UnitOfMeasure::NONE) {
2896
427
                    unit = defaultAngularUnit;
2897
427
                    emitRecoverableMissingUNIT(parentNodeName, unit);
2898
427
                }
2899
2900
                // ESRI WKT for geographic 3D CRS
2901
851
                auto &linUnitNode =
2902
851
                    parentNode->GP()->lookForChild(WKTConstants::LINUNIT);
2903
851
                if (!isNull(linUnitNode)) {
2904
3
                    return EllipsoidalCS::
2905
3
                        createLongitudeLatitudeEllipsoidalHeight(
2906
3
                            unit, buildUnit(linUnitNode,
2907
3
                                            UnitOfMeasure::Type::LINEAR));
2908
3
                }
2909
2910
                // WKT1 --> long/lat
2911
848
                return EllipsoidalCS::createLongitudeLatitude(unit);
2912
851
            }
2913
1.51k
        } else if (ci_equal(parentNodeName, WKTConstants::BASEGEODCRS) ||
2914
1.51k
                   ci_equal(parentNodeName, WKTConstants::BASEGEOGCRS)) {
2915
0
            csTypeCStr = EllipsoidalCS::WKT2_TYPE;
2916
0
            if (axisCount == 0) {
2917
0
                auto unit = buildUnitInSubNode(parentNode,
2918
0
                                               UnitOfMeasure::Type::ANGULAR);
2919
0
                if (unit == UnitOfMeasure::NONE) {
2920
0
                    unit = defaultAngularUnit;
2921
0
                }
2922
                // WKT2 --> presumably lat/long
2923
0
                return EllipsoidalCS::createLatitudeLongitude(unit);
2924
0
            }
2925
1.51k
        } else if (ci_equal(parentNodeName, WKTConstants::PROJCS) ||
2926
873
                   ci_equal(parentNodeName, WKTConstants::BASEPROJCRS) ||
2927
873
                   ci_equal(parentNodeName, WKTConstants::BASEENGCRS)) {
2928
639
            csTypeCStr = CartesianCS::WKT2_TYPE;
2929
639
            if (axisCount == 0) {
2930
639
                auto unit =
2931
639
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2932
639
                if (unit == UnitOfMeasure::NONE) {
2933
358
                    unit = UnitOfMeasure::METRE;
2934
358
                    if (ci_equal(parentNodeName, WKTConstants::PROJCS)) {
2935
358
                        emitRecoverableMissingUNIT(parentNodeName, unit);
2936
358
                    }
2937
358
                }
2938
639
                return CartesianCS::createEastingNorthing(unit);
2939
639
            }
2940
873
        } else if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2941
861
                   ci_equal(parentNodeName, WKTConstants::VERTCS) ||
2942
558
                   ci_equal(parentNodeName, WKTConstants::BASEVERTCRS)) {
2943
558
            csTypeCStr = VerticalCS::WKT2_TYPE;
2944
2945
558
            bool downDirection = false;
2946
558
            if (ci_equal(parentNodeName, WKTConstants::VERTCS)) // ESRI
2947
546
            {
2948
2.40k
                for (const auto &childNode : parentNode->GP()->children()) {
2949
2.40k
                    const auto &childNodeChildren = childNode->GP()->children();
2950
2.40k
                    if (childNodeChildren.size() == 2 &&
2951
230
                        ci_equal(childNode->GP()->value(),
2952
230
                                 WKTConstants::PARAMETER) &&
2953
11
                        childNodeChildren[0]->GP()->value() ==
2954
11
                            "\"Direction\"") {
2955
0
                        const auto &paramValue =
2956
0
                            childNodeChildren[1]->GP()->value();
2957
0
                        try {
2958
0
                            double val = asDouble(childNodeChildren[1]);
2959
0
                            if (val == 1.0) {
2960
                                // ok
2961
0
                            } else if (val == -1.0) {
2962
0
                                downDirection = true;
2963
0
                            }
2964
0
                        } catch (const std::exception &) {
2965
0
                            throw ParsingException(
2966
0
                                concat("unhandled parameter value type : ",
2967
0
                                       paramValue));
2968
0
                        }
2969
0
                    }
2970
2.40k
                }
2971
546
            }
2972
2973
558
            if (axisCount == 0) {
2974
496
                auto unit =
2975
496
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2976
496
                if (unit == UnitOfMeasure::NONE) {
2977
410
                    unit = UnitOfMeasure::METRE;
2978
410
                    if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
2979
410
                        ci_equal(parentNodeName, WKTConstants::VERTCS)) {
2980
410
                        emitRecoverableMissingUNIT(parentNodeName, unit);
2981
410
                    }
2982
410
                }
2983
496
                if (downDirection) {
2984
0
                    return VerticalCS::create(
2985
0
                        util::PropertyMap(),
2986
0
                        CoordinateSystemAxis::create(
2987
0
                            util::PropertyMap().set(IdentifiedObject::NAME_KEY,
2988
0
                                                    "depth"),
2989
0
                            "D", AxisDirection::DOWN, unit));
2990
0
                }
2991
496
                return VerticalCS::createGravityRelatedHeight(unit);
2992
496
            }
2993
558
        } else if (ci_equal(parentNodeName, WKTConstants::LOCAL_CS)) {
2994
315
            if (axisCount == 0) {
2995
236
                auto unit =
2996
236
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
2997
236
                if (unit == UnitOfMeasure::NONE) {
2998
233
                    unit = UnitOfMeasure::METRE;
2999
233
                }
3000
236
                return CartesianCS::createEastingNorthing(unit);
3001
236
            } else if (axisCount == 1) {
3002
74
                csTypeCStr = VerticalCS::WKT2_TYPE;
3003
74
            } else if (axisCount == 2 || axisCount == 3) {
3004
5
                csTypeCStr = CartesianCS::WKT2_TYPE;
3005
5
            } else {
3006
0
                throw ParsingException(
3007
0
                    "buildCS: unexpected AXIS count for LOCAL_CS");
3008
0
            }
3009
315
        } else if (ci_equal(parentNodeName, WKTConstants::BASEPARAMCRS)) {
3010
0
            csTypeCStr = ParametricCS::WKT2_TYPE;
3011
0
            if (axisCount == 0) {
3012
0
                auto unit =
3013
0
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::LINEAR);
3014
0
                if (unit == UnitOfMeasure::NONE) {
3015
0
                    unit = UnitOfMeasure("unknown", 1,
3016
0
                                         UnitOfMeasure::Type::PARAMETRIC);
3017
0
                }
3018
0
                return ParametricCS::create(
3019
0
                    emptyPropertyMap,
3020
0
                    CoordinateSystemAxis::create(
3021
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
3022
0
                                          "unknown parametric"),
3023
0
                        std::string(), AxisDirection::UNSPECIFIED, unit));
3024
0
            }
3025
0
        } else if (ci_equal(parentNodeName, WKTConstants::BASETIMECRS)) {
3026
0
            csTypeCStr = TemporalCS::WKT2_2015_TYPE;
3027
0
            if (axisCount == 0) {
3028
0
                auto unit =
3029
0
                    buildUnitInSubNode(parentNode, UnitOfMeasure::Type::TIME);
3030
0
                if (unit == UnitOfMeasure::NONE) {
3031
0
                    unit =
3032
0
                        UnitOfMeasure("unknown", 1, UnitOfMeasure::Type::TIME);
3033
0
                }
3034
0
                return DateTimeTemporalCS::create(
3035
0
                    emptyPropertyMap,
3036
0
                    CoordinateSystemAxis::create(
3037
0
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
3038
0
                                          "unknown temporal"),
3039
0
                        std::string(), AxisDirection::FUTURE, unit));
3040
0
            }
3041
0
        } else {
3042
            // Shouldn't happen normally
3043
0
            throw ParsingException(
3044
0
                concat("buildCS: unexpected parent node: ", parentNodeName));
3045
0
        }
3046
173
        csType = csTypeCStr;
3047
173
    }
3048
3049
189
    if (axisCount != 1 && axisCount != 2 && axisCount != 3) {
3050
16
        throw buildParsingExceptionInvalidAxisCount(csType);
3051
16
    }
3052
173
    if (numberOfAxis != axisCount) {
3053
0
        throw ParsingException("buildCS: declared number of axis by CS node "
3054
0
                               "and number of AXIS are inconsistent");
3055
0
    }
3056
3057
173
    const auto unitType =
3058
173
        ci_equal(csType, EllipsoidalCS::WKT2_TYPE)
3059
173
            ? UnitOfMeasure::Type::ANGULAR
3060
173
        : ci_equal(csType, OrdinalCS::WKT2_TYPE) ? UnitOfMeasure::Type::NONE
3061
173
        : ci_equal(csType, ParametricCS::WKT2_TYPE)
3062
173
            ? UnitOfMeasure::Type::PARAMETRIC
3063
173
        : ci_equal(csType, CartesianCS::WKT2_TYPE) ||
3064
136
                ci_equal(csType, VerticalCS::WKT2_TYPE) ||
3065
0
                ci_equal(csType, AffineCS::WKT2_TYPE)
3066
173
            ? UnitOfMeasure::Type::LINEAR
3067
173
        : (ci_equal(csType, TemporalCS::WKT2_2015_TYPE) ||
3068
0
           ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE) ||
3069
0
           ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE) ||
3070
0
           ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE))
3071
0
            ? UnitOfMeasure::Type::TIME
3072
0
            : UnitOfMeasure::Type::UNKNOWN;
3073
173
    UnitOfMeasure unit = buildUnitInSubNode(parentNode, unitType);
3074
3075
173
    if (unit == UnitOfMeasure::NONE) {
3076
164
        if (ci_equal(parentNodeName, WKTConstants::VERT_CS) ||
3077
164
            ci_equal(parentNodeName, WKTConstants::VERTCS)) {
3078
62
            unit = UnitOfMeasure::METRE;
3079
62
            emitRecoverableMissingUNIT(parentNodeName, unit);
3080
62
        }
3081
164
    }
3082
3083
173
    std::vector<CoordinateSystemAxisNNPtr> axisList;
3084
344
    for (int i = 0; i < axisCount; i++) {
3085
171
        axisList.emplace_back(
3086
171
            buildAxis(parentNode->GP()->lookForChild(WKTConstants::AXIS, i),
3087
171
                      unit, unitType, isGeocentric, i + 1));
3088
171
    }
3089
3090
173
    const PropertyMap &csMap = emptyPropertyMap;
3091
173
    if (ci_equal(csType, EllipsoidalCS::WKT2_TYPE)) {
3092
0
        if (axisCount == 2) {
3093
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
3094
0
        } else if (axisCount == 3) {
3095
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
3096
0
                                         axisList[2]);
3097
0
        }
3098
173
    } else if (ci_equal(csType, CartesianCS::WKT2_TYPE)) {
3099
4
        if (axisCount == 2) {
3100
0
            return CartesianCS::create(csMap, axisList[0], axisList[1]);
3101
4
        } else if (axisCount == 3) {
3102
0
            return CartesianCS::create(csMap, axisList[0], axisList[1],
3103
0
                                       axisList[2]);
3104
0
        }
3105
169
    } else if (ci_equal(csType, AffineCS::WKT2_TYPE)) {
3106
0
        if (axisCount == 2) {
3107
0
            return AffineCS::create(csMap, axisList[0], axisList[1]);
3108
0
        } else if (axisCount == 3) {
3109
0
            return AffineCS::create(csMap, axisList[0], axisList[1],
3110
0
                                    axisList[2]);
3111
0
        }
3112
169
    } else if (ci_equal(csType, VerticalCS::WKT2_TYPE)) {
3113
50
        if (axisCount == 1) {
3114
50
            return VerticalCS::create(csMap, axisList[0]);
3115
50
        }
3116
119
    } else if (ci_equal(csType, SphericalCS::WKT2_TYPE)) {
3117
0
        if (axisCount == 2) {
3118
            // Extension to ISO19111 to support (planet)-ocentric CS with
3119
            // geocentric latitude
3120
0
            return SphericalCS::create(csMap, axisList[0], axisList[1]);
3121
0
        } else if (axisCount == 3) {
3122
0
            return SphericalCS::create(csMap, axisList[0], axisList[1],
3123
0
                                       axisList[2]);
3124
0
        }
3125
119
    } else if (ci_equal(csType, OrdinalCS::WKT2_TYPE)) { // WKT2-2019
3126
0
        return OrdinalCS::create(csMap, axisList);
3127
119
    } else if (ci_equal(csType, ParametricCS::WKT2_TYPE)) {
3128
0
        if (axisCount == 1) {
3129
0
            return ParametricCS::create(csMap, axisList[0]);
3130
0
        }
3131
119
    } else if (ci_equal(csType, TemporalCS::WKT2_2015_TYPE)) {
3132
0
        if (axisCount == 1) {
3133
0
            if (isNull(
3134
0
                    parentNode->GP()->lookForChild(WKTConstants::TIMEUNIT)) &&
3135
0
                isNull(parentNode->GP()->lookForChild(WKTConstants::UNIT))) {
3136
0
                return DateTimeTemporalCS::create(csMap, axisList[0]);
3137
0
            } else {
3138
                // Default to TemporalMeasureCS
3139
                // TemporalCount could also be possible
3140
0
                return TemporalMeasureCS::create(csMap, axisList[0]);
3141
0
            }
3142
0
        }
3143
119
    } else if (ci_equal(csType, DateTimeTemporalCS::WKT2_2019_TYPE)) {
3144
0
        if (axisCount == 1) {
3145
0
            return DateTimeTemporalCS::create(csMap, axisList[0]);
3146
0
        }
3147
119
    } else if (ci_equal(csType, TemporalCountCS::WKT2_2019_TYPE)) {
3148
0
        if (axisCount == 1) {
3149
0
            return TemporalCountCS::create(csMap, axisList[0]);
3150
0
        }
3151
119
    } else if (ci_equal(csType, TemporalMeasureCS::WKT2_2019_TYPE)) {
3152
0
        if (axisCount == 1) {
3153
0
            return TemporalMeasureCS::create(csMap, axisList[0]);
3154
0
        }
3155
119
    } else {
3156
119
        throw ParsingException(concat("unhandled CS type: ", csType));
3157
119
    }
3158
4
    throw buildParsingExceptionInvalidAxisCount(csType);
3159
173
}
3160
3161
// ---------------------------------------------------------------------------
3162
3163
std::string
3164
2.30k
WKTParser::Private::getExtensionProj4(const WKTNode::Private *nodeP) {
3165
2.30k
    auto &extensionNode = nodeP->lookForChild(WKTConstants::EXTENSION);
3166
2.30k
    const auto &extensionChildren = extensionNode->GP()->children();
3167
2.30k
    if (extensionChildren.size() == 2) {
3168
147
        if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
3169
140
            return stripQuotes(extensionChildren[1]);
3170
140
        }
3171
147
    }
3172
2.16k
    return std::string();
3173
2.30k
}
3174
3175
// ---------------------------------------------------------------------------
3176
3177
void WKTParser::Private::addExtensionProj4ToProp(const WKTNode::Private *nodeP,
3178
1.55k
                                                 PropertyMap &props) {
3179
1.55k
    const auto extensionProj4(getExtensionProj4(nodeP));
3180
1.55k
    if (!extensionProj4.empty()) {
3181
47
        props.set("EXTENSION_PROJ4", extensionProj4);
3182
47
    }
3183
1.55k
}
3184
3185
// ---------------------------------------------------------------------------
3186
3187
GeodeticCRSNNPtr
3188
WKTParser::Private::buildGeodeticCRS(const WKTNodeNNPtr &node,
3189
1.15k
                                     bool forceGeocentricIfNoCs) {
3190
1.15k
    const auto *nodeP = node->GP();
3191
1.15k
    auto &datumNode = nodeP->lookForChild(
3192
1.15k
        WKTConstants::DATUM, WKTConstants::GEODETICDATUM, WKTConstants::TRF);
3193
1.15k
    auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
3194
1.15k
    if (isNull(datumNode) && isNull(ensembleNode)) {
3195
16
        throw ParsingException("Missing DATUM or ENSEMBLE node");
3196
16
    }
3197
3198
    // Do that now so that esriStyle_ can be set before buildPrimeMeridian()
3199
1.14k
    auto props = buildProperties(node);
3200
3201
1.14k
    auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
3202
3203
1.14k
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3204
1.14k
    const auto &nodeName = nodeP->value();
3205
1.14k
    if (isNull(csNode) && !ci_equal(nodeName, WKTConstants::GEOGCS) &&
3206
91
        !ci_equal(nodeName, WKTConstants::GEOCCS) &&
3207
2
        !ci_equal(nodeName, WKTConstants::BASEGEODCRS) &&
3208
2
        !ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
3209
2
        ThrowMissing(WKTConstants::CS_);
3210
2
    }
3211
3212
1.14k
    auto &primeMeridianNode =
3213
1.14k
        nodeP->lookForChild(WKTConstants::PRIMEM, WKTConstants::PRIMEMERIDIAN);
3214
1.14k
    if (isNull(primeMeridianNode)) {
3215
        // PRIMEM is required in WKT1
3216
843
        if (ci_equal(nodeName, WKTConstants::GEOGCS) ||
3217
843
            ci_equal(nodeName, WKTConstants::GEOCCS)) {
3218
843
            emitRecoverableWarning(nodeName + " should have a PRIMEM node");
3219
843
        }
3220
843
    }
3221
3222
1.14k
    auto angularUnit =
3223
1.14k
        buildUnitInSubNode(node, ci_equal(nodeName, WKTConstants::GEOGCS)
3224
1.14k
                                     ? UnitOfMeasure::Type::ANGULAR
3225
1.14k
                                     : UnitOfMeasure::Type::UNKNOWN);
3226
1.14k
    if (angularUnit.type() != UnitOfMeasure::Type::ANGULAR) {
3227
686
        angularUnit = UnitOfMeasure::NONE;
3228
686
    }
3229
3230
1.14k
    auto primeMeridian =
3231
1.14k
        !isNull(primeMeridianNode)
3232
1.14k
            ? buildPrimeMeridian(primeMeridianNode, angularUnit)
3233
1.14k
            : PrimeMeridian::GREENWICH;
3234
1.14k
    if (angularUnit == UnitOfMeasure::NONE) {
3235
685
        angularUnit = primeMeridian->longitude().unit();
3236
685
    }
3237
3238
1.14k
    addExtensionProj4ToProp(nodeP, props);
3239
3240
    // No explicit AXIS node ? (WKT1)
3241
1.14k
    if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
3242
1.08k
        props.set("IMPLICIT_CS", true);
3243
1.08k
    }
3244
3245
1.14k
    const std::string crsName = stripQuotes(nodeP->children()[0]);
3246
1.14k
    if (esriStyle_ && dbContext_) {
3247
278
        std::string outTableName;
3248
278
        std::string authNameFromAlias;
3249
278
        std::string codeFromAlias;
3250
278
        auto authFactory =
3251
278
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
3252
278
        auto officialName = authFactory->getOfficialNameFromAlias(
3253
278
            crsName, "geodetic_crs", "ESRI", false, outTableName,
3254
278
            authNameFromAlias, codeFromAlias);
3255
278
        if (!officialName.empty()) {
3256
229
            props.set(IdentifiedObject::NAME_KEY, officialName);
3257
229
        }
3258
278
    }
3259
3260
1.14k
    auto datum =
3261
1.14k
        !isNull(datumNode)
3262
1.14k
            ? buildGeodeticReferenceFrame(datumNode, primeMeridian, dynamicNode)
3263
954
                  .as_nullable()
3264
1.14k
            : nullptr;
3265
1.14k
    auto datumEnsemble =
3266
1.14k
        !isNull(ensembleNode)
3267
1.14k
            ? buildDatumEnsemble(ensembleNode, primeMeridian, true)
3268
162
                  .as_nullable()
3269
1.14k
            : nullptr;
3270
1.14k
    auto cs = buildCS(csNode, node, angularUnit);
3271
3272
    // If there's no CS[] node, typically for a BASEGEODCRS of a projected CRS,
3273
    // in a few rare cases, this might be a Geocentric CRS, and thus a
3274
    // Cartesian CS, and not the ellipsoidalCS we assumed above. The only way
3275
    // to figure that is to resolve the CRS from its code...
3276
1.14k
    if (isNull(csNode) && dbContext_ &&
3277
779
        ci_equal(nodeName, WKTConstants::BASEGEODCRS)) {
3278
0
        const auto &nodeChildren = nodeP->children();
3279
0
        for (const auto &subNode : nodeChildren) {
3280
0
            const auto &subNodeName(subNode->GP()->value());
3281
0
            if (ci_equal(subNodeName, WKTConstants::ID) ||
3282
0
                ci_equal(subNodeName, WKTConstants::AUTHORITY)) {
3283
0
                auto id = buildId(node, subNode, true, false);
3284
0
                if (id) {
3285
0
                    try {
3286
0
                        auto authFactory = AuthorityFactory::create(
3287
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
3288
0
                        auto dbCRS = authFactory->createGeodeticCRS(id->code());
3289
0
                        cs = dbCRS->coordinateSystem();
3290
0
                    } catch (const util::Exception &) {
3291
0
                    }
3292
0
                }
3293
0
            }
3294
0
        }
3295
0
    }
3296
1.14k
    if (forceGeocentricIfNoCs && isNull(csNode) &&
3297
0
        ci_equal(nodeName, WKTConstants::BASEGEODCRS)) {
3298
0
        cs = cs::CartesianCS::createGeocentric(UnitOfMeasure::METRE);
3299
0
    }
3300
3301
1.14k
    auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3302
1.14k
    if (ellipsoidalCS) {
3303
848
        if (ci_equal(nodeName, WKTConstants::GEOCCS)) {
3304
0
            throw ParsingException("ellipsoidal CS not expected in GEOCCS");
3305
0
        }
3306
848
        try {
3307
848
            auto crs = GeographicCRS::create(props, datum, datumEnsemble,
3308
848
                                             NN_NO_CHECK(ellipsoidalCS));
3309
            // In case of missing CS node, or to check it, query the coordinate
3310
            // system from the DB if possible (typically for the baseCRS of a
3311
            // ProjectedCRS)
3312
848
            if (!crs->identifiers().empty() && dbContext_) {
3313
7
                GeographicCRSPtr dbCRS;
3314
7
                try {
3315
7
                    const auto &id = crs->identifiers()[0];
3316
7
                    auto authFactory = AuthorityFactory::create(
3317
7
                        NN_NO_CHECK(dbContext_), *id->codeSpace());
3318
7
                    dbCRS = authFactory->createGeographicCRS(id->code())
3319
7
                                .as_nullable();
3320
7
                } catch (const util::Exception &) {
3321
7
                }
3322
7
                if (dbCRS &&
3323
0
                    (!isNull(csNode) ||
3324
0
                     node->countChildrenOfName(WKTConstants::AXIS) != 0) &&
3325
0
                    !ellipsoidalCS->_isEquivalentTo(
3326
0
                        dbCRS->coordinateSystem().get(),
3327
0
                        util::IComparable::Criterion::EQUIVALENT)) {
3328
0
                    if (unsetIdentifiersIfIncompatibleDef_) {
3329
0
                        emitRecoverableWarning(
3330
0
                            "Coordinate system of GeographicCRS in the WKT "
3331
0
                            "definition is different from the one of the "
3332
0
                            "authority. Unsetting the identifier to avoid "
3333
0
                            "confusion");
3334
0
                        props.unset(Identifier::CODESPACE_KEY);
3335
0
                        props.unset(Identifier::AUTHORITY_KEY);
3336
0
                        props.unset(IdentifiedObject::IDENTIFIERS_KEY);
3337
0
                    }
3338
0
                    crs = GeographicCRS::create(props, datum, datumEnsemble,
3339
0
                                                NN_NO_CHECK(ellipsoidalCS));
3340
7
                } else if (dbCRS) {
3341
0
                    auto csFromDB = dbCRS->coordinateSystem();
3342
0
                    auto csFromDBAltered = csFromDB;
3343
0
                    if (!isNull(nodeP->lookForChild(WKTConstants::UNIT))) {
3344
0
                        csFromDBAltered =
3345
0
                            csFromDB->alterAngularUnit(angularUnit);
3346
0
                        if (unsetIdentifiersIfIncompatibleDef_ &&
3347
0
                            !csFromDBAltered->_isEquivalentTo(
3348
0
                                csFromDB.get(),
3349
0
                                util::IComparable::Criterion::EQUIVALENT)) {
3350
0
                            emitRecoverableWarning(
3351
0
                                "Coordinate system of GeographicCRS in the WKT "
3352
0
                                "definition is different from the one of the "
3353
0
                                "authority. Unsetting the identifier to avoid "
3354
0
                                "confusion");
3355
0
                            props.unset(Identifier::CODESPACE_KEY);
3356
0
                            props.unset(Identifier::AUTHORITY_KEY);
3357
0
                            props.unset(IdentifiedObject::IDENTIFIERS_KEY);
3358
0
                        }
3359
0
                    }
3360
0
                    crs = GeographicCRS::create(props, datum, datumEnsemble,
3361
0
                                                csFromDBAltered);
3362
0
                }
3363
7
            }
3364
848
            return crs;
3365
848
        } catch (const util::Exception &e) {
3366
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3367
0
                                   e.what());
3368
0
        }
3369
848
    } else if (ci_equal(nodeName, WKTConstants::GEOGCRS) ||
3370
51
               ci_equal(nodeName, WKTConstants::GEOGRAPHICCRS) ||
3371
51
               ci_equal(nodeName, WKTConstants::BASEGEOGCRS)) {
3372
        // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3373
0
        throw ParsingException(concat("ellipsoidal CS expected, but found ",
3374
0
                                      cs->getWKT2Type(true)));
3375
0
    }
3376
3377
292
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3378
292
    if (cartesianCS) {
3379
51
        if (cartesianCS->axisList().size() != 3) {
3380
0
            throw ParsingException(
3381
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
3382
0
        }
3383
51
        try {
3384
51
            return GeodeticCRS::create(props, datum, datumEnsemble,
3385
51
                                       NN_NO_CHECK(cartesianCS));
3386
51
        } catch (const util::Exception &e) {
3387
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3388
0
                                   e.what());
3389
0
        }
3390
51
    }
3391
3392
241
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3393
241
    if (sphericalCS) {
3394
0
        try {
3395
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
3396
0
                                       NN_NO_CHECK(sphericalCS));
3397
0
        } catch (const util::Exception &e) {
3398
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
3399
0
                                   e.what());
3400
0
        }
3401
0
    }
3402
3403
241
    throw ParsingException(
3404
241
        concat("unhandled CS type: ", cs->getWKT2Type(true)));
3405
241
}
3406
3407
// ---------------------------------------------------------------------------
3408
3409
0
CRSNNPtr WKTParser::Private::buildDerivedGeodeticCRS(const WKTNodeNNPtr &node) {
3410
0
    const auto *nodeP = node->GP();
3411
0
    auto &baseGeodCRSNode = nodeP->lookForChild(WKTConstants::BASEGEODCRS,
3412
0
                                                WKTConstants::BASEGEOGCRS);
3413
    // given the constraints enforced on calling code path
3414
0
    assert(!isNull(baseGeodCRSNode));
3415
3416
0
    auto &derivingConversionNode =
3417
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
3418
0
    if (isNull(derivingConversionNode)) {
3419
0
        ThrowMissing(WKTConstants::DERIVINGCONVERSION);
3420
0
    }
3421
0
    auto derivingConversion = buildConversion(
3422
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
3423
3424
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
3425
0
    if (isNull(csNode)) {
3426
0
        ThrowMissing(WKTConstants::CS_);
3427
0
    }
3428
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
3429
3430
0
    bool forceGeocentricIfNoCs = false;
3431
0
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
3432
0
    if (cartesianCS) {
3433
0
        if (cartesianCS->axisList().size() != 3) {
3434
0
            throw ParsingException(
3435
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
3436
0
        }
3437
0
        const int methodCode = derivingConversion->method()->getEPSGCode();
3438
0
        if ((methodCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
3439
0
             methodCode ==
3440
0
                 EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC ||
3441
0
             methodCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
3442
0
             methodCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
3443
0
             methodCode ==
3444
0
                 EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
3445
0
             methodCode ==
3446
0
                 EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC) &&
3447
0
            nodeP->lookForChild(WKTConstants::BASEGEODCRS) != nullptr) {
3448
0
            forceGeocentricIfNoCs = true;
3449
0
        }
3450
0
    }
3451
0
    auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode, forceGeocentricIfNoCs);
3452
3453
0
    auto ellipsoidalCS = nn_dynamic_pointer_cast<EllipsoidalCS>(cs);
3454
0
    if (ellipsoidalCS) {
3455
3456
0
        if (ellipsoidalCS->axisList().size() == 3 &&
3457
0
            baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
3458
0
            baseGeodCRS =
3459
0
                NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
3460
0
                    baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
3461
0
        }
3462
3463
0
        return DerivedGeographicCRS::create(buildProperties(node), baseGeodCRS,
3464
0
                                            derivingConversion,
3465
0
                                            NN_NO_CHECK(ellipsoidalCS));
3466
0
    } else if (ci_equal(nodeP->value(), WKTConstants::GEOGCRS)) {
3467
        // This is a WKT2-2019 GeographicCRS. An ellipsoidal CS is expected
3468
0
        throw ParsingException(concat("ellipsoidal CS expected, but found ",
3469
0
                                      cs->getWKT2Type(true)));
3470
0
    }
3471
3472
0
    if (cartesianCS) {
3473
0
        return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3474
0
                                          derivingConversion,
3475
0
                                          NN_NO_CHECK(cartesianCS));
3476
0
    }
3477
3478
0
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
3479
0
    if (sphericalCS) {
3480
0
        return DerivedGeodeticCRS::create(buildProperties(node), baseGeodCRS,
3481
0
                                          derivingConversion,
3482
0
                                          NN_NO_CHECK(sphericalCS));
3483
0
    }
3484
3485
0
    throw ParsingException(
3486
0
        concat("unhandled CS type: ", cs->getWKT2Type(true)));
3487
0
}
3488
3489
// ---------------------------------------------------------------------------
3490
3491
UnitOfMeasure WKTParser::Private::guessUnitForParameter(
3492
    const std::string &paramName, const UnitOfMeasure &defaultLinearUnit,
3493
2.91k
    const UnitOfMeasure &defaultAngularUnit) {
3494
2.91k
    UnitOfMeasure unit;
3495
    // scale must be first because of 'Scale factor on pseudo standard parallel'
3496
2.91k
    if (ci_find(paramName, "scale") != std::string::npos ||
3497
2.85k
        ci_find(paramName, "scaling factor") != std::string::npos) {
3498
60
        unit = UnitOfMeasure::SCALE_UNITY;
3499
2.85k
    } else if (ci_find(paramName, "latitude") != std::string::npos ||
3500
2.63k
               ci_find(paramName, "longitude") != std::string::npos ||
3501
2.31k
               ci_find(paramName, "meridian") != std::string::npos ||
3502
2.30k
               ci_find(paramName, "parallel") != std::string::npos ||
3503
2.29k
               ci_find(paramName, "azimuth") != std::string::npos ||
3504
2.27k
               ci_find(paramName, "angle") != std::string::npos ||
3505
2.25k
               ci_find(paramName, "heading") != std::string::npos ||
3506
2.22k
               ci_find(paramName, "rotation") != std::string::npos) {
3507
670
        unit = defaultAngularUnit;
3508
2.18k
    } else if (ci_find(paramName, "easting") != std::string::npos ||
3509
1.90k
               ci_find(paramName, "northing") != std::string::npos ||
3510
1.59k
               ci_find(paramName, "height") != std::string::npos) {
3511
615
        unit = defaultLinearUnit;
3512
615
    }
3513
2.91k
    return unit;
3514
2.91k
}
3515
3516
// ---------------------------------------------------------------------------
3517
3518
static bool
3519
516
isEPSGCodeForInterpolationParameter(const OperationParameterNNPtr &parameter) {
3520
516
    const auto &name = parameter->nameStr();
3521
516
    const auto epsgCode = parameter->getEPSGCode();
3522
516
    return name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3523
509
           epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3524
475
           name == EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS ||
3525
475
           epsgCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS;
3526
516
}
3527
3528
// ---------------------------------------------------------------------------
3529
3530
259
static bool isIntegerParameter(const OperationParameterNNPtr &parameter) {
3531
259
    return isEPSGCodeForInterpolationParameter(parameter);
3532
259
}
3533
3534
// ---------------------------------------------------------------------------
3535
3536
void WKTParser::Private::consumeParameters(
3537
    const WKTNodeNNPtr &node, bool isAbridged,
3538
    std::vector<OperationParameterNNPtr> &parameters,
3539
    std::vector<ParameterValueNNPtr> &values,
3540
    const UnitOfMeasure &defaultLinearUnit,
3541
327
    const UnitOfMeasure &defaultAngularUnit) {
3542
1.97k
    for (const auto &childNode : node->GP()->children()) {
3543
1.97k
        const auto &childNodeChildren = childNode->GP()->children();
3544
1.97k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3545
416
            if (childNodeChildren.size() < 2) {
3546
7
                ThrowNotEnoughChildren(childNode->GP()->value());
3547
7
            }
3548
409
            parameters.push_back(
3549
409
                OperationParameter::create(buildProperties(childNode)));
3550
409
            const auto &paramValue = childNodeChildren[1]->GP()->value();
3551
409
            if (!paramValue.empty() && paramValue[0] == '"') {
3552
141
                values.push_back(
3553
141
                    ParameterValue::create(stripQuotes(childNodeChildren[1])));
3554
268
            } else {
3555
268
                try {
3556
268
                    double val = asDouble(childNodeChildren[1]);
3557
268
                    auto unit = buildUnitInSubNode(childNode);
3558
268
                    if (unit == UnitOfMeasure::NONE) {
3559
259
                        const auto &paramName =
3560
259
                            childNodeChildren[0]->GP()->value();
3561
259
                        unit = guessUnitForParameter(
3562
259
                            paramName, defaultLinearUnit, defaultAngularUnit);
3563
259
                    }
3564
3565
268
                    if (isAbridged) {
3566
0
                        const auto &paramName = parameters.back()->nameStr();
3567
0
                        int paramEPSGCode = 0;
3568
0
                        const auto &paramIds = parameters.back()->identifiers();
3569
0
                        if (paramIds.size() == 1 &&
3570
0
                            ci_equal(*(paramIds[0]->codeSpace()),
3571
0
                                     Identifier::EPSG)) {
3572
0
                            paramEPSGCode = ::atoi(paramIds[0]->code().c_str());
3573
0
                        }
3574
0
                        const common::UnitOfMeasure *pUnit = nullptr;
3575
0
                        if (OperationParameterValue::convertFromAbridged(
3576
0
                                paramName, val, pUnit, paramEPSGCode)) {
3577
0
                            unit = *pUnit;
3578
0
                            parameters.back() = OperationParameter::create(
3579
0
                                buildProperties(childNode)
3580
0
                                    .set(Identifier::CODESPACE_KEY,
3581
0
                                         Identifier::EPSG)
3582
0
                                    .set(Identifier::CODE_KEY, paramEPSGCode));
3583
0
                        }
3584
0
                    }
3585
3586
268
                    if (isIntegerParameter(parameters.back())) {
3587
29
                        values.push_back(ParameterValue::create(
3588
29
                            std::stoi(childNodeChildren[1]->GP()->value())));
3589
239
                    } else {
3590
239
                        values.push_back(
3591
239
                            ParameterValue::create(Measure(val, unit)));
3592
239
                    }
3593
268
                } catch (const std::exception &) {
3594
14
                    throw ParsingException(concat(
3595
14
                        "unhandled parameter value type : ", paramValue));
3596
14
                }
3597
268
            }
3598
1.56k
        } else if (ci_equal(childNode->GP()->value(),
3599
1.56k
                            WKTConstants::PARAMETERFILE)) {
3600
39
            if (childNodeChildren.size() < 2) {
3601
3
                ThrowNotEnoughChildren(childNode->GP()->value());
3602
3
            }
3603
36
            parameters.push_back(
3604
36
                OperationParameter::create(buildProperties(childNode)));
3605
36
            values.push_back(ParameterValue::createFilename(
3606
36
                stripQuotes(childNodeChildren[1])));
3607
36
        }
3608
1.97k
    }
3609
327
}
3610
3611
// ---------------------------------------------------------------------------
3612
3613
static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
3614
    DatabaseContextPtr &dbContext,
3615
    std::vector<OperationParameterNNPtr> &parameters,
3616
    std::vector<ParameterValueNNPtr> &values);
3617
3618
ConversionNNPtr
3619
WKTParser::Private::buildConversion(const WKTNodeNNPtr &node,
3620
                                    const UnitOfMeasure &defaultLinearUnit,
3621
345
                                    const UnitOfMeasure &defaultAngularUnit) {
3622
345
    auto &methodNode = node->GP()->lookForChild(WKTConstants::METHOD,
3623
345
                                                WKTConstants::PROJECTION);
3624
345
    if (isNull(methodNode)) {
3625
15
        ThrowMissing(WKTConstants::METHOD);
3626
15
    }
3627
330
    if (methodNode->GP()->childrenSize() == 0) {
3628
3
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3629
3
    }
3630
3631
327
    std::vector<OperationParameterNNPtr> parameters;
3632
327
    std::vector<ParameterValueNNPtr> values;
3633
327
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3634
327
                      defaultAngularUnit);
3635
3636
327
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
3637
327
        dbContext_, parameters, values);
3638
3639
327
    auto &convProps = buildProperties(node);
3640
327
    auto &methodProps = buildProperties(methodNode);
3641
327
    std::string convName;
3642
327
    std::string methodName;
3643
327
    if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
3644
302
        methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
3645
302
        starts_with(convName, "Inverse of ") &&
3646
76
        starts_with(methodName, "Inverse of ")) {
3647
3648
54
        auto &invConvProps = buildProperties(node, true);
3649
54
        auto &invMethodProps = buildProperties(methodNode, true);
3650
54
        auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
3651
54
            Conversion::create(invConvProps, invMethodProps, parameters, values)
3652
54
                ->inverse()));
3653
54
        if (interpolationCRS)
3654
0
            conv->setInterpolationCRS(interpolationCRS);
3655
54
        return conv;
3656
54
    }
3657
273
    auto conv = Conversion::create(convProps, methodProps, parameters, values);
3658
273
    if (interpolationCRS)
3659
0
        conv->setInterpolationCRS(interpolationCRS);
3660
273
    return conv;
3661
327
}
3662
3663
// ---------------------------------------------------------------------------
3664
3665
static CRSPtr dealWithEPSGCodeForInterpolationCRSParameter(
3666
    DatabaseContextPtr &dbContext,
3667
    std::vector<OperationParameterNNPtr> &parameters,
3668
303
    std::vector<ParameterValueNNPtr> &values) {
3669
    // Transform EPSG hacky PARAMETER["EPSG code for Interpolation CRS",
3670
    // crs_epsg_code] into proper interpolation CRS
3671
303
    if (dbContext != nullptr) {
3672
527
        for (size_t i = 0; i < parameters.size(); ++i) {
3673
257
            if (isEPSGCodeForInterpolationParameter(parameters[i])) {
3674
12
                const int code = values[i]->integerValue();
3675
12
                try {
3676
12
                    auto authFactory = AuthorityFactory::create(
3677
12
                        NN_NO_CHECK(dbContext), Identifier::EPSG);
3678
12
                    auto interpolationCRS =
3679
12
                        authFactory
3680
12
                            ->createGeographicCRS(internal::toString(code))
3681
12
                            .as_nullable();
3682
12
                    parameters.erase(parameters.begin() + i);
3683
12
                    values.erase(values.begin() + i);
3684
12
                    return interpolationCRS;
3685
12
                } catch (const util::Exception &) {
3686
12
                }
3687
12
            }
3688
257
        }
3689
270
    }
3690
303
    return nullptr;
3691
303
}
3692
3693
// ---------------------------------------------------------------------------
3694
3695
TransformationNNPtr
3696
16
WKTParser::Private::buildCoordinateOperation(const WKTNodeNNPtr &node) {
3697
16
    const auto *nodeP = node->GP();
3698
16
    auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3699
16
    if (isNull(methodNode)) {
3700
8
        ThrowMissing(WKTConstants::METHOD);
3701
8
    }
3702
8
    if (methodNode->GP()->childrenSize() == 0) {
3703
2
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3704
2
    }
3705
3706
6
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3707
6
    if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3708
6
        ThrowMissing(WKTConstants::SOURCECRS);
3709
6
    }
3710
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3711
0
    if (!sourceCRS) {
3712
0
        throw ParsingException("Invalid content in SOURCECRS node");
3713
0
    }
3714
3715
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3716
0
    if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3717
0
        ThrowMissing(WKTConstants::TARGETCRS);
3718
0
    }
3719
0
    auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3720
0
    if (!targetCRS) {
3721
0
        throw ParsingException("Invalid content in TARGETCRS node");
3722
0
    }
3723
3724
0
    auto &interpolationCRSNode =
3725
0
        nodeP->lookForChild(WKTConstants::INTERPOLATIONCRS);
3726
0
    CRSPtr interpolationCRS;
3727
0
    if (/*!isNull(interpolationCRSNode) && */ interpolationCRSNode->GP()
3728
0
            ->childrenSize() == 1) {
3729
0
        interpolationCRS = buildCRS(interpolationCRSNode->GP()->children()[0]);
3730
0
    }
3731
3732
0
    std::vector<OperationParameterNNPtr> parameters;
3733
0
    std::vector<ParameterValueNNPtr> values;
3734
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
3735
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
3736
0
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3737
0
                      defaultAngularUnit);
3738
3739
0
    if (interpolationCRS == nullptr)
3740
0
        interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
3741
0
            dbContext_, parameters, values);
3742
3743
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3744
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3745
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3746
0
        accuracies.push_back(PositionalAccuracy::create(
3747
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3748
0
    }
3749
3750
0
    return Transformation::create(buildProperties(node), NN_NO_CHECK(sourceCRS),
3751
0
                                  NN_NO_CHECK(targetCRS), interpolationCRS,
3752
0
                                  buildProperties(methodNode), parameters,
3753
0
                                  values, accuracies);
3754
0
}
3755
3756
// ---------------------------------------------------------------------------
3757
3758
PointMotionOperationNNPtr
3759
1
WKTParser::Private::buildPointMotionOperation(const WKTNodeNNPtr &node) {
3760
1
    const auto *nodeP = node->GP();
3761
1
    auto &methodNode = nodeP->lookForChild(WKTConstants::METHOD);
3762
1
    if (isNull(methodNode)) {
3763
1
        ThrowMissing(WKTConstants::METHOD);
3764
1
    }
3765
0
    if (methodNode->GP()->childrenSize() == 0) {
3766
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
3767
0
    }
3768
3769
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3770
0
    if (sourceCRSNode->GP()->childrenSize() != 1) {
3771
0
        ThrowMissing(WKTConstants::SOURCECRS);
3772
0
    }
3773
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3774
0
    if (!sourceCRS) {
3775
0
        throw ParsingException("Invalid content in SOURCECRS node");
3776
0
    }
3777
3778
0
    std::vector<OperationParameterNNPtr> parameters;
3779
0
    std::vector<ParameterValueNNPtr> values;
3780
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
3781
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
3782
0
    consumeParameters(node, false, parameters, values, defaultLinearUnit,
3783
0
                      defaultAngularUnit);
3784
3785
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3786
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3787
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3788
0
        accuracies.push_back(PositionalAccuracy::create(
3789
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3790
0
    }
3791
3792
0
    return PointMotionOperation::create(
3793
0
        buildProperties(node), NN_NO_CHECK(sourceCRS),
3794
0
        buildProperties(methodNode), parameters, values, accuracies);
3795
0
}
3796
3797
// ---------------------------------------------------------------------------
3798
3799
ConcatenatedOperationNNPtr
3800
6
WKTParser::Private::buildConcatenatedOperation(const WKTNodeNNPtr &node) {
3801
3802
6
    const auto *nodeP = node->GP();
3803
6
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
3804
6
    if (/*isNull(sourceCRSNode) ||*/ sourceCRSNode->GP()->childrenSize() != 1) {
3805
6
        ThrowMissing(WKTConstants::SOURCECRS);
3806
6
    }
3807
0
    auto sourceCRS = buildCRS(sourceCRSNode->GP()->children()[0]);
3808
0
    if (!sourceCRS) {
3809
0
        throw ParsingException("Invalid content in SOURCECRS node");
3810
0
    }
3811
3812
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
3813
0
    if (/*isNull(targetCRSNode) ||*/ targetCRSNode->GP()->childrenSize() != 1) {
3814
0
        ThrowMissing(WKTConstants::TARGETCRS);
3815
0
    }
3816
0
    auto targetCRS = buildCRS(targetCRSNode->GP()->children()[0]);
3817
0
    if (!targetCRS) {
3818
0
        throw ParsingException("Invalid content in TARGETCRS node");
3819
0
    }
3820
3821
0
    std::vector<CoordinateOperationNNPtr> operations;
3822
0
    for (const auto &childNode : nodeP->children()) {
3823
0
        if (ci_equal(childNode->GP()->value(), WKTConstants::STEP)) {
3824
0
            if (childNode->GP()->childrenSize() != 1) {
3825
0
                throw ParsingException("Invalid content in STEP node");
3826
0
            }
3827
0
            auto op = nn_dynamic_pointer_cast<CoordinateOperation>(
3828
0
                build(childNode->GP()->children()[0]));
3829
0
            if (!op) {
3830
0
                throw ParsingException("Invalid content in STEP node");
3831
0
            }
3832
0
            operations.emplace_back(NN_NO_CHECK(op));
3833
0
        }
3834
0
    }
3835
3836
0
    ConcatenatedOperation::fixSteps(
3837
0
        NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS), operations, dbContext_,
3838
0
        /* fixDirectionAllowed = */ true);
3839
3840
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
3841
0
    auto &accuracyNode = nodeP->lookForChild(WKTConstants::OPERATIONACCURACY);
3842
0
    if (/*!isNull(accuracyNode) && */ accuracyNode->GP()->childrenSize() == 1) {
3843
0
        accuracies.push_back(PositionalAccuracy::create(
3844
0
            stripQuotes(accuracyNode->GP()->children()[0])));
3845
0
    }
3846
3847
0
    try {
3848
0
        return ConcatenatedOperation::create(buildProperties(node), operations,
3849
0
                                             accuracies);
3850
0
    } catch (const InvalidOperation &e) {
3851
0
        throw ParsingException(
3852
0
            std::string("Cannot build concatenated operation: ") + e.what());
3853
0
    }
3854
0
}
3855
3856
// ---------------------------------------------------------------------------
3857
3858
bool WKTParser::Private::hasWebMercPROJ4String(
3859
613
    const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode) {
3860
613
    if (projectionNode->GP()->childrenSize() == 0) {
3861
2
        ThrowNotEnoughChildren(WKTConstants::PROJECTION);
3862
2
    }
3863
611
    const std::string wkt1ProjectionName =
3864
611
        stripQuotes(projectionNode->GP()->children()[0]);
3865
3866
611
    auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
3867
3868
611
    if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
3869
611
                                               "Mercator_1SP") &&
3870
0
        projCRSNode->countChildrenOfName("center_latitude") == 0) {
3871
3872
        // Hack to detect the hacky way of encodign webmerc in GDAL WKT1
3873
        // with a EXTENSION["PROJ4", "+proj=merc +a=6378137 +b=6378137
3874
        // +lat_ts=0.0 +lon_0=0.0 +x_0=0.0 +y_0=0 +k=1.0 +units=m
3875
        // +nadgrids=@null +wktext +no_defs"] node
3876
0
        if (extensionNode && extensionNode->GP()->childrenSize() == 2 &&
3877
0
            ci_equal(stripQuotes(extensionNode->GP()->children()[0]),
3878
0
                     "PROJ4")) {
3879
0
            std::string projString =
3880
0
                stripQuotes(extensionNode->GP()->children()[1]);
3881
0
            if (projString.find("+proj=merc") != std::string::npos &&
3882
0
                projString.find("+a=6378137") != std::string::npos &&
3883
0
                projString.find("+b=6378137") != std::string::npos &&
3884
0
                projString.find("+lon_0=0") != std::string::npos &&
3885
0
                projString.find("+x_0=0") != std::string::npos &&
3886
0
                projString.find("+y_0=0") != std::string::npos &&
3887
0
                projString.find("+nadgrids=@null") != std::string::npos &&
3888
0
                (projString.find("+lat_ts=") == std::string::npos ||
3889
0
                 projString.find("+lat_ts=0") != std::string::npos) &&
3890
0
                (projString.find("+k=") == std::string::npos ||
3891
0
                 projString.find("+k=1") != std::string::npos) &&
3892
0
                (projString.find("+units=") == std::string::npos ||
3893
0
                 projString.find("+units=m") != std::string::npos)) {
3894
0
                return true;
3895
0
            }
3896
0
        }
3897
0
    }
3898
611
    return false;
3899
611
}
3900
3901
// ---------------------------------------------------------------------------
3902
3903
static const MethodMapping *
3904
selectSphericalOrEllipsoidal(const MethodMapping *mapping,
3905
4.23k
                             const GeodeticCRSNNPtr &baseGeodCRS) {
3906
4.23k
    if (mapping->epsg_code ==
3907
4.23k
            EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL ||
3908
4.23k
        mapping->epsg_code == EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA) {
3909
95
        mapping = getMapping(
3910
95
            baseGeodCRS->ellipsoid()->isSphere()
3911
95
                ? EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL
3912
95
                : EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA);
3913
4.14k
    } else if (mapping->epsg_code ==
3914
4.14k
                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL ||
3915
4.14k
               mapping->epsg_code ==
3916
4.14k
                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
3917
168
        mapping = getMapping(
3918
168
            baseGeodCRS->ellipsoid()->isSphere()
3919
168
                ? EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL
3920
168
                : EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA);
3921
3.97k
    } else if (mapping->epsg_code ==
3922
3.97k
                   EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL ||
3923
3.97k
               mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL) {
3924
39
        mapping =
3925
39
            getMapping(baseGeodCRS->ellipsoid()->isSphere()
3926
39
                           ? EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL
3927
39
                           : EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL);
3928
39
    }
3929
4.23k
    return mapping;
3930
4.23k
}
3931
3932
// ---------------------------------------------------------------------------
3933
3934
const ESRIMethodMapping *WKTParser::Private::getESRIMapping(
3935
    const WKTNodeNNPtr &projCRSNode, const WKTNodeNNPtr &projectionNode,
3936
822
    std::map<std::string, std::string, ci_less_struct> &mapParamNameToValue) {
3937
822
    const std::string esriProjectionName =
3938
822
        stripQuotes(projectionNode->GP()->children()[0]);
3939
3940
    // Lambert_Conformal_Conic or Krovak may map to different WKT2 methods
3941
    // depending
3942
    // on the parameters / their values
3943
822
    const auto esriMappings = getMappingsFromESRI(esriProjectionName);
3944
822
    if (esriMappings.empty()) {
3945
374
        return nullptr;
3946
374
    }
3947
3948
    // Build a map of present parameters
3949
3.66k
    for (const auto &childNode : projCRSNode->GP()->children()) {
3950
3.66k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
3951
1.71k
            const auto &childNodeChildren = childNode->GP()->children();
3952
1.71k
            if (childNodeChildren.size() < 2) {
3953
11
                ThrowNotEnoughChildren(WKTConstants::PARAMETER);
3954
11
            }
3955
1.70k
            const std::string parameterName(stripQuotes(childNodeChildren[0]));
3956
1.70k
            const auto &paramValue = childNodeChildren[1]->GP()->value();
3957
1.70k
            mapParamNameToValue[parameterName] = paramValue;
3958
1.70k
        }
3959
3.66k
    }
3960
3961
    // Compare parameters present with the ones expected in the mapping
3962
437
    const ESRIMethodMapping *esriMapping = nullptr;
3963
437
    int bestMatchCount = -1;
3964
644
    for (const auto &mapping : esriMappings) {
3965
644
        int matchCount = 0;
3966
644
        int unmatchCount = 0;
3967
4.53k
        for (const auto *param = mapping->params; param->esri_name; ++param) {
3968
3.90k
            auto iter = mapParamNameToValue.find(param->esri_name);
3969
3.90k
            if (iter != mapParamNameToValue.end()) {
3970
1.17k
                if (param->wkt2_name == nullptr) {
3971
29
                    bool ok = true;
3972
29
                    try {
3973
29
                        if (io::asDouble(param->fixed_value) ==
3974
29
                            io::asDouble(iter->second)) {
3975
11
                            matchCount++;
3976
18
                        } else {
3977
18
                            ok = false;
3978
18
                        }
3979
29
                    } catch (const std::exception &) {
3980
2
                        ok = false;
3981
2
                    }
3982
29
                    if (!ok) {
3983
18
                        matchCount = -1;
3984
18
                        break;
3985
18
                    }
3986
1.14k
                } else {
3987
1.14k
                    matchCount++;
3988
1.14k
                }
3989
2.72k
            } else if (param->is_fixed_value) {
3990
90
                mapParamNameToValue[param->esri_name] = param->fixed_value;
3991
2.63k
            } else {
3992
2.63k
                unmatchCount++;
3993
2.63k
            }
3994
3.90k
        }
3995
644
        if (matchCount > bestMatchCount &&
3996
595
            !(maybeEsriStyle_ && unmatchCount >= matchCount)) {
3997
290
            esriMapping = mapping;
3998
290
            bestMatchCount = matchCount;
3999
290
        }
4000
644
    }
4001
4002
437
    return esriMapping;
4003
437
}
4004
4005
// ---------------------------------------------------------------------------
4006
4007
ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
4008
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4009
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4010
    const UnitOfMeasure &defaultAngularUnit,
4011
    const ESRIMethodMapping *esriMapping,
4012
287
    std::map<std::string, std::string, ci_less_struct> &mapParamNameToValue) {
4013
287
    std::map<std::string, const char *> mapWKT2NameToESRIName;
4014
1.54k
    for (const auto *param = esriMapping->params; param->esri_name; ++param) {
4015
1.25k
        if (param->wkt2_name) {
4016
1.21k
            mapWKT2NameToESRIName[param->wkt2_name] = param->esri_name;
4017
1.21k
        }
4018
1.25k
    }
4019
4020
287
    const std::string esriProjectionName =
4021
287
        stripQuotes(projectionNode->GP()->children()[0]);
4022
287
    const char *projectionMethodWkt2Name = esriMapping->wkt2_name;
4023
287
    if (ci_equal(esriProjectionName, "Krovak")) {
4024
11
        const std::string projCRSName =
4025
11
            stripQuotes(projCRSNode->GP()->children()[0]);
4026
11
        if (projCRSName.find("_East_North") != std::string::npos) {
4027
0
            projectionMethodWkt2Name = EPSG_NAME_METHOD_KROVAK_NORTH_ORIENTED;
4028
0
        }
4029
11
    }
4030
4031
287
    const auto *wkt2_mapping = getMapping(projectionMethodWkt2Name);
4032
287
    if (ci_equal(esriProjectionName, "Stereographic")) {
4033
20
        try {
4034
20
            const auto iterLatitudeOfOrigin =
4035
20
                mapParamNameToValue.find("Latitude_Of_Origin");
4036
20
            if (iterLatitudeOfOrigin != mapParamNameToValue.end() &&
4037
19
                std::fabs(io::asDouble(iterLatitudeOfOrigin->second)) == 90.0) {
4038
14
                wkt2_mapping =
4039
14
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
4040
14
            }
4041
20
        } catch (const std::exception &) {
4042
0
        }
4043
20
    }
4044
287
    assert(wkt2_mapping);
4045
4046
287
    wkt2_mapping = selectSphericalOrEllipsoidal(wkt2_mapping, baseGeodCRS);
4047
4048
287
    PropertyMap propertiesMethod;
4049
287
    propertiesMethod.set(IdentifiedObject::NAME_KEY, wkt2_mapping->wkt2_name);
4050
287
    if (wkt2_mapping->epsg_code != 0) {
4051
126
        propertiesMethod.set(Identifier::CODE_KEY, wkt2_mapping->epsg_code);
4052
126
        propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4053
126
    }
4054
4055
287
    std::vector<OperationParameterNNPtr> parameters;
4056
287
    std::vector<ParameterValueNNPtr> values;
4057
4058
287
    if (wkt2_mapping->epsg_code == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
4059
1
        ci_equal(esriProjectionName, "Plate_Carree")) {
4060
        // Add a fixed  Latitude of 1st parallel = 0 so as to have all
4061
        // parameters expected by Equidistant Cylindrical.
4062
1
        mapWKT2NameToESRIName[EPSG_NAME_PARAMETER_LATITUDE_1ST_STD_PARALLEL] =
4063
1
            "Standard_Parallel_1";
4064
1
        mapParamNameToValue["Standard_Parallel_1"] = "0";
4065
286
    } else if ((wkt2_mapping->epsg_code ==
4066
286
                    EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4067
281
                wkt2_mapping->epsg_code ==
4068
281
                    EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B) &&
4069
5
               !ci_equal(esriProjectionName,
4070
5
                         "Rectified_Skew_Orthomorphic_Natural_Origin") &&
4071
0
               !ci_equal(esriProjectionName,
4072
0
                         "Rectified_Skew_Orthomorphic_Center")) {
4073
        // ESRI WKT lacks the angle to skew grid
4074
        // Take it from the azimuth value
4075
0
        mapWKT2NameToESRIName
4076
0
            [EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID] = "Azimuth";
4077
0
    }
4078
4079
1.47k
    for (int i = 0; wkt2_mapping->params[i] != nullptr; i++) {
4080
1.19k
        const auto *paramMapping = wkt2_mapping->params[i];
4081
4082
1.19k
        auto iter = mapWKT2NameToESRIName.find(paramMapping->wkt2_name);
4083
1.19k
        if (iter == mapWKT2NameToESRIName.end()) {
4084
11
            continue;
4085
11
        }
4086
1.18k
        const auto &esriParamName = iter->second;
4087
1.18k
        auto iter2 = mapParamNameToValue.find(esriParamName);
4088
1.18k
        auto mapParamNameToValueEnd = mapParamNameToValue.end();
4089
1.18k
        if (iter2 == mapParamNameToValueEnd) {
4090
            // In case we don't find a direct match, try the aliases
4091
101
            for (iter2 = mapParamNameToValue.begin();
4092
244
                 iter2 != mapParamNameToValueEnd; ++iter2) {
4093
146
                if (areEquivalentParameters(iter2->first, esriParamName)) {
4094
3
                    break;
4095
3
                }
4096
146
            }
4097
101
            if (iter2 == mapParamNameToValueEnd) {
4098
98
                continue;
4099
98
            }
4100
101
        }
4101
4102
1.08k
        PropertyMap propertiesParameter;
4103
1.08k
        propertiesParameter.set(IdentifiedObject::NAME_KEY,
4104
1.08k
                                paramMapping->wkt2_name);
4105
1.08k
        if (paramMapping->epsg_code != 0) {
4106
1.04k
            propertiesParameter.set(Identifier::CODE_KEY,
4107
1.04k
                                    paramMapping->epsg_code);
4108
1.04k
            propertiesParameter.set(Identifier::CODESPACE_KEY,
4109
1.04k
                                    Identifier::EPSG);
4110
1.04k
        }
4111
1.08k
        parameters.push_back(OperationParameter::create(propertiesParameter));
4112
4113
1.08k
        try {
4114
1.08k
            double val = io::asDouble(iter2->second);
4115
1.08k
            auto unit = guessUnitForParameter(
4116
1.08k
                paramMapping->wkt2_name, defaultLinearUnit, defaultAngularUnit);
4117
1.08k
            values.push_back(ParameterValue::create(Measure(val, unit)));
4118
1.08k
        } catch (const std::exception &) {
4119
6
            throw ParsingException(
4120
6
                concat("unhandled parameter value type : ", iter2->second));
4121
6
        }
4122
1.08k
    }
4123
4124
281
    return Conversion::create(
4125
281
               PropertyMap().set(IdentifiedObject::NAME_KEY,
4126
281
                                 esriProjectionName == "Gauss_Kruger"
4127
281
                                     ? "unnnamed (Gauss Kruger)"
4128
281
                                     : "unnamed"),
4129
281
               propertiesMethod, parameters, values)
4130
281
        ->identify();
4131
287
}
4132
4133
// ---------------------------------------------------------------------------
4134
4135
ConversionNNPtr WKTParser::Private::buildProjectionFromESRI(
4136
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4137
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4138
578
    const UnitOfMeasure &defaultAngularUnit) {
4139
4140
578
    std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
4141
578
    const auto esriMapping =
4142
578
        getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue);
4143
578
    if (esriMapping == nullptr) {
4144
294
        return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
4145
294
                                       defaultLinearUnit, defaultAngularUnit);
4146
294
    }
4147
4148
284
    return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
4149
284
                                   defaultLinearUnit, defaultAngularUnit,
4150
284
                                   esriMapping, mapParamNameToValue);
4151
578
}
4152
4153
// ---------------------------------------------------------------------------
4154
4155
ConversionNNPtr WKTParser::Private::buildProjection(
4156
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4157
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4158
611
    const UnitOfMeasure &defaultAngularUnit) {
4159
611
    if (projectionNode->GP()->childrenSize() == 0) {
4160
0
        ThrowNotEnoughChildren(WKTConstants::PROJECTION);
4161
0
    }
4162
611
    if (esriStyle_ || maybeEsriStyle_) {
4163
578
        return buildProjectionFromESRI(baseGeodCRS, projCRSNode, projectionNode,
4164
578
                                       defaultLinearUnit, defaultAngularUnit);
4165
578
    }
4166
33
    return buildProjectionStandard(baseGeodCRS, projCRSNode, projectionNode,
4167
33
                                   defaultLinearUnit, defaultAngularUnit);
4168
611
}
4169
4170
// ---------------------------------------------------------------------------
4171
4172
std::string
4173
WKTParser::Private::projectionGetParameter(const WKTNodeNNPtr &projCRSNode,
4174
0
                                           const char *paramName) {
4175
0
    for (const auto &childNode : projCRSNode->GP()->children()) {
4176
0
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4177
0
            const auto &childNodeChildren = childNode->GP()->children();
4178
0
            if (childNodeChildren.size() == 2 &&
4179
0
                metadata::Identifier::isEquivalentName(
4180
0
                    stripQuotes(childNodeChildren[0]).c_str(), paramName)) {
4181
0
                return childNodeChildren[1]->GP()->value();
4182
0
            }
4183
0
        }
4184
0
    }
4185
0
    return std::string();
4186
0
}
4187
4188
// ---------------------------------------------------------------------------
4189
4190
ConversionNNPtr WKTParser::Private::buildProjectionStandard(
4191
    const GeodeticCRSNNPtr &baseGeodCRS, const WKTNodeNNPtr &projCRSNode,
4192
    const WKTNodeNNPtr &projectionNode, const UnitOfMeasure &defaultLinearUnit,
4193
327
    const UnitOfMeasure &defaultAngularUnit) {
4194
327
    std::string wkt1ProjectionName =
4195
327
        stripQuotes(projectionNode->GP()->children()[0]);
4196
4197
327
    std::vector<OperationParameterNNPtr> parameters;
4198
327
    std::vector<ParameterValueNNPtr> values;
4199
327
    bool tryToIdentifyWKT1Method = true;
4200
4201
327
    auto &extensionNode = projCRSNode->lookForChild(WKTConstants::EXTENSION);
4202
327
    const auto &extensionChildren = extensionNode->GP()->children();
4203
4204
327
    bool gdal_3026_hack = false;
4205
327
    if (metadata::Identifier::isEquivalentName(wkt1ProjectionName.c_str(),
4206
327
                                               "Mercator_1SP") &&
4207
0
        projectionGetParameter(projCRSNode, "center_latitude").empty()) {
4208
4209
        // Hack for https://trac.osgeo.org/gdal/ticket/3026
4210
0
        std::string lat0(
4211
0
            projectionGetParameter(projCRSNode, "latitude_of_origin"));
4212
0
        if (!lat0.empty() && lat0 != "0" && lat0 != "0.0") {
4213
0
            wkt1ProjectionName = "Mercator_2SP";
4214
0
            gdal_3026_hack = true;
4215
0
        } else {
4216
            // The latitude of origin, which should always be zero, is
4217
            // missing
4218
            // in GDAL WKT1, but provisionned in the EPSG Mercator_1SP
4219
            // definition,
4220
            // so add it manually.
4221
0
            PropertyMap propertiesParameter;
4222
0
            propertiesParameter.set(IdentifiedObject::NAME_KEY,
4223
0
                                    "Latitude of natural origin");
4224
0
            propertiesParameter.set(Identifier::CODE_KEY, 8801);
4225
0
            propertiesParameter.set(Identifier::CODESPACE_KEY,
4226
0
                                    Identifier::EPSG);
4227
0
            parameters.push_back(
4228
0
                OperationParameter::create(propertiesParameter));
4229
0
            values.push_back(
4230
0
                ParameterValue::create(Measure(0, UnitOfMeasure::DEGREE)));
4231
0
        }
4232
4233
327
    } else if (metadata::Identifier::isEquivalentName(
4234
327
                   wkt1ProjectionName.c_str(), "Polar_Stereographic")) {
4235
86
        std::map<std::string, Measure> mapParameters;
4236
2.15k
        for (const auto &childNode : projCRSNode->GP()->children()) {
4237
2.15k
            const auto &childNodeChildren = childNode->GP()->children();
4238
2.15k
            if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
4239
896
                childNodeChildren.size() == 2) {
4240
823
                const std::string wkt1ParameterName(
4241
823
                    stripQuotes(childNodeChildren[0]));
4242
823
                try {
4243
823
                    double val = asDouble(childNodeChildren[1]);
4244
823
                    auto unit = guessUnitForParameter(wkt1ParameterName,
4245
823
                                                      defaultLinearUnit,
4246
823
                                                      defaultAngularUnit);
4247
823
                    mapParameters.insert(std::pair<std::string, Measure>(
4248
823
                        tolower(wkt1ParameterName), Measure(val, unit)));
4249
823
                } catch (const std::exception &) {
4250
57
                }
4251
823
            }
4252
2.15k
        }
4253
4254
86
        Measure latitudeOfOrigin = mapParameters["latitude_of_origin"];
4255
86
        Measure centralMeridian = mapParameters["central_meridian"];
4256
86
        Measure scaleFactorFromMap = mapParameters["scale_factor"];
4257
86
        Measure scaleFactor((scaleFactorFromMap.unit() == UnitOfMeasure::NONE)
4258
86
                                ? Measure(1.0, UnitOfMeasure::SCALE_UNITY)
4259
86
                                : scaleFactorFromMap);
4260
86
        Measure falseEasting = mapParameters["false_easting"];
4261
86
        Measure falseNorthing = mapParameters["false_northing"];
4262
86
        if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
4263
0
            scaleFactor.getSIValue() == 1.0) {
4264
0
            return Conversion::createPolarStereographicVariantB(
4265
0
                PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4266
0
                Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
4267
0
                Angle(centralMeridian.value(), centralMeridian.unit()),
4268
0
                Length(falseEasting.value(), falseEasting.unit()),
4269
0
                Length(falseNorthing.value(), falseNorthing.unit()));
4270
0
        }
4271
4272
86
        if (latitudeOfOrigin.unit() != UnitOfMeasure::NONE &&
4273
0
            std::fabs(std::fabs(latitudeOfOrigin.convertToUnit(
4274
0
                          UnitOfMeasure::DEGREE)) -
4275
0
                      90.0) < 1e-10) {
4276
0
            return Conversion::createPolarStereographicVariantA(
4277
0
                PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4278
0
                Angle(latitudeOfOrigin.value(), latitudeOfOrigin.unit()),
4279
0
                Angle(centralMeridian.value(), centralMeridian.unit()),
4280
0
                Scale(scaleFactor.value(), scaleFactor.unit()),
4281
0
                Length(falseEasting.value(), falseEasting.unit()),
4282
0
                Length(falseNorthing.value(), falseNorthing.unit()));
4283
0
        }
4284
4285
86
        tryToIdentifyWKT1Method = false;
4286
        // Import GDAL PROJ4 extension nodes
4287
241
    } else if (extensionChildren.size() == 2 &&
4288
50
               ci_equal(stripQuotes(extensionChildren[0]), "PROJ4")) {
4289
47
        std::string projString = stripQuotes(extensionChildren[1]);
4290
47
        if (starts_with(projString, "+proj=")) {
4291
44
            if (projString.find(" +type=crs") == std::string::npos) {
4292
43
                projString += " +type=crs";
4293
43
            }
4294
44
            try {
4295
44
                auto projObj =
4296
44
                    PROJStringParser().createFromPROJString(projString);
4297
44
                auto projObjCrs =
4298
44
                    nn_dynamic_pointer_cast<ProjectedCRS>(projObj);
4299
44
                if (projObjCrs) {
4300
0
                    return projObjCrs->derivingConversion();
4301
0
                }
4302
44
            } catch (const io::ParsingException &) {
4303
42
            }
4304
44
        }
4305
47
    }
4306
4307
327
    std::string projectionName(std::move(wkt1ProjectionName));
4308
327
    const MethodMapping *mapping =
4309
327
        tryToIdentifyWKT1Method ? getMappingFromWKT1(projectionName) : nullptr;
4310
4311
327
    if (!mapping) {
4312
        // Sometimes non-WKT1:ESRI looking WKT can actually use WKT1:ESRI
4313
        // projection definitions
4314
244
        std::map<std::string, std::string, ci_less_struct> mapParamNameToValue;
4315
244
        const auto esriMapping =
4316
244
            getESRIMapping(projCRSNode, projectionNode, mapParamNameToValue);
4317
244
        if (esriMapping != nullptr) {
4318
11
            return buildProjectionFromESRI(
4319
11
                baseGeodCRS, projCRSNode, projectionNode, defaultLinearUnit,
4320
11
                defaultAngularUnit, esriMapping, mapParamNameToValue);
4321
11
        }
4322
244
    }
4323
4324
316
    if (mapping) {
4325
83
        mapping = selectSphericalOrEllipsoidal(mapping, baseGeodCRS);
4326
233
    } else if (metadata::Identifier::isEquivalentName(
4327
233
                   projectionName.c_str(), "Lambert Conformal Conic")) {
4328
        // Lambert Conformal Conic or Lambert_Conformal_Conic are respectively
4329
        // used by Oracle WKT and Trimble for either LCC 1SP or 2SP, so we
4330
        // have to look at parameters to figure out the variant.
4331
17
        bool found2ndStdParallel = false;
4332
17
        bool foundScaleFactor = false;
4333
115
        for (const auto &childNode : projCRSNode->GP()->children()) {
4334
115
            if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4335
28
                const auto &childNodeChildren = childNode->GP()->children();
4336
28
                if (childNodeChildren.size() < 2) {
4337
2
                    ThrowNotEnoughChildren(WKTConstants::PARAMETER);
4338
2
                }
4339
26
                const std::string wkt1ParameterName(
4340
26
                    stripQuotes(childNodeChildren[0]));
4341
26
                if (metadata::Identifier::isEquivalentName(
4342
26
                        wkt1ParameterName.c_str(), WKT1_STANDARD_PARALLEL_2)) {
4343
4
                    found2ndStdParallel = true;
4344
22
                } else if (metadata::Identifier::isEquivalentName(
4345
22
                               wkt1ParameterName.c_str(), WKT1_SCALE_FACTOR)) {
4346
0
                    foundScaleFactor = true;
4347
0
                }
4348
26
            }
4349
115
        }
4350
15
        if (found2ndStdParallel && !foundScaleFactor) {
4351
3
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
4352
12
        } else if (!found2ndStdParallel && foundScaleFactor) {
4353
0
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
4354
12
        } else if (found2ndStdParallel && foundScaleFactor) {
4355
            // Not sure if that happens
4356
0
            mapping = getMapping(
4357
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
4358
0
        }
4359
15
    }
4360
4361
    // For Krovak, we need to look at axis to decide between the Krovak and
4362
    // Krovak East-North Oriented methods
4363
314
    if (ci_equal(projectionName, "Krovak") &&
4364
67
        projCRSNode->countChildrenOfName(WKTConstants::AXIS) == 2 &&
4365
0
        &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 0),
4366
0
                   defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 1)
4367
0
                ->direction() == &AxisDirection::SOUTH &&
4368
0
        &buildAxis(projCRSNode->GP()->lookForChild(WKTConstants::AXIS, 1),
4369
0
                   defaultLinearUnit, UnitOfMeasure::Type::LINEAR, false, 2)
4370
0
                ->direction() == &AxisDirection::WEST) {
4371
0
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
4372
0
    }
4373
4374
314
    PropertyMap propertiesMethod;
4375
314
    if (mapping) {
4376
86
        projectionName = mapping->wkt2_name;
4377
86
        if (mapping->epsg_code != 0) {
4378
74
            propertiesMethod.set(Identifier::CODE_KEY, mapping->epsg_code);
4379
74
            propertiesMethod.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
4380
74
        }
4381
86
    }
4382
314
    propertiesMethod.set(IdentifiedObject::NAME_KEY, projectionName);
4383
4384
314
    std::vector<bool> foundParameters;
4385
314
    if (mapping) {
4386
86
        size_t countParams = 0;
4387
643
        while (mapping->params[countParams] != nullptr) {
4388
557
            ++countParams;
4389
557
        }
4390
86
        foundParameters.resize(countParams);
4391
86
    }
4392
4393
2.71k
    for (const auto &childNode : projCRSNode->GP()->children()) {
4394
2.71k
        if (ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER)) {
4395
980
            const auto &childNodeChildren = childNode->GP()->children();
4396
980
            if (childNodeChildren.size() < 2) {
4397
37
                ThrowNotEnoughChildren(WKTConstants::PARAMETER);
4398
37
            }
4399
943
            const auto &paramValue = childNodeChildren[1]->GP()->value();
4400
4401
943
            PropertyMap propertiesParameter;
4402
943
            const std::string wkt1ParameterName(
4403
943
                stripQuotes(childNodeChildren[0]));
4404
943
            std::string parameterName(wkt1ParameterName);
4405
943
            if (gdal_3026_hack) {
4406
0
                if (ci_equal(parameterName, "latitude_of_origin")) {
4407
0
                    parameterName = "standard_parallel_1";
4408
0
                } else if (ci_equal(parameterName, "scale_factor") &&
4409
0
                           paramValue == "1") {
4410
0
                    continue;
4411
0
                }
4412
0
            }
4413
943
            auto *paramMapping =
4414
943
                mapping ? getMappingFromWKT1(mapping, parameterName) : nullptr;
4415
943
            if (mapping &&
4416
258
                mapping->epsg_code == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
4417
0
                ci_equal(parameterName, "latitude_of_origin")) {
4418
                // Some illegal formulations of Mercator_2SP have a unexpected
4419
                // latitude_of_origin parameter. We accept it on import, but
4420
                // do not accept it when exporting to PROJ string, unless it is
4421
                // zero.
4422
                // No need to try to update foundParameters[] as this is a
4423
                // unexpected one.
4424
0
                parameterName = EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
4425
0
                propertiesParameter.set(
4426
0
                    Identifier::CODE_KEY,
4427
0
                    EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN);
4428
0
                propertiesParameter.set(Identifier::CODESPACE_KEY,
4429
0
                                        Identifier::EPSG);
4430
943
            } else if (mapping && paramMapping) {
4431
7
                for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4432
7
                    if (mapping->params[idx] == paramMapping) {
4433
4
                        foundParameters[idx] = true;
4434
4
                        break;
4435
4
                    }
4436
7
                }
4437
4
                parameterName = paramMapping->wkt2_name;
4438
4
                if (paramMapping->epsg_code != 0) {
4439
4
                    propertiesParameter.set(Identifier::CODE_KEY,
4440
4
                                            paramMapping->epsg_code);
4441
4
                    propertiesParameter.set(Identifier::CODESPACE_KEY,
4442
4
                                            Identifier::EPSG);
4443
4
                }
4444
4
            }
4445
943
            propertiesParameter.set(IdentifiedObject::NAME_KEY, parameterName);
4446
943
            parameters.push_back(
4447
943
                OperationParameter::create(propertiesParameter));
4448
943
            try {
4449
943
                double val = io::asDouble(paramValue);
4450
943
                auto unit = guessUnitForParameter(
4451
943
                    wkt1ParameterName, defaultLinearUnit, defaultAngularUnit);
4452
943
                values.push_back(ParameterValue::create(Measure(val, unit)));
4453
943
            } catch (const std::exception &) {
4454
134
                throw ParsingException(
4455
134
                    concat("unhandled parameter value type : ", paramValue));
4456
134
            }
4457
943
        }
4458
2.71k
    }
4459
4460
    // Add back important parameters that should normally be present, but
4461
    // are sometimes missing. Currently we only deal with Scale factor at
4462
    // natural origin. This is to avoid a default value of 0 to slip in later.
4463
    // But such WKT should be considered invalid.
4464
143
    if (mapping) {
4465
150
        for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4466
129
            if (!foundParameters[idx] &&
4467
129
                mapping->params[idx]->epsg_code ==
4468
129
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN) {
4469
4470
5
                emitRecoverableWarning(
4471
5
                    "The WKT string lacks a value "
4472
5
                    "for " EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN
4473
5
                    ". Default it to 1.");
4474
4475
5
                PropertyMap propertiesParameter;
4476
5
                propertiesParameter.set(
4477
5
                    Identifier::CODE_KEY,
4478
5
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4479
5
                propertiesParameter.set(Identifier::CODESPACE_KEY,
4480
5
                                        Identifier::EPSG);
4481
5
                propertiesParameter.set(
4482
5
                    IdentifiedObject::NAME_KEY,
4483
5
                    EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN);
4484
5
                parameters.push_back(
4485
5
                    OperationParameter::create(propertiesParameter));
4486
5
                values.push_back(ParameterValue::create(
4487
5
                    Measure(1.0, UnitOfMeasure::SCALE_UNITY)));
4488
5
            }
4489
129
        }
4490
21
    }
4491
4492
143
    if (mapping && (mapping->epsg_code ==
4493
21
                        EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A ||
4494
18
                    mapping->epsg_code ==
4495
18
                        EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B)) {
4496
        // Special case when importing some GDAL WKT of Hotine Oblique Mercator
4497
        // that have a Azimuth parameter but lacks the Rectified Grid Angle.
4498
        // We have code in the exportToPROJString() to deal with that situation,
4499
        // but also adds the rectified grid angle from the azimuth on import.
4500
3
        bool foundAngleRecifiedToSkewGrid = false;
4501
3
        bool foundAzimuth = false;
4502
24
        for (size_t idx = 0; mapping->params[idx] != nullptr; ++idx) {
4503
21
            if (foundParameters[idx] &&
4504
0
                mapping->params[idx]->epsg_code ==
4505
0
                    EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
4506
0
                foundAngleRecifiedToSkewGrid = true;
4507
21
            } else if (foundParameters[idx] &&
4508
0
                       mapping->params[idx]->epsg_code ==
4509
0
                           EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) {
4510
0
                foundAzimuth = true;
4511
0
            }
4512
21
        }
4513
3
        if (!foundAngleRecifiedToSkewGrid && foundAzimuth) {
4514
0
            for (size_t idx = 0; idx < parameters.size(); ++idx) {
4515
0
                if (parameters[idx]->getEPSGCode() ==
4516
0
                    EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE) {
4517
0
                    PropertyMap propertiesParameter;
4518
0
                    propertiesParameter.set(
4519
0
                        Identifier::CODE_KEY,
4520
0
                        EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID);
4521
0
                    propertiesParameter.set(Identifier::CODESPACE_KEY,
4522
0
                                            Identifier::EPSG);
4523
0
                    propertiesParameter.set(
4524
0
                        IdentifiedObject::NAME_KEY,
4525
0
                        EPSG_NAME_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID);
4526
0
                    parameters.push_back(
4527
0
                        OperationParameter::create(propertiesParameter));
4528
0
                    values.push_back(values[idx]);
4529
0
                }
4530
0
            }
4531
0
        }
4532
3
    }
4533
4534
143
    return Conversion::create(
4535
143
               PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
4536
143
               propertiesMethod, parameters, values)
4537
143
        ->identify();
4538
314
}
4539
4540
// ---------------------------------------------------------------------------
4541
4542
static ProjectedCRSNNPtr createPseudoMercator(const PropertyMap &props,
4543
0
                                              const cs::CartesianCSNNPtr &cs) {
4544
0
    auto conversion = Conversion::createPopularVisualisationPseudoMercator(
4545
0
        PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"), Angle(0),
4546
0
        Angle(0), Length(0), Length(0));
4547
0
    return ProjectedCRS::create(props, GeographicCRS::EPSG_4326, conversion,
4548
0
                                cs);
4549
0
}
4550
4551
// ---------------------------------------------------------------------------
4552
4553
ProjectedCRSNNPtr
4554
722
WKTParser::Private::buildProjectedCRS(const WKTNodeNNPtr &node) {
4555
4556
722
    const auto *nodeP = node->GP();
4557
722
    auto &conversionNode = nodeP->lookForChild(WKTConstants::CONVERSION);
4558
722
    auto &projectionNode = nodeP->lookForChild(WKTConstants::PROJECTION);
4559
722
    if (isNull(conversionNode) && isNull(projectionNode)) {
4560
52
        ThrowMissing(WKTConstants::CONVERSION);
4561
52
    }
4562
4563
670
    auto &baseGeodCRSNode =
4564
670
        nodeP->lookForChild(WKTConstants::BASEGEODCRS,
4565
670
                            WKTConstants::BASEGEOGCRS, WKTConstants::GEOGCS);
4566
670
    if (isNull(baseGeodCRSNode)) {
4567
9
        throw ParsingException(
4568
9
            "Missing BASEGEODCRS / BASEGEOGCRS / GEOGCS node");
4569
9
    }
4570
661
    auto baseGeodCRS = buildGeodeticCRS(baseGeodCRSNode);
4571
4572
661
    auto props = buildProperties(node);
4573
4574
661
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
4575
661
    const auto &nodeValue = nodeP->value();
4576
661
    if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::PROJCS) &&
4577
0
        !ci_equal(nodeValue, WKTConstants::BASEPROJCRS)) {
4578
0
        ThrowMissing(WKTConstants::CS_);
4579
0
    }
4580
4581
661
    std::string projCRSName = stripQuotes(nodeP->children()[0]);
4582
4583
661
    auto cs = [this, &projCRSName, &nodeP, &csNode, &node, &nodeValue,
4584
661
               &conversionNode]() -> CoordinateSystemNNPtr {
4585
642
        if (isNull(csNode) && ci_equal(nodeValue, WKTConstants::BASEPROJCRS) &&
4586
0
            !isNull(conversionNode)) {
4587
            // A BASEPROJCRS (as of WKT2 18-010r11) normally lacks an explicit
4588
            // CS[] which cause issues to properly instantiate it. So we first
4589
            // start by trying to identify the BASEPROJCRS by its id or name.
4590
            // And fallback to exploring the conversion parameters to infer the
4591
            // CS AXIS unit from the linear parameter unit... Not fully bullet
4592
            // proof.
4593
0
            if (dbContext_) {
4594
                // Get official name from database if ID is present
4595
0
                auto &idNode = nodeP->lookForChild(WKTConstants::ID);
4596
0
                if (!isNull(idNode)) {
4597
0
                    try {
4598
0
                        auto id = buildId(node, idNode, false, false);
4599
0
                        auto authFactory = AuthorityFactory::create(
4600
0
                            NN_NO_CHECK(dbContext_), *id->codeSpace());
4601
0
                        auto projCRS =
4602
0
                            authFactory->createProjectedCRS(id->code());
4603
0
                        return projCRS->coordinateSystem();
4604
0
                    } catch (const std::exception &) {
4605
0
                    }
4606
0
                }
4607
4608
0
                auto authFactory = AuthorityFactory::create(
4609
0
                    NN_NO_CHECK(dbContext_), std::string());
4610
0
                auto res = authFactory->createObjectsFromName(
4611
0
                    projCRSName, {AuthorityFactory::ObjectType::PROJECTED_CRS},
4612
0
                    false, 2);
4613
0
                if (res.size() == 1) {
4614
0
                    auto projCRS =
4615
0
                        dynamic_cast<const ProjectedCRS *>(res.front().get());
4616
0
                    if (projCRS) {
4617
0
                        return projCRS->coordinateSystem();
4618
0
                    }
4619
0
                }
4620
0
            }
4621
4622
0
            auto conv = buildConversion(conversionNode, UnitOfMeasure::METRE,
4623
0
                                        UnitOfMeasure::DEGREE);
4624
0
            UnitOfMeasure linearUOM = UnitOfMeasure::NONE;
4625
0
            for (const auto &genOpParamvalue : conv->parameterValues()) {
4626
0
                auto opParamvalue =
4627
0
                    dynamic_cast<const operation::OperationParameterValue *>(
4628
0
                        genOpParamvalue.get());
4629
0
                if (opParamvalue) {
4630
0
                    const auto &parameterValue = opParamvalue->parameterValue();
4631
0
                    if (parameterValue->type() ==
4632
0
                        operation::ParameterValue::Type::MEASURE) {
4633
0
                        const auto &measure = parameterValue->value();
4634
0
                        const auto &unit = measure.unit();
4635
0
                        if (unit.type() == UnitOfMeasure::Type::LINEAR) {
4636
0
                            if (linearUOM == UnitOfMeasure::NONE) {
4637
0
                                linearUOM = unit;
4638
0
                            } else if (linearUOM != unit) {
4639
0
                                linearUOM = UnitOfMeasure::NONE;
4640
0
                                break;
4641
0
                            }
4642
0
                        }
4643
0
                    }
4644
0
                }
4645
0
            }
4646
0
            if (linearUOM != UnitOfMeasure::NONE) {
4647
0
                return CartesianCS::createEastingNorthing(linearUOM);
4648
0
            }
4649
0
        }
4650
642
        return buildCS(csNode, node, UnitOfMeasure::NONE);
4651
642
    }();
4652
661
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
4653
4654
661
    if (dbContext_ && ((!esriStyle_ && projCRSName == "ETRF2000-PL / CS92" &&
4655
0
                        baseGeodCRS->nameStr() == "ETRF2000-PL") ||
4656
559
                       (esriStyle_ && projCRSName == "ETRF2000-PL_CS92" &&
4657
0
                        (baseGeodCRS->nameStr() == "GCS_ETRF2000-PL" ||
4658
0
                         baseGeodCRS->nameStr() == "ETRF2000-PL")))) {
4659
        // Oddity: "ETRF2000-PL / CS92" (EPSG:2180) has switched back to
4660
        // "ETRS89 / PL-1992"
4661
0
        auto authFactoryEPSG =
4662
0
            io::AuthorityFactory::create(NN_NO_CHECK(dbContext_), "EPSG");
4663
0
        auto newProjCRS = authFactoryEPSG->createProjectedCRS("2180");
4664
0
        props.set(IdentifiedObject::NAME_KEY, newProjCRS->nameStr());
4665
0
        baseGeodCRS = newProjCRS->baseCRS();
4666
0
    }
4667
    // In EPSG v12.025, Norway projected systems based on ETRS89 (EPSG:4258)
4668
    // have switched to use ETRS89-NOR [EUREF89] (EPSG:10875).
4669
    // Similarly for other ETRS89-like datums in later releases
4670
661
    else if (dbContext_ &&
4671
559
             (((starts_with(projCRSName, "ETRS89 / ") ||
4672
559
                (esriStyle_ && starts_with(projCRSName, "ETRS_1989_"))) &&
4673
0
               baseGeodCRS->nameStr() == "ETRS89") ||
4674
559
              starts_with(projCRSName, "ETRF2000-PL /")) &&
4675
0
             util::isOfExactType<GeographicCRS>(*(baseGeodCRS.get())) &&
4676
0
             baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
4677
0
        auto authFactoryEPSG =
4678
0
            io::AuthorityFactory::create(NN_NO_CHECK(dbContext_), "EPSG");
4679
0
        const auto objCandidates = authFactoryEPSG->createObjectsFromNameEx(
4680
0
            projCRSName, {io::AuthorityFactory::ObjectType::PROJECTED_CRS},
4681
0
            false, // approximateMatch
4682
0
            0,     // limit
4683
0
            true   // useAliases
4684
0
        );
4685
0
        for (const auto &[obj, name] : objCandidates) {
4686
0
            if (name == projCRSName) {
4687
0
                auto candidateProj =
4688
0
                    dynamic_cast<const crs::ProjectedCRS *>(obj.get());
4689
0
                if (candidateProj &&
4690
0
                    candidateProj->baseCRS()->nameStr() !=
4691
0
                        baseGeodCRS->nameStr() &&
4692
0
                    candidateProj->baseCRS()->_isEquivalentTo(
4693
0
                        baseGeodCRS.get(),
4694
0
                        util::IComparable::Criterion::
4695
0
                            EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
4696
0
                        dbContext_)) {
4697
0
                    props.set(IdentifiedObject::NAME_KEY,
4698
0
                              candidateProj->nameStr());
4699
0
                    baseGeodCRS = candidateProj->baseCRS();
4700
0
                    break;
4701
0
                }
4702
0
            }
4703
0
        }
4704
0
    }
4705
4706
661
    if (esriStyle_ && dbContext_) {
4707
275
        if (cartesianCS) {
4708
275
            std::string outTableName;
4709
275
            std::string authNameFromAlias;
4710
275
            std::string codeFromAlias;
4711
275
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
4712
275
                                                        std::string());
4713
275
            auto officialName = authFactory->getOfficialNameFromAlias(
4714
275
                projCRSName, "projected_crs", "ESRI", false, outTableName,
4715
275
                authNameFromAlias, codeFromAlias);
4716
275
            if (!officialName.empty()) {
4717
                // Special case for https://github.com/OSGeo/PROJ/issues/2086
4718
                // The name of the CRS to identify is
4719
                // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501
4720
                // whereas it should be
4721
                // NAD_1983_HARN_StatePlane_Colorado_North_FIPS_0501_Feet
4722
0
                constexpr double US_FOOT_CONV_FACTOR = 12.0 / 39.37;
4723
0
                if (projCRSName.find("_FIPS_") != std::string::npos &&
4724
0
                    projCRSName.find("_Feet") == std::string::npos &&
4725
0
                    std::fabs(
4726
0
                        cartesianCS->axisList()[0]->unit().conversionToSI() -
4727
0
                        US_FOOT_CONV_FACTOR) < 1e-10 * US_FOOT_CONV_FACTOR) {
4728
0
                    auto officialNameFromFeet =
4729
0
                        authFactory->getOfficialNameFromAlias(
4730
0
                            projCRSName + "_Feet", "projected_crs", "ESRI",
4731
0
                            false, outTableName, authNameFromAlias,
4732
0
                            codeFromAlias);
4733
0
                    if (!officialNameFromFeet.empty()) {
4734
0
                        officialName = std::move(officialNameFromFeet);
4735
0
                    }
4736
0
                }
4737
4738
0
                projCRSName = officialName;
4739
0
                props.set(IdentifiedObject::NAME_KEY, officialName);
4740
0
            }
4741
275
        }
4742
275
    }
4743
4744
661
    if (isNull(conversionNode) && hasWebMercPROJ4String(node, projectionNode) &&
4745
0
        cartesianCS) {
4746
0
        toWGS84Parameters_.clear();
4747
0
        return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4748
0
    }
4749
4750
    // WGS_84_Pseudo_Mercator: Particular case for corrupted ESRI WKT generated
4751
    // by older GDAL versions
4752
    // https://trac.osgeo.org/gdal/changeset/30732
4753
    // WGS_1984_Web_Mercator: deprecated ESRI:102113
4754
661
    if (cartesianCS && (metadata::Identifier::isEquivalentName(
4755
635
                            projCRSName.c_str(), "WGS_84_Pseudo_Mercator") ||
4756
635
                        metadata::Identifier::isEquivalentName(
4757
635
                            projCRSName.c_str(), "WGS_1984_Web_Mercator"))) {
4758
0
        toWGS84Parameters_.clear();
4759
0
        return createPseudoMercator(props, NN_NO_CHECK(cartesianCS));
4760
0
    }
4761
4762
    // For WKT2, if there is no explicit parameter unit, use metre for linear
4763
    // units and degree for angular units
4764
661
    const UnitOfMeasure linearUnit(
4765
661
        !isNull(conversionNode)
4766
661
            ? UnitOfMeasure::METRE
4767
661
            : buildUnitInSubNode(node, UnitOfMeasure::Type::LINEAR));
4768
661
    const auto &angularUnit =
4769
661
        !isNull(conversionNode)
4770
661
            ? UnitOfMeasure::DEGREE
4771
661
            : baseGeodCRS->coordinateSystem()->axisList()[0]->unit();
4772
4773
661
    auto conversion =
4774
661
        !isNull(conversionNode)
4775
661
            ? buildConversion(conversionNode, linearUnit, angularUnit)
4776
661
            : buildProjection(baseGeodCRS, node, projectionNode, linearUnit,
4777
637
                              angularUnit);
4778
4779
    // No explicit AXIS node ? (WKT1)
4780
661
    if (isNull(nodeP->lookForChild(WKTConstants::AXIS))) {
4781
442
        props.set("IMPLICIT_CS", true);
4782
442
    }
4783
4784
661
    if (isNull(csNode) && node->countChildrenOfName(WKTConstants::AXIS) == 0) {
4785
4786
442
        const auto methodCode = conversion->method()->getEPSGCode();
4787
        // Krovak south oriented ?
4788
442
        if (methodCode == EPSG_CODE_METHOD_KROVAK) {
4789
8
            cartesianCS =
4790
8
                CartesianCS::create(
4791
8
                    PropertyMap(),
4792
8
                    CoordinateSystemAxis::create(
4793
8
                        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4794
8
                                                AxisName::Southing),
4795
8
                        emptyString, AxisDirection::SOUTH, linearUnit),
4796
8
                    CoordinateSystemAxis::create(
4797
8
                        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
4798
8
                                                AxisName::Westing),
4799
8
                        emptyString, AxisDirection::WEST, linearUnit))
4800
8
                    .as_nullable();
4801
434
        } else if (methodCode ==
4802
434
                       EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A ||
4803
420
                   methodCode ==
4804
420
                       EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA) {
4805
            // It is likely that the ESRI definition of EPSG:32661 (UPS North) &
4806
            // EPSG:32761 (UPS South) uses the easting-northing order, instead
4807
            // of the EPSG northing-easting order.
4808
            // Same for WKT1_GDAL
4809
19
            const double lat0 = conversion->parameterValueNumeric(
4810
19
                EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
4811
19
                common::UnitOfMeasure::DEGREE);
4812
19
            if (std::fabs(lat0 - 90) < 1e-10) {
4813
10
                cartesianCS =
4814
10
                    CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4815
10
                        linearUnit)
4816
10
                        .as_nullable();
4817
10
            } else if (std::fabs(lat0 - -90) < 1e-10) {
4818
9
                cartesianCS =
4819
9
                    CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4820
9
                        linearUnit)
4821
9
                        .as_nullable();
4822
9
            }
4823
415
        } else if (methodCode ==
4824
415
                   EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B) {
4825
3
            const double lat_ts = conversion->parameterValueNumeric(
4826
3
                EPSG_CODE_PARAMETER_LATITUDE_STD_PARALLEL,
4827
3
                common::UnitOfMeasure::DEGREE);
4828
3
            if (lat_ts > 0) {
4829
3
                cartesianCS =
4830
3
                    CartesianCS::createNorthPoleEastingSouthNorthingSouth(
4831
3
                        linearUnit)
4832
3
                        .as_nullable();
4833
3
            } else if (lat_ts < 0) {
4834
0
                cartesianCS =
4835
0
                    CartesianCS::createSouthPoleEastingNorthNorthingNorth(
4836
0
                        linearUnit)
4837
0
                        .as_nullable();
4838
0
            }
4839
412
        } else if (methodCode ==
4840
412
                   EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
4841
0
            cartesianCS =
4842
0
                CartesianCS::createWestingSouthing(linearUnit).as_nullable();
4843
0
        }
4844
442
    }
4845
661
    if (!cartesianCS) {
4846
0
        ThrowNotExpectedCSType(CartesianCS::WKT2_TYPE);
4847
0
    }
4848
4849
661
    if (cartesianCS->axisList().size() == 3 &&
4850
0
        baseGeodCRS->coordinateSystem()->axisList().size() == 2) {
4851
0
        baseGeodCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
4852
0
            baseGeodCRS->promoteTo3D(std::string(), dbContext_)));
4853
0
    }
4854
4855
661
    addExtensionProj4ToProp(nodeP, props);
4856
4857
661
    return ProjectedCRS::create(props, baseGeodCRS, conversion,
4858
661
                                NN_NO_CHECK(cartesianCS));
4859
661
}
4860
4861
// ---------------------------------------------------------------------------
4862
4863
void WKTParser::Private::parseDynamic(const WKTNodeNNPtr &dynamicNode,
4864
                                      double &frameReferenceEpoch,
4865
11
                                      util::optional<std::string> &modelName) {
4866
11
    auto &frameEpochNode = dynamicNode->lookForChild(WKTConstants::FRAMEEPOCH);
4867
11
    const auto &frameEpochChildren = frameEpochNode->GP()->children();
4868
11
    if (frameEpochChildren.empty()) {
4869
1
        ThrowMissing(WKTConstants::FRAMEEPOCH);
4870
1
    }
4871
10
    try {
4872
10
        frameReferenceEpoch = asDouble(frameEpochChildren[0]);
4873
10
    } catch (const std::exception &) {
4874
3
        throw ParsingException("Invalid FRAMEEPOCH node");
4875
3
    }
4876
7
    auto &modelNode = dynamicNode->GP()->lookForChild(
4877
7
        WKTConstants::MODEL, WKTConstants::VELOCITYGRID);
4878
7
    const auto &modelChildren = modelNode->GP()->children();
4879
7
    if (modelChildren.size() == 1) {
4880
0
        modelName = stripQuotes(modelChildren[0]);
4881
0
    }
4882
7
}
4883
4884
// ---------------------------------------------------------------------------
4885
4886
VerticalReferenceFrameNNPtr WKTParser::Private::buildVerticalReferenceFrame(
4887
707
    const WKTNodeNNPtr &node, const WKTNodeNNPtr &dynamicNode) {
4888
4889
707
    if (!isNull(dynamicNode)) {
4890
0
        double frameReferenceEpoch = 0.0;
4891
0
        util::optional<std::string> modelName;
4892
0
        parseDynamic(dynamicNode, frameReferenceEpoch, modelName);
4893
0
        return DynamicVerticalReferenceFrame::create(
4894
0
            buildProperties(node), getAnchor(node),
4895
0
            optional<RealizationMethod>(),
4896
0
            common::Measure(frameReferenceEpoch, common::UnitOfMeasure::YEAR),
4897
0
            modelName);
4898
0
    }
4899
4900
    // WKT1 VERT_DATUM has a datum type after the datum name
4901
707
    const auto *nodeP = node->GP();
4902
707
    const std::string &name(nodeP->value());
4903
707
    auto &props = buildProperties(node);
4904
707
    const auto &children = nodeP->children();
4905
4906
707
    if (esriStyle_ && dbContext_ && !children.empty()) {
4907
9
        std::string outTableName;
4908
9
        std::string authNameFromAlias;
4909
9
        std::string codeFromAlias;
4910
9
        auto authFactory =
4911
9
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
4912
9
        const std::string datumName = stripQuotes(children[0]);
4913
9
        auto officialName = authFactory->getOfficialNameFromAlias(
4914
9
            datumName, "vertical_datum", "ESRI", false, outTableName,
4915
9
            authNameFromAlias, codeFromAlias);
4916
9
        if (!officialName.empty()) {
4917
0
            props.set(IdentifiedObject::NAME_KEY, officialName);
4918
0
        }
4919
9
    }
4920
4921
707
    if (ci_equal(name, WKTConstants::VERT_DATUM)) {
4922
25
        if (children.size() >= 2) {
4923
20
            props.set("VERT_DATUM_TYPE", children[1]->GP()->value());
4924
20
        }
4925
25
    }
4926
4927
707
    return VerticalReferenceFrame::create(props, getAnchor(node),
4928
707
                                          getAnchorEpoch(node));
4929
707
}
4930
4931
// ---------------------------------------------------------------------------
4932
4933
TemporalDatumNNPtr
4934
23
WKTParser::Private::buildTemporalDatum(const WKTNodeNNPtr &node) {
4935
23
    const auto *nodeP = node->GP();
4936
23
    auto &calendarNode = nodeP->lookForChild(WKTConstants::CALENDAR);
4937
23
    std::string calendar = TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN;
4938
23
    const auto &calendarChildren = calendarNode->GP()->children();
4939
23
    if (calendarChildren.size() == 1) {
4940
0
        calendar = stripQuotes(calendarChildren[0]);
4941
0
    }
4942
4943
23
    auto &timeOriginNode = nodeP->lookForChild(WKTConstants::TIMEORIGIN);
4944
23
    std::string originStr;
4945
23
    const auto &timeOriginNodeChildren = timeOriginNode->GP()->children();
4946
23
    if (timeOriginNodeChildren.size() == 1) {
4947
4
        originStr = stripQuotes(timeOriginNodeChildren[0]);
4948
4
    }
4949
23
    auto origin = DateTime::create(originStr);
4950
23
    return TemporalDatum::create(buildProperties(node), origin, calendar);
4951
23
}
4952
4953
// ---------------------------------------------------------------------------
4954
4955
EngineeringDatumNNPtr
4956
6
WKTParser::Private::buildEngineeringDatum(const WKTNodeNNPtr &node) {
4957
6
    return EngineeringDatum::create(buildProperties(node), getAnchor(node));
4958
6
}
4959
4960
// ---------------------------------------------------------------------------
4961
4962
ParametricDatumNNPtr
4963
11
WKTParser::Private::buildParametricDatum(const WKTNodeNNPtr &node) {
4964
11
    return ParametricDatum::create(buildProperties(node), getAnchor(node));
4965
11
}
4966
4967
// ---------------------------------------------------------------------------
4968
4969
static CRSNNPtr
4970
createBoundCRSSourceTransformationCRS(const crs::CRSPtr &sourceCRS,
4971
6
                                      const crs::CRSPtr &targetCRS) {
4972
6
    CRSPtr sourceTransformationCRS;
4973
6
    if (dynamic_cast<GeographicCRS *>(targetCRS.get())) {
4974
6
        GeographicCRSPtr sourceGeographicCRS =
4975
6
            sourceCRS->extractGeographicCRS();
4976
6
        sourceTransformationCRS = sourceGeographicCRS;
4977
6
        if (sourceGeographicCRS) {
4978
0
            const auto &sourceDatum = sourceGeographicCRS->datum();
4979
0
            if (sourceDatum != nullptr && sourceGeographicCRS->primeMeridian()
4980
0
                                                  ->longitude()
4981
0
                                                  .getSIValue() != 0.0) {
4982
0
                sourceTransformationCRS =
4983
0
                    GeographicCRS::create(
4984
0
                        util::PropertyMap().set(
4985
0
                            common::IdentifiedObject::NAME_KEY,
4986
0
                            sourceGeographicCRS->nameStr() +
4987
0
                                " (with Greenwich prime meridian)"),
4988
0
                        datum::GeodeticReferenceFrame::create(
4989
0
                            util::PropertyMap().set(
4990
0
                                common::IdentifiedObject::NAME_KEY,
4991
0
                                sourceDatum->nameStr() +
4992
0
                                    " (with Greenwich prime meridian)"),
4993
0
                            sourceDatum->ellipsoid(),
4994
0
                            util::optional<std::string>(),
4995
0
                            datum::PrimeMeridian::GREENWICH),
4996
0
                        sourceGeographicCRS->coordinateSystem())
4997
0
                        .as_nullable();
4998
0
            }
4999
6
        } else {
5000
6
            auto vertSourceCRS =
5001
6
                std::dynamic_pointer_cast<VerticalCRS>(sourceCRS);
5002
6
            if (!vertSourceCRS) {
5003
0
                throw ParsingException(
5004
0
                    "Cannot find GeographicCRS or VerticalCRS in sourceCRS");
5005
0
            }
5006
6
            const auto &axis = vertSourceCRS->coordinateSystem()->axisList()[0];
5007
6
            if (axis->unit() == common::UnitOfMeasure::METRE &&
5008
3
                &(axis->direction()) == &AxisDirection::UP) {
5009
3
                sourceTransformationCRS = sourceCRS;
5010
3
            } else {
5011
3
                std::string sourceTransformationCRSName(
5012
3
                    vertSourceCRS->nameStr());
5013
3
                if (ends_with(sourceTransformationCRSName, " (ftUS)")) {
5014
0
                    sourceTransformationCRSName.resize(
5015
0
                        sourceTransformationCRSName.size() - strlen(" (ftUS)"));
5016
0
                }
5017
3
                if (ends_with(sourceTransformationCRSName, " depth")) {
5018
0
                    sourceTransformationCRSName.resize(
5019
0
                        sourceTransformationCRSName.size() - strlen(" depth"));
5020
0
                }
5021
3
                if (!ends_with(sourceTransformationCRSName, " height")) {
5022
3
                    sourceTransformationCRSName += " height";
5023
3
                }
5024
3
                sourceTransformationCRS =
5025
3
                    VerticalCRS::create(
5026
3
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
5027
3
                                          sourceTransformationCRSName),
5028
3
                        vertSourceCRS->datum(), vertSourceCRS->datumEnsemble(),
5029
3
                        VerticalCS::createGravityRelatedHeight(
5030
3
                            common::UnitOfMeasure::METRE))
5031
3
                        .as_nullable();
5032
3
            }
5033
6
        }
5034
6
    } else {
5035
0
        sourceTransformationCRS = sourceCRS;
5036
0
    }
5037
6
    return NN_NO_CHECK(sourceTransformationCRS);
5038
6
}
5039
5040
// ---------------------------------------------------------------------------
5041
5042
644
CRSNNPtr WKTParser::Private::buildVerticalCRS(const WKTNodeNNPtr &node) {
5043
644
    const auto *nodeP = node->GP();
5044
644
    const auto &nodeValue = nodeP->value();
5045
644
    auto &vdatumNode =
5046
644
        nodeP->lookForChild(WKTConstants::VDATUM, WKTConstants::VERT_DATUM,
5047
644
                            WKTConstants::VERTICALDATUM, WKTConstants::VRF);
5048
644
    auto &ensembleNode = nodeP->lookForChild(WKTConstants::ENSEMBLE);
5049
    // like in ESRI  VERTCS["WGS_1984",DATUM["D_WGS_1984",
5050
    //               SPHEROID["WGS_1984",6378137.0,298.257223563]],
5051
    //               PARAMETER["Vertical_Shift",0.0],
5052
    //               PARAMETER["Direction",1.0],UNIT["Meter",1.0]
5053
644
    auto &geogDatumNode = ci_equal(nodeValue, WKTConstants::VERTCS)
5054
644
                              ? nodeP->lookForChild(WKTConstants::DATUM)
5055
644
                              : null_node;
5056
644
    if (isNull(vdatumNode) && isNull(geogDatumNode) && isNull(ensembleNode)) {
5057
24
        throw ParsingException("Missing VDATUM or ENSEMBLE node");
5058
24
    }
5059
5060
2.61k
    for (const auto &childNode : nodeP->children()) {
5061
2.61k
        const auto &childNodeChildren = childNode->GP()->children();
5062
2.61k
        if (childNodeChildren.size() == 2 &&
5063
268
            ci_equal(childNode->GP()->value(), WKTConstants::PARAMETER) &&
5064
11
            childNodeChildren[0]->GP()->value() == "\"Vertical_Shift\"") {
5065
0
            esriStyle_ = true;
5066
0
            break;
5067
0
        }
5068
2.61k
    }
5069
5070
620
    auto &dynamicNode = nodeP->lookForChild(WKTConstants::DYNAMIC);
5071
620
    auto vdatum =
5072
620
        !isNull(geogDatumNode)
5073
620
            ? VerticalReferenceFrame::create(
5074
19
                  PropertyMap()
5075
19
                      .set(IdentifiedObject::NAME_KEY,
5076
19
                           buildGeodeticReferenceFrame(geogDatumNode,
5077
19
                                                       PrimeMeridian::GREENWICH,
5078
19
                                                       null_node)
5079
19
                               ->nameStr())
5080
19
                      .set("VERT_DATUM_TYPE", "2002"))
5081
19
                  .as_nullable()
5082
620
        : !isNull(vdatumNode)
5083
601
            ? buildVerticalReferenceFrame(vdatumNode, dynamicNode).as_nullable()
5084
601
            : nullptr;
5085
620
    auto datumEnsemble =
5086
620
        !isNull(ensembleNode)
5087
620
            ? buildDatumEnsemble(ensembleNode, nullptr, false).as_nullable()
5088
620
            : nullptr;
5089
5090
620
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5091
620
    if (isNull(csNode) && !ci_equal(nodeValue, WKTConstants::VERT_CS) &&
5092
549
        !ci_equal(nodeValue, WKTConstants::VERTCS) &&
5093
3
        !ci_equal(nodeValue, WKTConstants::BASEVERTCRS)) {
5094
3
        ThrowMissing(WKTConstants::CS_);
5095
3
    }
5096
617
    auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(
5097
617
        buildCS(csNode, node, UnitOfMeasure::NONE));
5098
617
    if (!verticalCS) {
5099
0
        ThrowNotExpectedCSType(VerticalCS::WKT2_TYPE);
5100
0
    }
5101
5102
617
    if (vdatum && vdatum->getWKT1DatumType() == "2002" &&
5103
14
        &(verticalCS->axisList()[0]->direction()) == &(AxisDirection::UP)) {
5104
13
        verticalCS =
5105
13
            VerticalCS::create(
5106
13
                util::PropertyMap(),
5107
13
                CoordinateSystemAxis::create(
5108
13
                    util::PropertyMap().set(IdentifiedObject::NAME_KEY,
5109
13
                                            "ellipsoidal height"),
5110
13
                    "h", AxisDirection::UP, verticalCS->axisList()[0]->unit()))
5111
13
                .as_nullable();
5112
13
    }
5113
5114
617
    auto &props = buildProperties(node);
5115
5116
617
    if (esriStyle_ && dbContext_) {
5117
17
        std::string outTableName;
5118
17
        std::string authNameFromAlias;
5119
17
        std::string codeFromAlias;
5120
17
        auto authFactory =
5121
17
            AuthorityFactory::create(NN_NO_CHECK(dbContext_), std::string());
5122
17
        const std::string vertCRSName = stripQuotes(nodeP->children()[0]);
5123
17
        auto officialName = authFactory->getOfficialNameFromAlias(
5124
17
            vertCRSName, "vertical_crs", "ESRI", false, outTableName,
5125
17
            authNameFromAlias, codeFromAlias);
5126
17
        if (!officialName.empty()) {
5127
0
            props.set(IdentifiedObject::NAME_KEY, officialName);
5128
0
        }
5129
17
    }
5130
5131
    // Deal with Lidar WKT1 VertCRS that embeds geoid model in CRS name,
5132
    // following conventions from
5133
    // https://pubs.usgs.gov/tm/11b4/pdf/tm11-B4.pdf
5134
    // page 9
5135
617
    if (ci_equal(nodeValue, WKTConstants::VERT_CS) ||
5136
538
        ci_equal(nodeValue, WKTConstants::VERTCS)) {
5137
538
        std::string name;
5138
538
        if (props.getStringValue(IdentifiedObject::NAME_KEY, name)) {
5139
538
            std::string geoidName;
5140
538
            for (const char *prefix :
5141
538
                 {"NAVD88 - ", "NAVD88 via ", "NAVD88 height - ",
5142
2.01k
                  "NAVD88 height (ftUS) - "}) {
5143
2.01k
                if (starts_with(name, prefix)) {
5144
92
                    geoidName = name.substr(strlen(prefix));
5145
92
                    auto pos = geoidName.find_first_of(" (");
5146
92
                    if (pos != std::string::npos) {
5147
81
                        geoidName.resize(pos);
5148
81
                    }
5149
92
                    break;
5150
92
                }
5151
2.01k
            }
5152
538
            if (!geoidName.empty()) {
5153
92
                const auto &axis = verticalCS->axisList()[0];
5154
92
                const auto &dir = axis->direction();
5155
92
                if (dir == cs::AxisDirection::UP) {
5156
88
                    if (axis->unit() == common::UnitOfMeasure::METRE) {
5157
65
                        props.set(IdentifiedObject::NAME_KEY, "NAVD88 height");
5158
65
                        props.set(Identifier::CODE_KEY, 5703);
5159
65
                        props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5160
65
                    } else if (axis->unit().name() == "US survey foot") {
5161
0
                        props.set(IdentifiedObject::NAME_KEY,
5162
0
                                  "NAVD88 height (ftUS)");
5163
0
                        props.set(Identifier::CODE_KEY, 6360);
5164
0
                        props.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5165
0
                    }
5166
88
                }
5167
92
                PropertyMap propsModel;
5168
92
                propsModel.set(IdentifiedObject::NAME_KEY, toupper(geoidName));
5169
92
                PropertyMap propsDatum;
5170
92
                propsDatum.set(IdentifiedObject::NAME_KEY,
5171
92
                               "North American Vertical Datum 1988");
5172
92
                propsDatum.set(Identifier::CODE_KEY, 5103);
5173
92
                propsDatum.set(Identifier::CODESPACE_KEY, Identifier::EPSG);
5174
92
                vdatum =
5175
92
                    VerticalReferenceFrame::create(propsDatum).as_nullable();
5176
92
                const auto dummyCRS =
5177
92
                    VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
5178
92
                                        NN_NO_CHECK(verticalCS));
5179
92
                const auto model(Transformation::create(
5180
92
                    propsModel, dummyCRS, dummyCRS, nullptr,
5181
92
                    OperationMethod::create(
5182
92
                        PropertyMap(), std::vector<OperationParameterNNPtr>()),
5183
92
                    {}, {}));
5184
92
                props.set("GEOID_MODEL", model);
5185
92
            }
5186
538
        }
5187
538
    }
5188
5189
617
    auto &geoidModelNode = nodeP->lookForChild(WKTConstants::GEOIDMODEL);
5190
617
    if (!isNull(geoidModelNode)) {
5191
45
        ArrayOfBaseObjectNNPtr arrayModels = ArrayOfBaseObject::create();
5192
301
        for (const auto &childNode : nodeP->children()) {
5193
301
            const auto &childNodeChildren = childNode->GP()->children();
5194
301
            if (childNodeChildren.size() >= 1 &&
5195
121
                ci_equal(childNode->GP()->value(), WKTConstants::GEOIDMODEL)) {
5196
70
                auto &propsModel = buildProperties(childNode);
5197
70
                const auto dummyCRS =
5198
70
                    VerticalCRS::create(PropertyMap(), vdatum, datumEnsemble,
5199
70
                                        NN_NO_CHECK(verticalCS));
5200
70
                const auto model(Transformation::create(
5201
70
                    propsModel, dummyCRS, dummyCRS, nullptr,
5202
70
                    OperationMethod::create(
5203
70
                        PropertyMap(), std::vector<OperationParameterNNPtr>()),
5204
70
                    {}, {}));
5205
70
                arrayModels->add(model);
5206
70
            }
5207
301
        }
5208
45
        props.set("GEOID_MODEL", arrayModels);
5209
45
    }
5210
5211
617
    auto crs = nn_static_pointer_cast<CRS>(VerticalCRS::create(
5212
617
        props, vdatum, datumEnsemble, NN_NO_CHECK(verticalCS)));
5213
5214
617
    if (!isNull(vdatumNode)) {
5215
512
        auto &extensionNode = vdatumNode->lookForChild(WKTConstants::EXTENSION);
5216
512
        const auto &extensionChildren = extensionNode->GP()->children();
5217
512
        if (extensionChildren.size() == 2) {
5218
7
            if (ci_equal(stripQuotes(extensionChildren[0]), "PROJ4_GRIDS")) {
5219
6
                const auto gridName(stripQuotes(extensionChildren[1]));
5220
                // This is the expansion of EPSG:5703 by old GDAL versions.
5221
                // See
5222
                // 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
5223
                // It is unlikely that the user really explicitly wants this.
5224
6
                if (gridName != "g2003conus.gtx,g2003alaska.gtx,"
5225
6
                                "g2003h01.gtx,g2003p01.gtx" &&
5226
6
                    gridName != "g2012a_conus.gtx,g2012a_alaska.gtx,"
5227
6
                                "g2012a_guam.gtx,g2012a_hawaii.gtx,"
5228
6
                                "g2012a_puertorico.gtx,g2012a_samoa.gtx") {
5229
6
                    auto geogCRS =
5230
6
                        geogCRSOfCompoundCRS_ &&
5231
0
                                geogCRSOfCompoundCRS_->primeMeridian()
5232
0
                                        ->longitude()
5233
0
                                        .getSIValue() == 0 &&
5234
0
                                geogCRSOfCompoundCRS_->coordinateSystem()
5235
0
                                        ->axisList()[0]
5236
0
                                        ->unit() == UnitOfMeasure::DEGREE
5237
6
                            ? geogCRSOfCompoundCRS_->promoteTo3D(std::string(),
5238
0
                                                                 dbContext_)
5239
6
                            : GeographicCRS::EPSG_4979;
5240
5241
6
                    auto sourceTransformationCRS =
5242
6
                        createBoundCRSSourceTransformationCRS(
5243
6
                            crs.as_nullable(), geogCRS.as_nullable());
5244
6
                    auto transformation = Transformation::
5245
6
                        createGravityRelatedHeightToGeographic3D(
5246
6
                            PropertyMap().set(
5247
6
                                IdentifiedObject::NAME_KEY,
5248
6
                                sourceTransformationCRS->nameStr() + " to " +
5249
6
                                    geogCRS->nameStr() + " ellipsoidal height"),
5250
6
                            sourceTransformationCRS, geogCRS, nullptr, gridName,
5251
6
                            std::vector<PositionalAccuracyNNPtr>());
5252
6
                    return nn_static_pointer_cast<CRS>(
5253
6
                        BoundCRS::create(crs, geogCRS, transformation));
5254
6
                }
5255
6
            }
5256
7
        }
5257
512
    }
5258
5259
611
    return crs;
5260
617
}
5261
5262
// ---------------------------------------------------------------------------
5263
5264
DerivedVerticalCRSNNPtr
5265
0
WKTParser::Private::buildDerivedVerticalCRS(const WKTNodeNNPtr &node) {
5266
0
    const auto *nodeP = node->GP();
5267
0
    auto &baseVertCRSNode = nodeP->lookForChild(WKTConstants::BASEVERTCRS);
5268
    // given the constraints enforced on calling code path
5269
0
    assert(!isNull(baseVertCRSNode));
5270
5271
0
    auto baseVertCRS_tmp = buildVerticalCRS(baseVertCRSNode);
5272
0
    auto baseVertCRS = NN_NO_CHECK(baseVertCRS_tmp->extractVerticalCRS());
5273
5274
0
    auto &derivingConversionNode =
5275
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5276
0
    if (isNull(derivingConversionNode)) {
5277
0
        ThrowMissing(WKTConstants::DERIVINGCONVERSION);
5278
0
    }
5279
0
    auto derivingConversion = buildConversion(
5280
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
5281
5282
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5283
0
    if (isNull(csNode)) {
5284
0
        ThrowMissing(WKTConstants::CS_);
5285
0
    }
5286
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5287
5288
0
    auto verticalCS = nn_dynamic_pointer_cast<VerticalCS>(cs);
5289
0
    if (!verticalCS) {
5290
0
        throw ParsingException(
5291
0
            concat("vertical CS expected, but found ", cs->getWKT2Type(true)));
5292
0
    }
5293
5294
0
    return DerivedVerticalCRS::create(buildProperties(node), baseVertCRS,
5295
0
                                      derivingConversion,
5296
0
                                      NN_NO_CHECK(verticalCS));
5297
0
}
5298
5299
// ---------------------------------------------------------------------------
5300
5301
259
CRSNNPtr WKTParser::Private::buildCompoundCRS(const WKTNodeNNPtr &node) {
5302
259
    std::vector<CRSNNPtr> components;
5303
259
    bool bFirstNode = true;
5304
1.94k
    for (const auto &child : node->GP()->children()) {
5305
1.94k
        auto crs = buildCRS(child);
5306
1.94k
        if (crs) {
5307
629
            if (bFirstNode) {
5308
200
                geogCRSOfCompoundCRS_ = crs->extractGeographicCRS();
5309
200
                bFirstNode = false;
5310
200
            }
5311
629
            components.push_back(NN_NO_CHECK(crs));
5312
629
        }
5313
1.94k
    }
5314
5315
259
    if (ci_equal(node->GP()->value(), WKTConstants::COMPD_CS)) {
5316
157
        return CompoundCRS::createLax(buildProperties(node), components,
5317
157
                                      dbContext_);
5318
157
    } else {
5319
102
        return CompoundCRS::create(buildProperties(node), components);
5320
102
    }
5321
259
}
5322
5323
// ---------------------------------------------------------------------------
5324
5325
static TransformationNNPtr buildTransformationForBoundCRS(
5326
    DatabaseContextPtr &dbContext,
5327
    const util::PropertyMap &abridgedNodeProperties,
5328
    const util::PropertyMap &methodNodeProperties, const CRSNNPtr &sourceCRS,
5329
    const CRSNNPtr &targetCRS, std::vector<OperationParameterNNPtr> &parameters,
5330
0
    std::vector<ParameterValueNNPtr> &values) {
5331
5332
0
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
5333
0
        dbContext, parameters, values);
5334
5335
0
    const auto sourceTransformationCRS(
5336
0
        createBoundCRSSourceTransformationCRS(sourceCRS, targetCRS));
5337
0
    auto transformation = Transformation::create(
5338
0
        abridgedNodeProperties, sourceTransformationCRS, targetCRS,
5339
0
        interpolationCRS, methodNodeProperties, parameters, values,
5340
0
        std::vector<PositionalAccuracyNNPtr>());
5341
5342
    // If the transformation is a "Geographic3D to GravityRelatedHeight" one,
5343
    // then the sourceCRS is expected to be a GeographicCRS and the target a
5344
    // VerticalCRS. Due to how things work in a BoundCRS, we have the opposite,
5345
    // so use our "GravityRelatedHeight to Geographic3D" method instead.
5346
0
    if (Transformation::isGeographic3DToGravityRelatedHeight(
5347
0
            transformation->method(), true) &&
5348
0
        dynamic_cast<VerticalCRS *>(sourceTransformationCRS.get()) &&
5349
0
        dynamic_cast<GeographicCRS *>(targetCRS.get())) {
5350
0
        auto fileParameter = transformation->parameterValue(
5351
0
            EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
5352
0
            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
5353
0
        if (fileParameter &&
5354
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
5355
0
            const auto &filename = fileParameter->valueFile();
5356
5357
0
            transformation =
5358
0
                Transformation::createGravityRelatedHeightToGeographic3D(
5359
0
                    abridgedNodeProperties, sourceTransformationCRS, targetCRS,
5360
0
                    interpolationCRS, filename,
5361
0
                    std::vector<PositionalAccuracyNNPtr>());
5362
0
        }
5363
0
    }
5364
0
    return transformation;
5365
0
}
5366
5367
// ---------------------------------------------------------------------------
5368
5369
15
BoundCRSNNPtr WKTParser::Private::buildBoundCRS(const WKTNodeNNPtr &node) {
5370
15
    const auto *nodeP = node->GP();
5371
15
    auto &abridgedNode =
5372
15
        nodeP->lookForChild(WKTConstants::ABRIDGEDTRANSFORMATION);
5373
15
    if (isNull(abridgedNode)) {
5374
8
        ThrowNotEnoughChildren(WKTConstants::ABRIDGEDTRANSFORMATION);
5375
8
    }
5376
5377
7
    auto &methodNode = abridgedNode->GP()->lookForChild(WKTConstants::METHOD);
5378
7
    if (isNull(methodNode)) {
5379
7
        ThrowMissing(WKTConstants::METHOD);
5380
7
    }
5381
0
    if (methodNode->GP()->childrenSize() == 0) {
5382
0
        ThrowNotEnoughChildren(WKTConstants::METHOD);
5383
0
    }
5384
5385
0
    auto &sourceCRSNode = nodeP->lookForChild(WKTConstants::SOURCECRS);
5386
0
    const auto &sourceCRSNodeChildren = sourceCRSNode->GP()->children();
5387
0
    if (sourceCRSNodeChildren.size() != 1) {
5388
0
        ThrowNotEnoughChildren(WKTConstants::SOURCECRS);
5389
0
    }
5390
0
    auto sourceCRS = buildCRS(sourceCRSNodeChildren[0]);
5391
0
    if (!sourceCRS) {
5392
0
        throw ParsingException("Invalid content in SOURCECRS node");
5393
0
    }
5394
5395
0
    auto &targetCRSNode = nodeP->lookForChild(WKTConstants::TARGETCRS);
5396
0
    const auto &targetCRSNodeChildren = targetCRSNode->GP()->children();
5397
0
    if (targetCRSNodeChildren.size() != 1) {
5398
0
        ThrowNotEnoughChildren(WKTConstants::TARGETCRS);
5399
0
    }
5400
0
    auto targetCRS = buildCRS(targetCRSNodeChildren[0]);
5401
0
    if (!targetCRS) {
5402
0
        throw ParsingException("Invalid content in TARGETCRS node");
5403
0
    }
5404
5405
0
    std::vector<OperationParameterNNPtr> parameters;
5406
0
    std::vector<ParameterValueNNPtr> values;
5407
0
    const auto &defaultLinearUnit = UnitOfMeasure::NONE;
5408
0
    const auto &defaultAngularUnit = UnitOfMeasure::NONE;
5409
0
    consumeParameters(abridgedNode, true, parameters, values, defaultLinearUnit,
5410
0
                      defaultAngularUnit);
5411
5412
0
    const auto nnSourceCRS = NN_NO_CHECK(sourceCRS);
5413
0
    const auto nnTargetCRS = NN_NO_CHECK(targetCRS);
5414
0
    const auto transformation = buildTransformationForBoundCRS(
5415
0
        dbContext_, buildProperties(abridgedNode), buildProperties(methodNode),
5416
0
        nnSourceCRS, nnTargetCRS, parameters, values);
5417
5418
0
    return BoundCRS::create(buildProperties(node, false, false),
5419
0
                            NN_NO_CHECK(sourceCRS), NN_NO_CHECK(targetCRS),
5420
0
                            transformation);
5421
0
}
5422
5423
// ---------------------------------------------------------------------------
5424
5425
TemporalCSNNPtr
5426
9
WKTParser::Private::buildTemporalCS(const WKTNodeNNPtr &parentNode) {
5427
5428
9
    auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
5429
9
    if (isNull(csNode) &&
5430
7
        !ci_equal(parentNode->GP()->value(), WKTConstants::BASETIMECRS)) {
5431
7
        ThrowMissing(WKTConstants::CS_);
5432
7
    }
5433
2
    auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
5434
2
    auto temporalCS = nn_dynamic_pointer_cast<TemporalCS>(cs);
5435
2
    if (!temporalCS) {
5436
0
        ThrowNotExpectedCSType(TemporalCS::WKT2_2015_TYPE);
5437
0
    }
5438
2
    return NN_NO_CHECK(temporalCS);
5439
2
}
5440
5441
// ---------------------------------------------------------------------------
5442
5443
TemporalCRSNNPtr
5444
16
WKTParser::Private::buildTemporalCRS(const WKTNodeNNPtr &node) {
5445
16
    auto &datumNode =
5446
16
        node->GP()->lookForChild(WKTConstants::TDATUM, WKTConstants::TIMEDATUM);
5447
16
    if (isNull(datumNode)) {
5448
7
        throw ParsingException("Missing TDATUM / TIMEDATUM node");
5449
7
    }
5450
5451
9
    return TemporalCRS::create(buildProperties(node),
5452
9
                               buildTemporalDatum(datumNode),
5453
9
                               buildTemporalCS(node));
5454
16
}
5455
5456
// ---------------------------------------------------------------------------
5457
5458
DerivedTemporalCRSNNPtr
5459
3
WKTParser::Private::buildDerivedTemporalCRS(const WKTNodeNNPtr &node) {
5460
3
    const auto *nodeP = node->GP();
5461
3
    auto &baseCRSNode = nodeP->lookForChild(WKTConstants::BASETIMECRS);
5462
    // given the constraints enforced on calling code path
5463
3
    assert(!isNull(baseCRSNode));
5464
5465
3
    auto &derivingConversionNode =
5466
3
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5467
3
    if (isNull(derivingConversionNode)) {
5468
3
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5469
3
    }
5470
5471
0
    return DerivedTemporalCRS::create(
5472
0
        buildProperties(node), buildTemporalCRS(baseCRSNode),
5473
0
        buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
5474
0
                        UnitOfMeasure::NONE),
5475
0
        buildTemporalCS(node));
5476
3
}
5477
5478
// ---------------------------------------------------------------------------
5479
5480
EngineeringCRSNNPtr
5481
5
WKTParser::Private::buildEngineeringCRS(const WKTNodeNNPtr &node) {
5482
5
    const auto *nodeP = node->GP();
5483
5
    auto &datumNode = nodeP->lookForChild(WKTConstants::EDATUM,
5484
5
                                          WKTConstants::ENGINEERINGDATUM);
5485
5
    if (isNull(datumNode)) {
5486
1
        throw ParsingException("Missing EDATUM / ENGINEERINGDATUM node");
5487
1
    }
5488
5489
4
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5490
4
    if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::BASEENGCRS)) {
5491
4
        ThrowMissing(WKTConstants::CS_);
5492
4
    }
5493
5494
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5495
0
    return EngineeringCRS::create(buildProperties(node),
5496
0
                                  buildEngineeringDatum(datumNode), cs);
5497
4
}
5498
5499
// ---------------------------------------------------------------------------
5500
5501
EngineeringCRSNNPtr
5502
315
WKTParser::Private::buildEngineeringCRSFromLocalCS(const WKTNodeNNPtr &node) {
5503
315
    auto &datumNode = node->GP()->lookForChild(WKTConstants::LOCAL_DATUM);
5504
315
    auto cs = buildCS(null_node, node, UnitOfMeasure::NONE);
5505
315
    auto datum = EngineeringDatum::create(
5506
315
        !isNull(datumNode)
5507
315
            ? buildProperties(datumNode)
5508
315
            :
5509
            // In theory OGC 01-009 mandates LOCAL_DATUM, but GDAL
5510
            // has a tradition of emitting just LOCAL_CS["foo"]
5511
315
            []() {
5512
233
                PropertyMap map;
5513
233
                map.set(IdentifiedObject::NAME_KEY, UNKNOWN_ENGINEERING_DATUM);
5514
233
                return map;
5515
233
            }());
5516
315
    return EngineeringCRS::create(buildProperties(node), datum, cs);
5517
315
}
5518
5519
// ---------------------------------------------------------------------------
5520
5521
DerivedEngineeringCRSNNPtr
5522
0
WKTParser::Private::buildDerivedEngineeringCRS(const WKTNodeNNPtr &node) {
5523
0
    const auto *nodeP = node->GP();
5524
0
    auto &baseEngCRSNode = nodeP->lookForChild(WKTConstants::BASEENGCRS);
5525
    // given the constraints enforced on calling code path
5526
0
    assert(!isNull(baseEngCRSNode));
5527
5528
0
    auto baseEngCRS = buildEngineeringCRS(baseEngCRSNode);
5529
5530
0
    auto &derivingConversionNode =
5531
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5532
0
    if (isNull(derivingConversionNode)) {
5533
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5534
0
    }
5535
0
    auto derivingConversion = buildConversion(
5536
0
        derivingConversionNode, UnitOfMeasure::NONE, UnitOfMeasure::NONE);
5537
5538
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5539
0
    if (isNull(csNode)) {
5540
0
        ThrowMissing(WKTConstants::CS_);
5541
0
    }
5542
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5543
5544
0
    return DerivedEngineeringCRS::create(buildProperties(node), baseEngCRS,
5545
0
                                         derivingConversion, cs);
5546
0
}
5547
5548
// ---------------------------------------------------------------------------
5549
5550
ParametricCSNNPtr
5551
0
WKTParser::Private::buildParametricCS(const WKTNodeNNPtr &parentNode) {
5552
5553
0
    auto &csNode = parentNode->GP()->lookForChild(WKTConstants::CS_);
5554
0
    if (isNull(csNode) &&
5555
0
        !ci_equal(parentNode->GP()->value(), WKTConstants::BASEPARAMCRS)) {
5556
0
        ThrowMissing(WKTConstants::CS_);
5557
0
    }
5558
0
    auto cs = buildCS(csNode, parentNode, UnitOfMeasure::NONE);
5559
0
    auto parametricCS = nn_dynamic_pointer_cast<ParametricCS>(cs);
5560
0
    if (!parametricCS) {
5561
0
        ThrowNotExpectedCSType(ParametricCS::WKT2_TYPE);
5562
0
    }
5563
0
    return NN_NO_CHECK(parametricCS);
5564
0
}
5565
5566
// ---------------------------------------------------------------------------
5567
5568
ParametricCRSNNPtr
5569
9
WKTParser::Private::buildParametricCRS(const WKTNodeNNPtr &node) {
5570
9
    auto &datumNode = node->GP()->lookForChild(WKTConstants::PDATUM,
5571
9
                                               WKTConstants::PARAMETRICDATUM);
5572
9
    if (isNull(datumNode)) {
5573
9
        throw ParsingException("Missing PDATUM / PARAMETRICDATUM node");
5574
9
    }
5575
5576
0
    return ParametricCRS::create(buildProperties(node),
5577
0
                                 buildParametricDatum(datumNode),
5578
0
                                 buildParametricCS(node));
5579
9
}
5580
5581
// ---------------------------------------------------------------------------
5582
5583
DerivedParametricCRSNNPtr
5584
3
WKTParser::Private::buildDerivedParametricCRS(const WKTNodeNNPtr &node) {
5585
3
    const auto *nodeP = node->GP();
5586
3
    auto &baseParamCRSNode = nodeP->lookForChild(WKTConstants::BASEPARAMCRS);
5587
    // given the constraints enforced on calling code path
5588
3
    assert(!isNull(baseParamCRSNode));
5589
5590
3
    auto &derivingConversionNode =
5591
3
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5592
3
    if (isNull(derivingConversionNode)) {
5593
3
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5594
3
    }
5595
5596
0
    return DerivedParametricCRS::create(
5597
0
        buildProperties(node), buildParametricCRS(baseParamCRSNode),
5598
0
        buildConversion(derivingConversionNode, UnitOfMeasure::NONE,
5599
0
                        UnitOfMeasure::NONE),
5600
0
        buildParametricCS(node));
5601
3
}
5602
5603
// ---------------------------------------------------------------------------
5604
5605
DerivedProjectedCRSNNPtr
5606
3
WKTParser::Private::buildDerivedProjectedCRS(const WKTNodeNNPtr &node) {
5607
3
    const auto *nodeP = node->GP();
5608
3
    auto &baseProjCRSNode = nodeP->lookForChild(WKTConstants::BASEPROJCRS);
5609
3
    if (isNull(baseProjCRSNode)) {
5610
3
        ThrowNotEnoughChildren(WKTConstants::BASEPROJCRS);
5611
3
    }
5612
0
    auto baseProjCRS = buildProjectedCRS(baseProjCRSNode);
5613
5614
0
    auto &conversionNode =
5615
0
        nodeP->lookForChild(WKTConstants::DERIVINGCONVERSION);
5616
0
    if (isNull(conversionNode)) {
5617
0
        ThrowNotEnoughChildren(WKTConstants::DERIVINGCONVERSION);
5618
0
    }
5619
5620
0
    auto linearUnit = buildUnitInSubNode(node);
5621
0
    const auto &angularUnit =
5622
0
        baseProjCRS->baseCRS()->coordinateSystem()->axisList()[0]->unit();
5623
5624
0
    auto conversion = buildConversion(conversionNode, linearUnit, angularUnit);
5625
5626
0
    auto &csNode = nodeP->lookForChild(WKTConstants::CS_);
5627
0
    if (isNull(csNode) && !ci_equal(nodeP->value(), WKTConstants::PROJCS)) {
5628
0
        ThrowMissing(WKTConstants::CS_);
5629
0
    }
5630
0
    auto cs = buildCS(csNode, node, UnitOfMeasure::NONE);
5631
5632
0
    if (cs->axisList().size() == 3 &&
5633
0
        baseProjCRS->coordinateSystem()->axisList().size() == 2) {
5634
0
        baseProjCRS = NN_NO_CHECK(util::nn_dynamic_pointer_cast<ProjectedCRS>(
5635
0
            baseProjCRS->promoteTo3D(std::string(), dbContext_)));
5636
0
    }
5637
5638
0
    return DerivedProjectedCRS::create(buildProperties(node), baseProjCRS,
5639
0
                                       conversion, cs);
5640
0
}
5641
5642
// ---------------------------------------------------------------------------
5643
5644
CoordinateMetadataNNPtr
5645
7
WKTParser::Private::buildCoordinateMetadata(const WKTNodeNNPtr &node) {
5646
7
    const auto *nodeP = node->GP();
5647
5648
7
    const auto &l_children = nodeP->children();
5649
7
    if (l_children.empty()) {
5650
0
        ThrowNotEnoughChildren(WKTConstants::COORDINATEMETADATA);
5651
0
    }
5652
5653
7
    auto crs = buildCRS(l_children[0]);
5654
7
    if (!crs) {
5655
3
        throw ParsingException("Invalid content in CRS node");
5656
3
    }
5657
5658
4
    auto &epochNode = nodeP->lookForChild(WKTConstants::EPOCH);
5659
4
    if (!isNull(epochNode)) {
5660
0
        const auto &epochChildren = epochNode->GP()->children();
5661
0
        if (epochChildren.empty()) {
5662
0
            ThrowMissing(WKTConstants::EPOCH);
5663
0
        }
5664
0
        double coordinateEpoch;
5665
0
        try {
5666
0
            coordinateEpoch = asDouble(epochChildren[0]);
5667
0
        } catch (const std::exception &) {
5668
0
            throw ParsingException("Invalid EPOCH node");
5669
0
        }
5670
0
        return CoordinateMetadata::create(NN_NO_CHECK(crs), coordinateEpoch,
5671
0
                                          dbContext_);
5672
0
    }
5673
5674
4
    return CoordinateMetadata::create(NN_NO_CHECK(crs));
5675
4
}
5676
5677
// ---------------------------------------------------------------------------
5678
5679
4.21k
static bool isGeodeticCRS(const std::string &name) {
5680
4.21k
    return ci_equal(name, WKTConstants::GEODCRS) ||       // WKT2
5681
4.21k
           ci_equal(name, WKTConstants::GEODETICCRS) ||   // WKT2
5682
4.21k
           ci_equal(name, WKTConstants::GEOGCRS) ||       // WKT2 2019
5683
4.21k
           ci_equal(name, WKTConstants::GEOGRAPHICCRS) || // WKT2 2019
5684
4.21k
           ci_equal(name, WKTConstants::GEOGCS) ||        // WKT1
5685
3.81k
           ci_equal(name, WKTConstants::GEOCCS);          // WKT1
5686
4.21k
}
5687
5688
// ---------------------------------------------------------------------------
5689
5690
4.21k
CRSPtr WKTParser::Private::buildCRS(const WKTNodeNNPtr &node) {
5691
4.21k
    const auto *nodeP = node->GP();
5692
4.21k
    const std::string &name(nodeP->value());
5693
5694
4.21k
    const auto applyHorizontalBoundCRSParams = [&](const CRSNNPtr &crs) {
5695
722
        if (!toWGS84Parameters_.empty()) {
5696
0
            auto ret = BoundCRS::createFromTOWGS84(crs, toWGS84Parameters_);
5697
0
            toWGS84Parameters_.clear();
5698
0
            return util::nn_static_pointer_cast<CRS>(ret);
5699
722
        } else if (!datumPROJ4Grids_.empty()) {
5700
0
            auto ret = BoundCRS::createFromNadgrids(crs, datumPROJ4Grids_);
5701
0
            datumPROJ4Grids_.clear();
5702
0
            return util::nn_static_pointer_cast<CRS>(ret);
5703
0
        }
5704
722
        return crs;
5705
722
    };
5706
5707
4.21k
    if (isGeodeticCRS(name)) {
5708
497
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEGEOGCRS,
5709
497
                                        WKTConstants::BASEGEODCRS))) {
5710
0
            return util::nn_static_pointer_cast<CRS>(
5711
0
                applyHorizontalBoundCRSParams(buildDerivedGeodeticCRS(node)));
5712
497
        } else {
5713
497
            return util::nn_static_pointer_cast<CRS>(
5714
497
                applyHorizontalBoundCRSParams(buildGeodeticCRS(node)));
5715
497
        }
5716
497
    }
5717
5718
3.71k
    if (ci_equal(name, WKTConstants::PROJCS) ||
5719
2.97k
        ci_equal(name, WKTConstants::PROJCRS) ||
5720
2.96k
        ci_equal(name, WKTConstants::PROJECTEDCRS)) {
5721
        // Get the EXTENSION "PROJ4" node before attempting to call
5722
        // buildProjectedCRS() since formulations of WKT1_GDAL from GDAL 2.x
5723
        // with the netCDF driver and the lack the required UNIT[] node
5724
750
        std::string projString = getExtensionProj4(nodeP);
5725
750
        if (!projString.empty() &&
5726
93
            (starts_with(projString, "+proj=ob_tran +o_proj=longlat") ||
5727
60
             starts_with(projString, "+proj=ob_tran +o_proj=lonlat") ||
5728
60
             starts_with(projString, "+proj=ob_tran +o_proj=latlong") ||
5729
66
             starts_with(projString, "+proj=ob_tran +o_proj=latlon"))) {
5730
            // Those are not a projected CRS, but a DerivedGeographic one...
5731
66
            if (projString.find(" +type=crs") == std::string::npos) {
5732
66
                projString += " +type=crs";
5733
66
            }
5734
66
            try {
5735
66
                auto projObj =
5736
66
                    PROJStringParser().createFromPROJString(projString);
5737
66
                auto crs = nn_dynamic_pointer_cast<CRS>(projObj);
5738
66
                if (crs) {
5739
25
                    return util::nn_static_pointer_cast<CRS>(
5740
25
                        applyHorizontalBoundCRSParams(NN_NO_CHECK(crs)));
5741
25
                }
5742
66
            } catch (const io::ParsingException &) {
5743
33
            }
5744
66
        }
5745
722
        return util::nn_static_pointer_cast<CRS>(
5746
722
            applyHorizontalBoundCRSParams(buildProjectedCRS(node)));
5747
750
    }
5748
5749
2.96k
    if (ci_equal(name, WKTConstants::VERT_CS) ||
5750
2.95k
        ci_equal(name, WKTConstants::VERTCS) ||
5751
2.42k
        ci_equal(name, WKTConstants::VERTCRS) ||
5752
2.42k
        ci_equal(name, WKTConstants::VERTICALCRS)) {
5753
546
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEVERTCRS))) {
5754
0
            return util::nn_static_pointer_cast<CRS>(
5755
0
                buildDerivedVerticalCRS(node));
5756
546
        } else {
5757
546
            return util::nn_static_pointer_cast<CRS>(buildVerticalCRS(node));
5758
546
        }
5759
546
    }
5760
5761
2.42k
    if (ci_equal(name, WKTConstants::COMPD_CS) ||
5762
2.16k
        ci_equal(name, WKTConstants::COMPOUNDCRS)) {
5763
259
        return util::nn_static_pointer_cast<CRS>(buildCompoundCRS(node));
5764
259
    }
5765
5766
2.16k
    if (ci_equal(name, WKTConstants::BOUNDCRS)) {
5767
15
        return util::nn_static_pointer_cast<CRS>(buildBoundCRS(node));
5768
15
    }
5769
5770
2.14k
    if (ci_equal(name, WKTConstants::TIMECRS)) {
5771
19
        if (!isNull(nodeP->lookForChild(WKTConstants::BASETIMECRS))) {
5772
3
            return util::nn_static_pointer_cast<CRS>(
5773
3
                buildDerivedTemporalCRS(node));
5774
16
        } else {
5775
16
            return util::nn_static_pointer_cast<CRS>(buildTemporalCRS(node));
5776
16
        }
5777
19
    }
5778
5779
2.13k
    if (ci_equal(name, WKTConstants::DERIVEDPROJCRS)) {
5780
3
        return util::nn_static_pointer_cast<CRS>(
5781
3
            buildDerivedProjectedCRS(node));
5782
3
    }
5783
5784
2.12k
    if (ci_equal(name, WKTConstants::ENGCRS) ||
5785
2.12k
        ci_equal(name, WKTConstants::ENGINEERINGCRS)) {
5786
5
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEENGCRS))) {
5787
0
            return util::nn_static_pointer_cast<CRS>(
5788
0
                buildDerivedEngineeringCRS(node));
5789
5
        } else {
5790
5
            return util::nn_static_pointer_cast<CRS>(buildEngineeringCRS(node));
5791
5
        }
5792
5
    }
5793
5794
2.12k
    if (ci_equal(name, WKTConstants::LOCAL_CS)) {
5795
315
        return util::nn_static_pointer_cast<CRS>(
5796
315
            buildEngineeringCRSFromLocalCS(node));
5797
315
    }
5798
5799
1.80k
    if (ci_equal(name, WKTConstants::PARAMETRICCRS)) {
5800
12
        if (!isNull(nodeP->lookForChild(WKTConstants::BASEPARAMCRS))) {
5801
3
            return util::nn_static_pointer_cast<CRS>(
5802
3
                buildDerivedParametricCRS(node));
5803
9
        } else {
5804
9
            return util::nn_static_pointer_cast<CRS>(buildParametricCRS(node));
5805
9
        }
5806
12
    }
5807
5808
1.79k
    return nullptr;
5809
1.80k
}
5810
5811
// ---------------------------------------------------------------------------
5812
5813
2.09k
BaseObjectNNPtr WKTParser::Private::build(const WKTNodeNNPtr &node) {
5814
2.09k
    const auto *nodeP = node->GP();
5815
2.09k
    const std::string &name(nodeP->value());
5816
5817
2.09k
    auto crs = buildCRS(node);
5818
2.09k
    if (crs) {
5819
649
        return util::nn_static_pointer_cast<BaseObject>(NN_NO_CHECK(crs));
5820
649
    }
5821
5822
    // Datum handled by caller code WKTParser::createFromWKT()
5823
5824
1.44k
    if (ci_equal(name, WKTConstants::ENSEMBLE)) {
5825
7
        return util::nn_static_pointer_cast<BaseObject>(buildDatumEnsemble(
5826
7
            node, PrimeMeridian::GREENWICH,
5827
7
            !isNull(nodeP->lookForChild(WKTConstants::ELLIPSOID))));
5828
7
    }
5829
5830
1.44k
    if (ci_equal(name, WKTConstants::VDATUM) ||
5831
576
        ci_equal(name, WKTConstants::VERT_DATUM) ||
5832
574
        ci_equal(name, WKTConstants::VERTICALDATUM) ||
5833
574
        ci_equal(name, WKTConstants::VRF)) {
5834
153
        return util::nn_static_pointer_cast<BaseObject>(
5835
153
            buildVerticalReferenceFrame(node, null_node));
5836
153
    }
5837
5838
1.28k
    if (ci_equal(name, WKTConstants::TDATUM) ||
5839
410
        ci_equal(name, WKTConstants::TIMEDATUM)) {
5840
14
        return util::nn_static_pointer_cast<BaseObject>(
5841
14
            buildTemporalDatum(node));
5842
14
    }
5843
5844
1.27k
    if (ci_equal(name, WKTConstants::EDATUM) ||
5845
403
        ci_equal(name, WKTConstants::ENGINEERINGDATUM)) {
5846
6
        return util::nn_static_pointer_cast<BaseObject>(
5847
6
            buildEngineeringDatum(node));
5848
6
    }
5849
5850
1.26k
    if (ci_equal(name, WKTConstants::PDATUM) ||
5851
395
        ci_equal(name, WKTConstants::PARAMETRICDATUM)) {
5852
11
        return util::nn_static_pointer_cast<BaseObject>(
5853
11
            buildParametricDatum(node));
5854
11
    }
5855
5856
1.25k
    if (ci_equal(name, WKTConstants::ELLIPSOID) ||
5857
392
        ci_equal(name, WKTConstants::SPHEROID)) {
5858
4
        return util::nn_static_pointer_cast<BaseObject>(buildEllipsoid(node));
5859
4
    }
5860
5861
1.25k
    if (ci_equal(name, WKTConstants::COORDINATEOPERATION)) {
5862
16
        auto transf = buildCoordinateOperation(node);
5863
5864
16
        const char *prefixes[] = {
5865
16
            "PROJ-based operation method: ",
5866
16
            "PROJ-based operation method (approximate): "};
5867
16
        for (const char *prefix : prefixes) {
5868
0
            if (starts_with(transf->method()->nameStr(), prefix)) {
5869
0
                auto projString =
5870
0
                    transf->method()->nameStr().substr(strlen(prefix));
5871
0
                return util::nn_static_pointer_cast<BaseObject>(
5872
0
                    PROJBasedOperation::create(
5873
0
                        PropertyMap(), projString, transf->sourceCRS(),
5874
0
                        transf->targetCRS(),
5875
0
                        transf->coordinateOperationAccuracies()));
5876
0
            }
5877
0
        }
5878
5879
16
        return util::nn_static_pointer_cast<BaseObject>(transf);
5880
16
    }
5881
5882
1.23k
    if (ci_equal(name, WKTConstants::CONVERSION)) {
5883
321
        auto conv =
5884
321
            buildConversion(node, UnitOfMeasure::METRE, UnitOfMeasure::DEGREE);
5885
5886
321
        if (starts_with(conv->method()->nameStr(),
5887
321
                        "PROJ-based operation method: ")) {
5888
2
            auto projString = conv->method()->nameStr().substr(
5889
2
                strlen("PROJ-based operation method: "));
5890
2
            return util::nn_static_pointer_cast<BaseObject>(
5891
2
                PROJBasedOperation::create(PropertyMap(), projString, nullptr,
5892
2
                                           nullptr, {}));
5893
2
        }
5894
5895
319
        return util::nn_static_pointer_cast<BaseObject>(conv);
5896
321
    }
5897
5898
916
    if (ci_equal(name, WKTConstants::CONCATENATEDOPERATION)) {
5899
6
        return util::nn_static_pointer_cast<BaseObject>(
5900
6
            buildConcatenatedOperation(node));
5901
6
    }
5902
5903
910
    if (ci_equal(name, WKTConstants::POINTMOTIONOPERATION)) {
5904
1
        return util::nn_static_pointer_cast<BaseObject>(
5905
1
            buildPointMotionOperation(node));
5906
1
    }
5907
5908
909
    if (ci_equal(name, WKTConstants::ID) ||
5909
30
        ci_equal(name, WKTConstants::AUTHORITY)) {
5910
30
        return util::nn_static_pointer_cast<BaseObject>(
5911
30
            NN_NO_CHECK(buildId(node, node, false, false)));
5912
30
    }
5913
5914
879
    if (ci_equal(name, WKTConstants::COORDINATEMETADATA)) {
5915
7
        return util::nn_static_pointer_cast<BaseObject>(
5916
7
            buildCoordinateMetadata(node));
5917
7
    }
5918
5919
872
    throw ParsingException(concat("unhandled keyword: ", name));
5920
879
}
5921
//! @endcond
5922
5923
// ---------------------------------------------------------------------------
5924
5925
//! @cond Doxygen_Suppress
5926
class JSONParser {
5927
    DatabaseContextPtr dbContext_{};
5928
    std::string deformationModelName_{};
5929
5930
    static std::string getString(const json &j, const char *key);
5931
    static json getObject(const json &j, const char *key);
5932
    static json getArray(const json &j, const char *key);
5933
    static int getInteger(const json &j, const char *key);
5934
    static double getNumber(const json &j, const char *key);
5935
    static UnitOfMeasure getUnit(const json &j, const char *key);
5936
    static std::string getName(const json &j);
5937
    static std::string getType(const json &j);
5938
    static Length getLength(const json &j, const char *key);
5939
    static Measure getMeasure(const json &j);
5940
5941
    IdentifierNNPtr buildId(const json &parentJ, const json &j,
5942
                            bool removeInverseOf);
5943
    static ObjectDomainPtr buildObjectDomain(const json &j);
5944
    PropertyMap buildProperties(const json &j, bool removeInverseOf = false,
5945
                                bool nameRequired = true);
5946
5947
    GeographicCRSNNPtr buildGeographicCRS(const json &j);
5948
    GeodeticCRSNNPtr buildGeodeticCRS(const json &j);
5949
    ProjectedCRSNNPtr buildProjectedCRS(const json &j);
5950
    ConversionNNPtr buildConversion(const json &j);
5951
    DatumEnsembleNNPtr buildDatumEnsemble(const json &j);
5952
    GeodeticReferenceFrameNNPtr buildGeodeticReferenceFrame(const json &j);
5953
    VerticalReferenceFrameNNPtr buildVerticalReferenceFrame(const json &j);
5954
    DynamicGeodeticReferenceFrameNNPtr
5955
    buildDynamicGeodeticReferenceFrame(const json &j);
5956
    DynamicVerticalReferenceFrameNNPtr
5957
    buildDynamicVerticalReferenceFrame(const json &j);
5958
    EllipsoidNNPtr buildEllipsoid(const json &j);
5959
    PrimeMeridianNNPtr buildPrimeMeridian(const json &j);
5960
    CoordinateSystemNNPtr buildCS(const json &j);
5961
    MeridianNNPtr buildMeridian(const json &j);
5962
    CoordinateSystemAxisNNPtr buildAxis(const json &j);
5963
    VerticalCRSNNPtr buildVerticalCRS(const json &j);
5964
    CRSNNPtr buildCRS(const json &j);
5965
    CompoundCRSNNPtr buildCompoundCRS(const json &j);
5966
    BoundCRSNNPtr buildBoundCRS(const json &j);
5967
    TransformationNNPtr buildTransformation(const json &j);
5968
    PointMotionOperationNNPtr buildPointMotionOperation(const json &j);
5969
    ConcatenatedOperationNNPtr buildConcatenatedOperation(const json &j);
5970
    CoordinateMetadataNNPtr buildCoordinateMetadata(const json &j);
5971
5972
    void buildGeodeticDatumOrDatumEnsemble(const json &j,
5973
                                           GeodeticReferenceFramePtr &datum,
5974
                                           DatumEnsemblePtr &datumEnsemble);
5975
5976
11
    static util::optional<std::string> getAnchor(const json &j) {
5977
11
        util::optional<std::string> anchor;
5978
11
        if (j.contains("anchor")) {
5979
0
            anchor = getString(j, "anchor");
5980
0
        }
5981
11
        return anchor;
5982
11
    }
5983
5984
0
    static util::optional<common::Measure> getAnchorEpoch(const json &j) {
5985
0
        if (j.contains("anchor_epoch")) {
5986
0
            return util::optional<common::Measure>(common::Measure(
5987
0
                getNumber(j, "anchor_epoch"), common::UnitOfMeasure::YEAR));
5988
0
        }
5989
0
        return util::optional<common::Measure>();
5990
0
    }
5991
5992
17
    EngineeringDatumNNPtr buildEngineeringDatum(const json &j) {
5993
17
        return EngineeringDatum::create(buildProperties(j), getAnchor(j));
5994
17
    }
5995
5996
0
    ParametricDatumNNPtr buildParametricDatum(const json &j) {
5997
0
        return ParametricDatum::create(buildProperties(j), getAnchor(j));
5998
0
    }
5999
6000
0
    TemporalDatumNNPtr buildTemporalDatum(const json &j) {
6001
0
        auto calendar = getString(j, "calendar");
6002
0
        auto origin = DateTime::create(j.contains("time_origin")
6003
0
                                           ? getString(j, "time_origin")
6004
0
                                           : std::string());
6005
0
        return TemporalDatum::create(buildProperties(j), origin, calendar);
6006
0
    }
6007
6008
    template <class TargetCRS, class DatumBuilderType,
6009
              class CSClass = CoordinateSystem>
6010
    util::nn<std::shared_ptr<TargetCRS>> buildCRS(const json &j,
6011
0
                                                  DatumBuilderType f) {
6012
0
        auto datum = (this->*f)(getObject(j, "datum"));
6013
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
6014
0
        auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
6015
0
        if (!csCast) {
6016
0
            throw ParsingException("coordinate_system not of expected type");
6017
0
        }
6018
0
        return TargetCRS::create(buildProperties(j), datum,
6019
0
                                 NN_NO_CHECK(csCast));
6020
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&))
6021
6022
    template <class TargetCRS, class BaseCRS, class CSClass = CoordinateSystem>
6023
0
    util::nn<std::shared_ptr<TargetCRS>> buildDerivedCRS(const json &j) {
6024
0
        auto baseCRSObj = create(getObject(j, "base_crs"));
6025
0
        auto baseCRS = util::nn_dynamic_pointer_cast<BaseCRS>(baseCRSObj);
6026
0
        if (!baseCRS) {
6027
0
            throw ParsingException("base_crs not of expected type");
6028
0
        }
6029
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
6030
0
        auto csCast = util::nn_dynamic_pointer_cast<CSClass>(cs);
6031
0
        if (!csCast) {
6032
0
            throw ParsingException("coordinate_system not of expected type");
6033
0
        }
6034
0
        auto conv = buildConversion(getObject(j, "conversion"));
6035
0
        return TargetCRS::create(buildProperties(j), NN_NO_CHECK(baseCRS), conv,
6036
0
                                 NN_NO_CHECK(csCast));
6037
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&)
6038
6039
  public:
6040
86
    JSONParser() = default;
6041
6042
86
    JSONParser &attachDatabaseContext(const DatabaseContextPtr &dbContext) {
6043
86
        dbContext_ = dbContext;
6044
86
        return *this;
6045
86
    }
6046
6047
    BaseObjectNNPtr create(const json &j);
6048
};
6049
6050
// ---------------------------------------------------------------------------
6051
6052
121
std::string JSONParser::getString(const json &j, const char *key) {
6053
121
    if (!j.contains(key)) {
6054
27
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6055
27
    }
6056
94
    auto v = j[key];
6057
94
    if (!v.is_string()) {
6058
32
        throw ParsingException(std::string("The value of \"") + key +
6059
32
                               "\" should be a string");
6060
32
    }
6061
62
    return v.get<std::string>();
6062
94
}
6063
6064
// ---------------------------------------------------------------------------
6065
6066
19
json JSONParser::getObject(const json &j, const char *key) {
6067
19
    if (!j.contains(key)) {
6068
2
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6069
2
    }
6070
17
    auto v = j[key];
6071
17
    if (!v.is_object()) {
6072
0
        throw ParsingException(std::string("The value of \"") + key +
6073
0
                               "\" should be a object");
6074
0
    }
6075
17
    return v.get<json>();
6076
17
}
6077
6078
// ---------------------------------------------------------------------------
6079
6080
0
json JSONParser::getArray(const json &j, const char *key) {
6081
0
    if (!j.contains(key)) {
6082
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6083
0
    }
6084
0
    auto v = j[key];
6085
0
    if (!v.is_array()) {
6086
0
        throw ParsingException(std::string("The value of \"") + key +
6087
0
                               "\" should be a array");
6088
0
    }
6089
0
    return v.get<json>();
6090
0
}
6091
6092
// ---------------------------------------------------------------------------
6093
6094
0
int JSONParser::getInteger(const json &j, const char *key) {
6095
0
    if (!j.contains(key)) {
6096
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6097
0
    }
6098
0
    auto v = j[key];
6099
0
    if (!v.is_number()) {
6100
0
        throw ParsingException(std::string("The value of \"") + key +
6101
0
                               "\" should be an integer");
6102
0
    }
6103
0
    const double dbl = v.get<double>();
6104
0
    if (!(dbl >= std::numeric_limits<int>::min() &&
6105
0
          dbl <= std::numeric_limits<int>::max() &&
6106
0
          static_cast<int>(dbl) == dbl)) {
6107
0
        throw ParsingException(std::string("The value of \"") + key +
6108
0
                               "\" should be an integer");
6109
0
    }
6110
0
    return static_cast<int>(dbl);
6111
0
}
6112
6113
// ---------------------------------------------------------------------------
6114
6115
0
double JSONParser::getNumber(const json &j, const char *key) {
6116
0
    if (!j.contains(key)) {
6117
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6118
0
    }
6119
0
    auto v = j[key];
6120
0
    if (!v.is_number()) {
6121
0
        throw ParsingException(std::string("The value of \"") + key +
6122
0
                               "\" should be a number");
6123
0
    }
6124
0
    return v.get<double>();
6125
0
}
6126
6127
// ---------------------------------------------------------------------------
6128
6129
0
UnitOfMeasure JSONParser::getUnit(const json &j, const char *key) {
6130
0
    if (!j.contains(key)) {
6131
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6132
0
    }
6133
0
    auto v = j[key];
6134
0
    if (v.is_string()) {
6135
0
        auto vStr = v.get<std::string>();
6136
0
        for (const auto &unit : {UnitOfMeasure::METRE, UnitOfMeasure::DEGREE,
6137
0
                                 UnitOfMeasure::SCALE_UNITY}) {
6138
0
            if (vStr == unit.name())
6139
0
                return unit;
6140
0
        }
6141
0
        throw ParsingException("Unknown unit name: " + vStr);
6142
0
    }
6143
0
    if (!v.is_object()) {
6144
0
        throw ParsingException(std::string("The value of \"") + key +
6145
0
                               "\" should be a string or an object");
6146
0
    }
6147
0
    auto typeStr = getType(v);
6148
0
    UnitOfMeasure::Type type = UnitOfMeasure::Type::UNKNOWN;
6149
0
    if (typeStr == "LinearUnit") {
6150
0
        type = UnitOfMeasure::Type::LINEAR;
6151
0
    } else if (typeStr == "AngularUnit") {
6152
0
        type = UnitOfMeasure::Type::ANGULAR;
6153
0
    } else if (typeStr == "ScaleUnit") {
6154
0
        type = UnitOfMeasure::Type::SCALE;
6155
0
    } else if (typeStr == "TimeUnit") {
6156
0
        type = UnitOfMeasure::Type::TIME;
6157
0
    } else if (typeStr == "ParametricUnit") {
6158
0
        type = UnitOfMeasure::Type::PARAMETRIC;
6159
0
    } else if (typeStr == "Unit") {
6160
0
        type = UnitOfMeasure::Type::UNKNOWN;
6161
0
    } else {
6162
0
        throw ParsingException("Unsupported value of \"type\"");
6163
0
    }
6164
0
    auto nameStr = getName(v);
6165
0
    auto convFactor = getNumber(v, "conversion_factor");
6166
0
    std::string authorityStr;
6167
0
    std::string codeStr;
6168
0
    if (v.contains("authority") && v.contains("code")) {
6169
0
        authorityStr = getString(v, "authority");
6170
0
        auto code = v["code"];
6171
0
        if (code.is_string()) {
6172
0
            codeStr = code.get<std::string>();
6173
0
        } else if (code.is_number_integer()) {
6174
0
            codeStr = internal::toString(code.get<int>());
6175
0
        } else {
6176
0
            throw ParsingException("Unexpected type for value of \"code\"");
6177
0
        }
6178
0
    }
6179
0
    return UnitOfMeasure(nameStr, convFactor, type, authorityStr, codeStr);
6180
0
}
6181
6182
// ---------------------------------------------------------------------------
6183
6184
18
std::string JSONParser::getName(const json &j) { return getString(j, "name"); }
6185
6186
// ---------------------------------------------------------------------------
6187
6188
0
std::string JSONParser::getType(const json &j) { return getString(j, "type"); }
6189
6190
// ---------------------------------------------------------------------------
6191
6192
0
Length JSONParser::getLength(const json &j, const char *key) {
6193
0
    if (!j.contains(key)) {
6194
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
6195
0
    }
6196
0
    auto v = j[key];
6197
0
    if (v.is_number()) {
6198
0
        return Length(v.get<double>(), UnitOfMeasure::METRE);
6199
0
    }
6200
0
    if (v.is_object()) {
6201
0
        return Length(getMeasure(v));
6202
0
    }
6203
0
    throw ParsingException(std::string("The value of \"") + key +
6204
0
                           "\" should be a number or an object");
6205
0
}
6206
6207
// ---------------------------------------------------------------------------
6208
6209
0
Measure JSONParser::getMeasure(const json &j) {
6210
0
    return Measure(getNumber(j, "value"), getUnit(j, "unit"));
6211
0
}
6212
6213
// ---------------------------------------------------------------------------
6214
6215
11
ObjectDomainPtr JSONParser::buildObjectDomain(const json &j) {
6216
11
    optional<std::string> scope;
6217
11
    if (j.contains("scope")) {
6218
0
        scope = getString(j, "scope");
6219
0
    }
6220
11
    std::string area;
6221
11
    if (j.contains("area")) {
6222
0
        area = getString(j, "area");
6223
0
    }
6224
11
    std::vector<GeographicExtentNNPtr> geogExtent;
6225
11
    if (j.contains("bbox")) {
6226
0
        auto bbox = getObject(j, "bbox");
6227
0
        double south = getNumber(bbox, "south_latitude");
6228
0
        double west = getNumber(bbox, "west_longitude");
6229
0
        double north = getNumber(bbox, "north_latitude");
6230
0
        double east = getNumber(bbox, "east_longitude");
6231
0
        try {
6232
0
            geogExtent.emplace_back(
6233
0
                GeographicBoundingBox::create(west, south, east, north));
6234
0
        } catch (const std::exception &e) {
6235
0
            throw ParsingException(
6236
0
                std::string("Invalid bbox node: ").append(e.what()));
6237
0
        }
6238
0
    }
6239
6240
11
    std::vector<VerticalExtentNNPtr> verticalExtent;
6241
11
    if (j.contains("vertical_extent")) {
6242
0
        const auto vertical_extent = getObject(j, "vertical_extent");
6243
0
        const auto min = getNumber(vertical_extent, "minimum");
6244
0
        const auto max = getNumber(vertical_extent, "maximum");
6245
0
        const auto unit = vertical_extent.contains("unit")
6246
0
                              ? getUnit(vertical_extent, "unit")
6247
0
                              : UnitOfMeasure::METRE;
6248
0
        verticalExtent.emplace_back(VerticalExtent::create(
6249
0
            min, max, util::nn_make_shared<UnitOfMeasure>(unit)));
6250
0
    }
6251
6252
11
    std::vector<TemporalExtentNNPtr> temporalExtent;
6253
11
    if (j.contains("temporal_extent")) {
6254
0
        const auto temporal_extent = getObject(j, "temporal_extent");
6255
0
        const auto start = getString(temporal_extent, "start");
6256
0
        const auto end = getString(temporal_extent, "end");
6257
0
        temporalExtent.emplace_back(TemporalExtent::create(start, end));
6258
0
    }
6259
6260
11
    if (scope.has_value() || !area.empty() || !geogExtent.empty() ||
6261
11
        !verticalExtent.empty() || !temporalExtent.empty()) {
6262
0
        util::optional<std::string> description;
6263
0
        if (!area.empty())
6264
0
            description = area;
6265
0
        ExtentPtr extent;
6266
0
        if (description.has_value() || !geogExtent.empty() ||
6267
0
            !verticalExtent.empty() || !temporalExtent.empty()) {
6268
0
            extent = Extent::create(description, geogExtent, verticalExtent,
6269
0
                                    temporalExtent)
6270
0
                         .as_nullable();
6271
0
        }
6272
0
        return ObjectDomain::create(scope, extent).as_nullable();
6273
0
    }
6274
11
    return nullptr;
6275
11
}
6276
6277
// ---------------------------------------------------------------------------
6278
6279
IdentifierNNPtr JSONParser::buildId(const json &parentJ, const json &j,
6280
0
                                    bool removeInverseOf) {
6281
6282
0
    PropertyMap propertiesId;
6283
0
    auto codeSpace(getString(j, "authority"));
6284
0
    if (removeInverseOf && starts_with(codeSpace, "INVERSE(") &&
6285
0
        codeSpace.back() == ')') {
6286
0
        codeSpace = codeSpace.substr(strlen("INVERSE("));
6287
0
        codeSpace.resize(codeSpace.size() - 1);
6288
0
    }
6289
6290
0
    std::string version;
6291
0
    if (j.contains("version")) {
6292
0
        auto versionJ = j["version"];
6293
0
        if (versionJ.is_string()) {
6294
0
            version = versionJ.get<std::string>();
6295
0
        } else if (versionJ.is_number()) {
6296
0
            const double dblVersion = versionJ.get<double>();
6297
0
            if (dblVersion >= std::numeric_limits<int>::min() &&
6298
0
                dblVersion <= std::numeric_limits<int>::max() &&
6299
0
                static_cast<int>(dblVersion) == dblVersion) {
6300
0
                version = internal::toString(static_cast<int>(dblVersion));
6301
0
            } else {
6302
0
                version = internal::toString(dblVersion, /*precision=*/15);
6303
0
            }
6304
0
        } else {
6305
0
            throw ParsingException("Unexpected type for value of \"version\"");
6306
0
        }
6307
0
    }
6308
6309
    // IAU + 2015 -> IAU_2015
6310
0
    if (dbContext_ && !version.empty()) {
6311
0
        std::string codeSpaceOut;
6312
0
        if (dbContext_->getVersionedAuthority(codeSpace, version,
6313
0
                                              codeSpaceOut)) {
6314
0
            codeSpace = std::move(codeSpaceOut);
6315
0
            version.clear();
6316
0
        }
6317
0
    }
6318
6319
0
    propertiesId.set(metadata::Identifier::CODESPACE_KEY, codeSpace);
6320
0
    propertiesId.set(metadata::Identifier::AUTHORITY_KEY, codeSpace);
6321
0
    if (!j.contains("code")) {
6322
0
        throw ParsingException("Missing \"code\" key");
6323
0
    }
6324
0
    std::string code;
6325
0
    auto codeJ = j["code"];
6326
0
    if (codeJ.is_string()) {
6327
0
        code = codeJ.get<std::string>();
6328
0
    } else if (codeJ.is_number_integer()) {
6329
0
        code = internal::toString(codeJ.get<int>());
6330
0
    } else {
6331
0
        throw ParsingException("Unexpected type for value of \"code\"");
6332
0
    }
6333
6334
    // Prior to PROJ 9.5, when synthetizing an ID for a CONVERSION UTM Zone
6335
    // south, we generated a wrong value. Auto-fix that
6336
0
    if (parentJ.contains("type") && getType(parentJ) == "Conversion" &&
6337
0
        codeSpace == Identifier::EPSG && parentJ.contains("name")) {
6338
0
        const auto parentNodeName(getName(parentJ));
6339
0
        if (ci_starts_with(parentNodeName, "UTM Zone ") &&
6340
0
            parentNodeName.find('S') != std::string::npos) {
6341
0
            const int nZone =
6342
0
                atoi(parentNodeName.c_str() + strlen("UTM Zone "));
6343
0
            if (nZone >= 1 && nZone <= 60) {
6344
0
                code = internal::toString(16100 + nZone);
6345
0
            }
6346
0
        }
6347
0
    }
6348
6349
0
    if (!version.empty()) {
6350
0
        propertiesId.set(Identifier::VERSION_KEY, version);
6351
0
    }
6352
6353
0
    if (j.contains("authority_citation")) {
6354
0
        propertiesId.set(Identifier::AUTHORITY_KEY,
6355
0
                         getString(j, "authority_citation"));
6356
0
    }
6357
6358
0
    if (j.contains("uri")) {
6359
0
        propertiesId.set(Identifier::URI_KEY, getString(j, "uri"));
6360
0
    }
6361
6362
0
    return Identifier::create(code, propertiesId);
6363
0
}
6364
6365
// ---------------------------------------------------------------------------
6366
6367
PropertyMap JSONParser::buildProperties(const json &j, bool removeInverseOf,
6368
18
                                        bool nameRequired) {
6369
18
    PropertyMap map;
6370
6371
18
    if (j.contains("name") || nameRequired) {
6372
18
        std::string name(getName(j));
6373
18
        if (removeInverseOf && starts_with(name, "Inverse of ")) {
6374
0
            name = name.substr(strlen("Inverse of "));
6375
0
        }
6376
18
        map.set(IdentifiedObject::NAME_KEY, name);
6377
18
    }
6378
6379
18
    if (j.contains("ids")) {
6380
0
        auto idsJ = getArray(j, "ids");
6381
0
        auto identifiers = ArrayOfBaseObject::create();
6382
0
        for (const auto &idJ : idsJ) {
6383
0
            if (!idJ.is_object()) {
6384
0
                throw ParsingException(
6385
0
                    "Unexpected type for value of \"ids\" child");
6386
0
            }
6387
0
            identifiers->add(buildId(j, idJ, removeInverseOf));
6388
0
        }
6389
0
        map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
6390
18
    } else if (j.contains("id")) {
6391
0
        auto idJ = getObject(j, "id");
6392
0
        auto identifiers = ArrayOfBaseObject::create();
6393
0
        identifiers->add(buildId(j, idJ, removeInverseOf));
6394
0
        map.set(IdentifiedObject::IDENTIFIERS_KEY, identifiers);
6395
0
    }
6396
6397
18
    if (j.contains("remarks")) {
6398
0
        map.set(IdentifiedObject::REMARKS_KEY, getString(j, "remarks"));
6399
0
    }
6400
6401
18
    if (j.contains("usages")) {
6402
0
        ArrayOfBaseObjectNNPtr array = ArrayOfBaseObject::create();
6403
0
        auto usages = j["usages"];
6404
0
        if (!usages.is_array()) {
6405
0
            throw ParsingException("Unexpected type for value of \"usages\"");
6406
0
        }
6407
0
        for (const auto &usage : usages) {
6408
0
            if (!usage.is_object()) {
6409
0
                throw ParsingException(
6410
0
                    "Unexpected type for value of \"usages\" child");
6411
0
            }
6412
0
            auto objectDomain = buildObjectDomain(usage);
6413
0
            if (!objectDomain) {
6414
0
                throw ParsingException("missing children in \"usages\" child");
6415
0
            }
6416
0
            array->add(NN_NO_CHECK(objectDomain));
6417
0
        }
6418
0
        if (!array->empty()) {
6419
0
            map.set(ObjectUsage::OBJECT_DOMAIN_KEY, array);
6420
0
        }
6421
18
    } else {
6422
18
        auto objectDomain = buildObjectDomain(j);
6423
18
        if (objectDomain) {
6424
0
            map.set(ObjectUsage::OBJECT_DOMAIN_KEY, NN_NO_CHECK(objectDomain));
6425
0
        }
6426
18
    }
6427
6428
18
    return map;
6429
18
}
6430
6431
// ---------------------------------------------------------------------------
6432
6433
BaseObjectNNPtr JSONParser::create(const json &j)
6434
6435
103
{
6436
103
    if (!j.is_object()) {
6437
0
        throw ParsingException("JSON object expected");
6438
0
    }
6439
103
    auto type = getString(j, "type");
6440
103
    if (type == "GeographicCRS") {
6441
18
        return buildGeographicCRS(j);
6442
18
    }
6443
85
    if (type == "GeodeticCRS") {
6444
0
        return buildGeodeticCRS(j);
6445
0
    }
6446
85
    if (type == "ProjectedCRS") {
6447
0
        return buildProjectedCRS(j);
6448
0
    }
6449
85
    if (type == "VerticalCRS") {
6450
0
        return buildVerticalCRS(j);
6451
0
    }
6452
85
    if (type == "CompoundCRS") {
6453
0
        return buildCompoundCRS(j);
6454
0
    }
6455
85
    if (type == "BoundCRS") {
6456
0
        return buildBoundCRS(j);
6457
0
    }
6458
85
    if (type == "EngineeringCRS") {
6459
0
        return buildCRS<EngineeringCRS>(j, &JSONParser::buildEngineeringDatum);
6460
0
    }
6461
85
    if (type == "ParametricCRS") {
6462
0
        return buildCRS<ParametricCRS,
6463
0
                        decltype(&JSONParser::buildParametricDatum),
6464
0
                        ParametricCS>(j, &JSONParser::buildParametricDatum);
6465
0
    }
6466
85
    if (type == "TemporalCRS") {
6467
0
        return buildCRS<TemporalCRS, decltype(&JSONParser::buildTemporalDatum),
6468
0
                        TemporalCS>(j, &JSONParser::buildTemporalDatum);
6469
0
    }
6470
85
    if (type == "DerivedGeodeticCRS") {
6471
0
        auto baseCRSObj = create(getObject(j, "base_crs"));
6472
0
        auto baseCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(baseCRSObj);
6473
0
        if (!baseCRS) {
6474
0
            throw ParsingException("base_crs not of expected type");
6475
0
        }
6476
0
        auto cs = buildCS(getObject(j, "coordinate_system"));
6477
0
        auto conv = buildConversion(getObject(j, "conversion"));
6478
0
        auto csCartesian = util::nn_dynamic_pointer_cast<CartesianCS>(cs);
6479
0
        if (csCartesian)
6480
0
            return DerivedGeodeticCRS::create(buildProperties(j),
6481
0
                                              NN_NO_CHECK(baseCRS), conv,
6482
0
                                              NN_NO_CHECK(csCartesian));
6483
0
        auto csSpherical = util::nn_dynamic_pointer_cast<SphericalCS>(cs);
6484
0
        if (csSpherical)
6485
0
            return DerivedGeodeticCRS::create(buildProperties(j),
6486
0
                                              NN_NO_CHECK(baseCRS), conv,
6487
0
                                              NN_NO_CHECK(csSpherical));
6488
0
        throw ParsingException("coordinate_system not of expected type");
6489
0
    }
6490
85
    if (type == "DerivedGeographicCRS") {
6491
0
        return buildDerivedCRS<DerivedGeographicCRS, GeodeticCRS,
6492
0
                               EllipsoidalCS>(j);
6493
0
    }
6494
85
    if (type == "DerivedProjectedCRS") {
6495
0
        return buildDerivedCRS<DerivedProjectedCRS, ProjectedCRS>(j);
6496
0
    }
6497
85
    if (type == "DerivedVerticalCRS") {
6498
0
        return buildDerivedCRS<DerivedVerticalCRS, VerticalCRS, VerticalCS>(j);
6499
0
    }
6500
85
    if (type == "DerivedEngineeringCRS") {
6501
0
        return buildDerivedCRS<DerivedEngineeringCRS, EngineeringCRS>(j);
6502
0
    }
6503
85
    if (type == "DerivedParametricCRS") {
6504
0
        return buildDerivedCRS<DerivedParametricCRS, ParametricCRS,
6505
0
                               ParametricCS>(j);
6506
0
    }
6507
85
    if (type == "DerivedTemporalCRS") {
6508
0
        return buildDerivedCRS<DerivedTemporalCRS, TemporalCRS, TemporalCS>(j);
6509
0
    }
6510
85
    if (type == "DatumEnsemble") {
6511
0
        return buildDatumEnsemble(j);
6512
0
    }
6513
85
    if (type == "GeodeticReferenceFrame") {
6514
0
        return buildGeodeticReferenceFrame(j);
6515
0
    }
6516
85
    if (type == "VerticalReferenceFrame") {
6517
1
        return buildVerticalReferenceFrame(j);
6518
1
    }
6519
84
    if (type == "DynamicGeodeticReferenceFrame") {
6520
0
        return buildDynamicGeodeticReferenceFrame(j);
6521
0
    }
6522
84
    if (type == "DynamicVerticalReferenceFrame") {
6523
0
        return buildDynamicVerticalReferenceFrame(j);
6524
0
    }
6525
84
    if (type == "EngineeringDatum") {
6526
17
        return buildEngineeringDatum(j);
6527
17
    }
6528
67
    if (type == "ParametricDatum") {
6529
0
        return buildParametricDatum(j);
6530
0
    }
6531
67
    if (type == "TemporalDatum") {
6532
0
        return buildTemporalDatum(j);
6533
0
    }
6534
67
    if (type == "Ellipsoid") {
6535
0
        return buildEllipsoid(j);
6536
0
    }
6537
67
    if (type == "PrimeMeridian") {
6538
0
        return buildPrimeMeridian(j);
6539
0
    }
6540
67
    if (type == "CoordinateSystem") {
6541
0
        return buildCS(j);
6542
0
    }
6543
67
    if (type == "Conversion") {
6544
0
        return buildConversion(j);
6545
0
    }
6546
67
    if (type == "Transformation") {
6547
0
        return buildTransformation(j);
6548
0
    }
6549
67
    if (type == "PointMotionOperation") {
6550
0
        return buildPointMotionOperation(j);
6551
0
    }
6552
67
    if (type == "ConcatenatedOperation") {
6553
0
        return buildConcatenatedOperation(j);
6554
0
    }
6555
67
    if (type == "CoordinateMetadata") {
6556
1
        return buildCoordinateMetadata(j);
6557
1
    }
6558
66
    if (type == "Axis") {
6559
0
        return buildAxis(j);
6560
0
    }
6561
66
    throw ParsingException("Unsupported value of \"type\"");
6562
66
}
6563
6564
// ---------------------------------------------------------------------------
6565
6566
void JSONParser::buildGeodeticDatumOrDatumEnsemble(
6567
    const json &j, GeodeticReferenceFramePtr &datum,
6568
18
    DatumEnsemblePtr &datumEnsemble) {
6569
18
    if (j.contains("datum")) {
6570
17
        auto datumJ = getObject(j, "datum");
6571
6572
17
        if (j.contains("deformation_models")) {
6573
0
            auto deformationModelsJ = getArray(j, "deformation_models");
6574
0
            if (!deformationModelsJ.empty()) {
6575
0
                const auto &deformationModelJ = deformationModelsJ[0];
6576
0
                deformationModelName_ = getString(deformationModelJ, "name");
6577
                // We can handle only one for now
6578
0
            }
6579
0
        }
6580
6581
17
        datum = util::nn_dynamic_pointer_cast<GeodeticReferenceFrame>(
6582
17
            create(datumJ));
6583
17
        if (!datum) {
6584
0
            throw ParsingException("datum of wrong type");
6585
0
        }
6586
6587
17
        deformationModelName_.clear();
6588
17
    } else {
6589
1
        datumEnsemble =
6590
1
            buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
6591
1
    }
6592
18
}
6593
6594
// ---------------------------------------------------------------------------
6595
6596
18
GeographicCRSNNPtr JSONParser::buildGeographicCRS(const json &j) {
6597
18
    GeodeticReferenceFramePtr datum;
6598
18
    DatumEnsemblePtr datumEnsemble;
6599
18
    buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
6600
18
    auto csJ = getObject(j, "coordinate_system");
6601
18
    auto ellipsoidalCS =
6602
18
        util::nn_dynamic_pointer_cast<EllipsoidalCS>(buildCS(csJ));
6603
18
    if (!ellipsoidalCS) {
6604
0
        throw ParsingException("expected an ellipsoidal CS");
6605
0
    }
6606
18
    return GeographicCRS::create(buildProperties(j), datum, datumEnsemble,
6607
18
                                 NN_NO_CHECK(ellipsoidalCS));
6608
18
}
6609
6610
// ---------------------------------------------------------------------------
6611
6612
0
GeodeticCRSNNPtr JSONParser::buildGeodeticCRS(const json &j) {
6613
0
    GeodeticReferenceFramePtr datum;
6614
0
    DatumEnsemblePtr datumEnsemble;
6615
0
    buildGeodeticDatumOrDatumEnsemble(j, datum, datumEnsemble);
6616
0
    auto csJ = getObject(j, "coordinate_system");
6617
0
    auto cs = buildCS(csJ);
6618
0
    auto props = buildProperties(j);
6619
0
    auto cartesianCS = nn_dynamic_pointer_cast<CartesianCS>(cs);
6620
0
    if (cartesianCS) {
6621
0
        if (cartesianCS->axisList().size() != 3) {
6622
0
            throw ParsingException(
6623
0
                "Cartesian CS for a GeodeticCRS should have 3 axis");
6624
0
        }
6625
0
        try {
6626
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
6627
0
                                       NN_NO_CHECK(cartesianCS));
6628
0
        } catch (const util::Exception &e) {
6629
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
6630
0
                                   e.what());
6631
0
        }
6632
0
    }
6633
6634
0
    auto sphericalCS = nn_dynamic_pointer_cast<SphericalCS>(cs);
6635
0
    if (sphericalCS) {
6636
0
        try {
6637
0
            return GeodeticCRS::create(props, datum, datumEnsemble,
6638
0
                                       NN_NO_CHECK(sphericalCS));
6639
0
        } catch (const util::Exception &e) {
6640
0
            throw ParsingException(std::string("buildGeodeticCRS: ") +
6641
0
                                   e.what());
6642
0
        }
6643
0
    }
6644
0
    throw ParsingException("expected a Cartesian or spherical CS");
6645
0
}
6646
6647
// ---------------------------------------------------------------------------
6648
6649
0
ProjectedCRSNNPtr JSONParser::buildProjectedCRS(const json &j) {
6650
0
    auto jBaseCRS = getObject(j, "base_crs");
6651
0
    auto jBaseCS = getObject(jBaseCRS, "coordinate_system");
6652
0
    auto baseCS = buildCS(jBaseCS);
6653
0
    auto baseCRS = dynamic_cast<EllipsoidalCS *>(baseCS.get()) != nullptr
6654
0
                       ? util::nn_static_pointer_cast<GeodeticCRS>(
6655
0
                             buildGeographicCRS(jBaseCRS))
6656
0
                       : buildGeodeticCRS(jBaseCRS);
6657
0
    auto csJ = getObject(j, "coordinate_system");
6658
0
    auto cartesianCS = util::nn_dynamic_pointer_cast<CartesianCS>(buildCS(csJ));
6659
0
    if (!cartesianCS) {
6660
0
        throw ParsingException("expected a Cartesian CS");
6661
0
    }
6662
0
    auto conv = buildConversion(getObject(j, "conversion"));
6663
0
    return ProjectedCRS::create(buildProperties(j), baseCRS, conv,
6664
0
                                NN_NO_CHECK(cartesianCS));
6665
0
}
6666
6667
// ---------------------------------------------------------------------------
6668
6669
0
VerticalCRSNNPtr JSONParser::buildVerticalCRS(const json &j) {
6670
0
    VerticalReferenceFramePtr datum;
6671
0
    DatumEnsemblePtr datumEnsemble;
6672
0
    if (j.contains("datum")) {
6673
0
        auto datumJ = getObject(j, "datum");
6674
6675
0
        if (j.contains("deformation_models")) {
6676
0
            auto deformationModelsJ = getArray(j, "deformation_models");
6677
0
            if (!deformationModelsJ.empty()) {
6678
0
                const auto &deformationModelJ = deformationModelsJ[0];
6679
0
                deformationModelName_ = getString(deformationModelJ, "name");
6680
                // We can handle only one for now
6681
0
            }
6682
0
        }
6683
6684
0
        datum = util::nn_dynamic_pointer_cast<VerticalReferenceFrame>(
6685
0
            create(datumJ));
6686
0
        if (!datum) {
6687
0
            throw ParsingException("datum of wrong type");
6688
0
        }
6689
0
    } else {
6690
0
        datumEnsemble =
6691
0
            buildDatumEnsemble(getObject(j, "datum_ensemble")).as_nullable();
6692
0
    }
6693
0
    auto csJ = getObject(j, "coordinate_system");
6694
0
    auto verticalCS = util::nn_dynamic_pointer_cast<VerticalCS>(buildCS(csJ));
6695
0
    if (!verticalCS) {
6696
0
        throw ParsingException("expected a vertical CS");
6697
0
    }
6698
6699
0
    const auto buildGeoidModel = [this, &datum, &datumEnsemble,
6700
0
                                  &verticalCS](const json &geoidModelJ) {
6701
0
        auto propsModel = buildProperties(geoidModelJ);
6702
0
        const auto dummyCRS = VerticalCRS::create(
6703
0
            PropertyMap(), datum, datumEnsemble, NN_NO_CHECK(verticalCS));
6704
0
        CRSPtr interpolationCRS;
6705
0
        if (geoidModelJ.contains("interpolation_crs")) {
6706
0
            auto interpolationCRSJ =
6707
0
                getObject(geoidModelJ, "interpolation_crs");
6708
0
            interpolationCRS = buildCRS(interpolationCRSJ).as_nullable();
6709
0
        }
6710
0
        return Transformation::create(
6711
0
            propsModel, dummyCRS,
6712
0
            GeographicCRS::EPSG_4979, // arbitrarily chosen. Ignored,
6713
0
            interpolationCRS,
6714
0
            OperationMethod::create(PropertyMap(),
6715
0
                                    std::vector<OperationParameterNNPtr>()),
6716
0
            {}, {});
6717
0
    };
6718
6719
0
    auto props = buildProperties(j);
6720
0
    if (j.contains("geoid_model")) {
6721
0
        auto geoidModelJ = getObject(j, "geoid_model");
6722
0
        props.set("GEOID_MODEL", buildGeoidModel(geoidModelJ));
6723
0
    } else if (j.contains("geoid_models")) {
6724
0
        auto geoidModelsJ = getArray(j, "geoid_models");
6725
0
        auto geoidModels = ArrayOfBaseObject::create();
6726
0
        for (const auto &geoidModelJ : geoidModelsJ) {
6727
0
            geoidModels->add(buildGeoidModel(geoidModelJ));
6728
0
        }
6729
0
        props.set("GEOID_MODEL", geoidModels);
6730
0
    }
6731
6732
0
    return VerticalCRS::create(props, datum, datumEnsemble,
6733
0
                               NN_NO_CHECK(verticalCS));
6734
0
}
6735
6736
// ---------------------------------------------------------------------------
6737
6738
0
CRSNNPtr JSONParser::buildCRS(const json &j) {
6739
0
    auto crs = util::nn_dynamic_pointer_cast<CRS>(create(j));
6740
0
    if (crs) {
6741
0
        return NN_NO_CHECK(crs);
6742
0
    }
6743
0
    throw ParsingException("Object is not a CRS");
6744
0
}
6745
6746
// ---------------------------------------------------------------------------
6747
6748
0
CompoundCRSNNPtr JSONParser::buildCompoundCRS(const json &j) {
6749
0
    auto componentsJ = getArray(j, "components");
6750
0
    std::vector<CRSNNPtr> components;
6751
0
    for (const auto &componentJ : componentsJ) {
6752
0
        if (!componentJ.is_object()) {
6753
0
            throw ParsingException(
6754
0
                "Unexpected type for a \"components\" child");
6755
0
        }
6756
0
        components.push_back(buildCRS(componentJ));
6757
0
    }
6758
0
    return CompoundCRS::create(buildProperties(j), components);
6759
0
}
6760
6761
// ---------------------------------------------------------------------------
6762
6763
0
ConversionNNPtr JSONParser::buildConversion(const json &j) {
6764
0
    auto methodJ = getObject(j, "method");
6765
0
    auto convProps = buildProperties(j);
6766
0
    auto methodProps = buildProperties(methodJ);
6767
0
    if (!j.contains("parameters")) {
6768
0
        return Conversion::create(convProps, methodProps, {}, {});
6769
0
    }
6770
6771
0
    auto parametersJ = getArray(j, "parameters");
6772
0
    std::vector<OperationParameterNNPtr> parameters;
6773
0
    std::vector<ParameterValueNNPtr> values;
6774
0
    for (const auto &param : parametersJ) {
6775
0
        if (!param.is_object()) {
6776
0
            throw ParsingException(
6777
0
                "Unexpected type for a \"parameters\" child");
6778
0
        }
6779
0
        parameters.emplace_back(
6780
0
            OperationParameter::create(buildProperties(param)));
6781
0
        if (isIntegerParameter(parameters.back())) {
6782
0
            values.emplace_back(
6783
0
                ParameterValue::create(getInteger(param, "value")));
6784
0
        } else {
6785
0
            values.emplace_back(ParameterValue::create(getMeasure(param)));
6786
0
        }
6787
0
    }
6788
6789
0
    auto interpolationCRS = dealWithEPSGCodeForInterpolationCRSParameter(
6790
0
        dbContext_, parameters, values);
6791
6792
0
    std::string convName;
6793
0
    std::string methodName;
6794
0
    if (convProps.getStringValue(IdentifiedObject::NAME_KEY, convName) &&
6795
0
        methodProps.getStringValue(IdentifiedObject::NAME_KEY, methodName) &&
6796
0
        starts_with(convName, "Inverse of ") &&
6797
0
        starts_with(methodName, "Inverse of ")) {
6798
6799
0
        auto invConvProps = buildProperties(j, true);
6800
0
        auto invMethodProps = buildProperties(methodJ, true);
6801
0
        auto conv = NN_NO_CHECK(util::nn_dynamic_pointer_cast<Conversion>(
6802
0
            Conversion::create(invConvProps, invMethodProps, parameters, values)
6803
0
                ->inverse()));
6804
0
        if (interpolationCRS)
6805
0
            conv->setInterpolationCRS(interpolationCRS);
6806
0
        return conv;
6807
0
    }
6808
0
    auto conv = Conversion::create(convProps, methodProps, parameters, values);
6809
0
    if (interpolationCRS)
6810
0
        conv->setInterpolationCRS(interpolationCRS);
6811
0
    return conv;
6812
0
}
6813
6814
// ---------------------------------------------------------------------------
6815
6816
0
BoundCRSNNPtr JSONParser::buildBoundCRS(const json &j) {
6817
6818
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6819
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6820
0
    auto transformationJ = getObject(j, "transformation");
6821
0
    auto methodJ = getObject(transformationJ, "method");
6822
0
    auto parametersJ = getArray(transformationJ, "parameters");
6823
0
    std::vector<OperationParameterNNPtr> parameters;
6824
0
    std::vector<ParameterValueNNPtr> values;
6825
0
    for (const auto &param : parametersJ) {
6826
0
        if (!param.is_object()) {
6827
0
            throw ParsingException(
6828
0
                "Unexpected type for a \"parameters\" child");
6829
0
        }
6830
0
        parameters.emplace_back(
6831
0
            OperationParameter::create(buildProperties(param)));
6832
0
        if (param.contains("value")) {
6833
0
            auto v = param["value"];
6834
0
            if (v.is_string()) {
6835
0
                values.emplace_back(
6836
0
                    ParameterValue::createFilename(v.get<std::string>()));
6837
0
                continue;
6838
0
            }
6839
0
        }
6840
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6841
0
    }
6842
6843
0
    const auto transformation = [&]() {
6844
        // Unofficial extension / mostly for testing purposes.
6845
        // Allow to explicitly specify the source_crs of the transformation of
6846
        // the boundCRS if it is not the source_crs of the BoundCRS. Cf
6847
        // https://github.com/OSGeo/PROJ/issues/3428 use case
6848
0
        if (transformationJ.contains("source_crs")) {
6849
0
            auto sourceTransformationCRS =
6850
0
                buildCRS(getObject(transformationJ, "source_crs"));
6851
0
            auto interpolationCRS =
6852
0
                dealWithEPSGCodeForInterpolationCRSParameter(
6853
0
                    dbContext_, parameters, values);
6854
0
            return Transformation::create(
6855
0
                buildProperties(transformationJ), sourceTransformationCRS,
6856
0
                targetCRS, interpolationCRS, buildProperties(methodJ),
6857
0
                parameters, values, std::vector<PositionalAccuracyNNPtr>());
6858
0
        }
6859
6860
0
        return buildTransformationForBoundCRS(
6861
0
            dbContext_, buildProperties(transformationJ),
6862
0
            buildProperties(methodJ), sourceCRS, targetCRS, parameters, values);
6863
0
    }();
6864
6865
0
    return BoundCRS::create(buildProperties(j,
6866
0
                                            /* removeInverseOf= */ false,
6867
0
                                            /* nameRequired=*/false),
6868
0
                            sourceCRS, targetCRS, transformation);
6869
0
}
6870
6871
// ---------------------------------------------------------------------------
6872
6873
0
TransformationNNPtr JSONParser::buildTransformation(const json &j) {
6874
6875
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6876
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6877
0
    auto methodJ = getObject(j, "method");
6878
0
    auto parametersJ = getArray(j, "parameters");
6879
0
    std::vector<OperationParameterNNPtr> parameters;
6880
0
    std::vector<ParameterValueNNPtr> values;
6881
0
    for (const auto &param : parametersJ) {
6882
0
        if (!param.is_object()) {
6883
0
            throw ParsingException(
6884
0
                "Unexpected type for a \"parameters\" child");
6885
0
        }
6886
0
        parameters.emplace_back(
6887
0
            OperationParameter::create(buildProperties(param)));
6888
0
        if (param.contains("value")) {
6889
0
            auto v = param["value"];
6890
0
            if (v.is_string()) {
6891
0
                values.emplace_back(
6892
0
                    ParameterValue::createFilename(v.get<std::string>()));
6893
0
                continue;
6894
0
            }
6895
0
        }
6896
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6897
0
    }
6898
0
    CRSPtr interpolationCRS;
6899
0
    if (j.contains("interpolation_crs")) {
6900
0
        interpolationCRS =
6901
0
            buildCRS(getObject(j, "interpolation_crs")).as_nullable();
6902
0
    }
6903
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6904
0
    if (j.contains("accuracy")) {
6905
0
        accuracies.push_back(
6906
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6907
0
    }
6908
6909
0
    return Transformation::create(buildProperties(j), sourceCRS, targetCRS,
6910
0
                                  interpolationCRS, buildProperties(methodJ),
6911
0
                                  parameters, values, accuracies);
6912
0
}
6913
6914
// ---------------------------------------------------------------------------
6915
6916
0
PointMotionOperationNNPtr JSONParser::buildPointMotionOperation(const json &j) {
6917
6918
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6919
0
    auto methodJ = getObject(j, "method");
6920
0
    auto parametersJ = getArray(j, "parameters");
6921
0
    std::vector<OperationParameterNNPtr> parameters;
6922
0
    std::vector<ParameterValueNNPtr> values;
6923
0
    for (const auto &param : parametersJ) {
6924
0
        if (!param.is_object()) {
6925
0
            throw ParsingException(
6926
0
                "Unexpected type for a \"parameters\" child");
6927
0
        }
6928
0
        parameters.emplace_back(
6929
0
            OperationParameter::create(buildProperties(param)));
6930
0
        if (param.contains("value")) {
6931
0
            auto v = param["value"];
6932
0
            if (v.is_string()) {
6933
0
                values.emplace_back(
6934
0
                    ParameterValue::createFilename(v.get<std::string>()));
6935
0
                continue;
6936
0
            }
6937
0
        }
6938
0
        values.emplace_back(ParameterValue::create(getMeasure(param)));
6939
0
    }
6940
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6941
0
    if (j.contains("accuracy")) {
6942
0
        accuracies.push_back(
6943
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6944
0
    }
6945
6946
0
    return PointMotionOperation::create(buildProperties(j), sourceCRS,
6947
0
                                        buildProperties(methodJ), parameters,
6948
0
                                        values, accuracies);
6949
0
}
6950
6951
// ---------------------------------------------------------------------------
6952
6953
ConcatenatedOperationNNPtr
6954
0
JSONParser::buildConcatenatedOperation(const json &j) {
6955
6956
0
    auto sourceCRS = buildCRS(getObject(j, "source_crs"));
6957
0
    auto targetCRS = buildCRS(getObject(j, "target_crs"));
6958
0
    auto stepsJ = getArray(j, "steps");
6959
0
    std::vector<CoordinateOperationNNPtr> operations;
6960
0
    for (const auto &stepJ : stepsJ) {
6961
0
        if (!stepJ.is_object()) {
6962
0
            throw ParsingException("Unexpected type for a \"steps\" child");
6963
0
        }
6964
0
        auto op = nn_dynamic_pointer_cast<CoordinateOperation>(create(stepJ));
6965
0
        if (!op) {
6966
0
            throw ParsingException("Invalid content in a \"steps\" child");
6967
0
        }
6968
0
        operations.emplace_back(NN_NO_CHECK(op));
6969
0
    }
6970
6971
0
    ConcatenatedOperation::fixSteps(sourceCRS, targetCRS, operations,
6972
0
                                    dbContext_,
6973
0
                                    /* fixDirectionAllowed = */ true);
6974
6975
0
    std::vector<PositionalAccuracyNNPtr> accuracies;
6976
0
    if (j.contains("accuracy")) {
6977
0
        accuracies.push_back(
6978
0
            PositionalAccuracy::create(getString(j, "accuracy")));
6979
0
    }
6980
6981
0
    try {
6982
0
        return ConcatenatedOperation::create(buildProperties(j), operations,
6983
0
                                             accuracies);
6984
0
    } catch (const InvalidOperation &e) {
6985
0
        throw ParsingException(
6986
0
            std::string("Cannot build concatenated operation: ") + e.what());
6987
0
    }
6988
0
}
6989
6990
// ---------------------------------------------------------------------------
6991
6992
1
CoordinateMetadataNNPtr JSONParser::buildCoordinateMetadata(const json &j) {
6993
6994
1
    auto crs = buildCRS(getObject(j, "crs"));
6995
1
    if (j.contains("coordinateEpoch")) {
6996
0
        auto jCoordinateEpoch = j["coordinateEpoch"];
6997
0
        if (jCoordinateEpoch.is_number()) {
6998
0
            return CoordinateMetadata::create(
6999
0
                crs, jCoordinateEpoch.get<double>(), dbContext_);
7000
0
        }
7001
0
        throw ParsingException(
7002
0
            "Unexpected type for value of \"coordinateEpoch\"");
7003
0
    }
7004
1
    return CoordinateMetadata::create(crs);
7005
1
}
7006
7007
// ---------------------------------------------------------------------------
7008
7009
0
MeridianNNPtr JSONParser::buildMeridian(const json &j) {
7010
0
    if (!j.contains("longitude")) {
7011
0
        throw ParsingException("Missing \"longitude\" key");
7012
0
    }
7013
0
    auto longitude = j["longitude"];
7014
0
    if (longitude.is_number()) {
7015
0
        return Meridian::create(
7016
0
            Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
7017
0
    } else if (longitude.is_object()) {
7018
0
        return Meridian::create(Angle(getMeasure(longitude)));
7019
0
    }
7020
0
    throw ParsingException("Unexpected type for value of \"longitude\"");
7021
0
}
7022
7023
// ---------------------------------------------------------------------------
7024
7025
0
CoordinateSystemAxisNNPtr JSONParser::buildAxis(const json &j) {
7026
0
    auto dirString = getString(j, "direction");
7027
0
    auto abbreviation = getString(j, "abbreviation");
7028
0
    const UnitOfMeasure unit(
7029
0
        j.contains("unit")
7030
0
            ? getUnit(j, "unit")
7031
0
            : UnitOfMeasure(std::string(), 1.0, UnitOfMeasure::Type::NONE));
7032
0
    auto direction = AxisDirection::valueOf(dirString);
7033
0
    if (!direction) {
7034
0
        throw ParsingException(concat("unhandled axis direction: ", dirString));
7035
0
    }
7036
0
    auto meridian = j.contains("meridian")
7037
0
                        ? buildMeridian(getObject(j, "meridian")).as_nullable()
7038
0
                        : nullptr;
7039
7040
0
    util::optional<double> minVal;
7041
0
    if (j.contains("minimum_value")) {
7042
0
        minVal = getNumber(j, "minimum_value");
7043
0
    }
7044
7045
0
    util::optional<double> maxVal;
7046
0
    if (j.contains("maximum_value")) {
7047
0
        maxVal = getNumber(j, "maximum_value");
7048
0
    }
7049
7050
0
    util::optional<RangeMeaning> rangeMeaning;
7051
0
    if (j.contains("range_meaning")) {
7052
0
        const auto val = getString(j, "range_meaning");
7053
0
        const RangeMeaning *meaning = RangeMeaning::valueOf(val);
7054
0
        if (meaning == nullptr) {
7055
0
            throw ParsingException(
7056
0
                concat("buildAxis: invalid range_meaning value: ", val));
7057
0
        }
7058
0
        rangeMeaning = util::optional<RangeMeaning>(*meaning);
7059
0
    }
7060
7061
0
    return CoordinateSystemAxis::create(buildProperties(j), abbreviation,
7062
0
                                        *direction, unit, minVal, maxVal,
7063
0
                                        rangeMeaning, meridian);
7064
0
}
7065
7066
// ---------------------------------------------------------------------------
7067
7068
0
CoordinateSystemNNPtr JSONParser::buildCS(const json &j) {
7069
0
    auto subtype = getString(j, "subtype");
7070
0
    if (!j.contains("axis")) {
7071
0
        throw ParsingException("Missing \"axis\" key");
7072
0
    }
7073
0
    auto jAxisList = j["axis"];
7074
0
    if (!jAxisList.is_array()) {
7075
0
        throw ParsingException("Unexpected type for value of \"axis\"");
7076
0
    }
7077
0
    std::vector<CoordinateSystemAxisNNPtr> axisList;
7078
0
    for (const auto &axis : jAxisList) {
7079
0
        if (!axis.is_object()) {
7080
0
            throw ParsingException(
7081
0
                "Unexpected type for value of a \"axis\" member");
7082
0
        }
7083
0
        axisList.emplace_back(buildAxis(axis));
7084
0
    }
7085
0
    const PropertyMap &csMap = emptyPropertyMap;
7086
0
    const auto axisCount = axisList.size();
7087
0
    if (subtype == EllipsoidalCS::WKT2_TYPE) {
7088
0
        if (axisCount == 2) {
7089
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1]);
7090
0
        }
7091
0
        if (axisCount == 3) {
7092
0
            return EllipsoidalCS::create(csMap, axisList[0], axisList[1],
7093
0
                                         axisList[2]);
7094
0
        }
7095
0
        throw ParsingException("Expected 2 or 3 axis");
7096
0
    }
7097
0
    if (subtype == CartesianCS::WKT2_TYPE) {
7098
0
        if (axisCount == 2) {
7099
0
            return CartesianCS::create(csMap, axisList[0], axisList[1]);
7100
0
        }
7101
0
        if (axisCount == 3) {
7102
0
            return CartesianCS::create(csMap, axisList[0], axisList[1],
7103
0
                                       axisList[2]);
7104
0
        }
7105
0
        throw ParsingException("Expected 2 or 3 axis");
7106
0
    }
7107
0
    if (subtype == AffineCS::WKT2_TYPE) {
7108
0
        if (axisCount == 2) {
7109
0
            return AffineCS::create(csMap, axisList[0], axisList[1]);
7110
0
        }
7111
0
        if (axisCount == 3) {
7112
0
            return AffineCS::create(csMap, axisList[0], axisList[1],
7113
0
                                    axisList[2]);
7114
0
        }
7115
0
        throw ParsingException("Expected 2 or 3 axis");
7116
0
    }
7117
0
    if (subtype == VerticalCS::WKT2_TYPE) {
7118
0
        if (axisCount == 1) {
7119
0
            return VerticalCS::create(csMap, axisList[0]);
7120
0
        }
7121
0
        throw ParsingException("Expected 1 axis");
7122
0
    }
7123
0
    if (subtype == SphericalCS::WKT2_TYPE) {
7124
0
        if (axisCount == 2) {
7125
            // Extension to ISO19111 to support (planet)-ocentric CS with
7126
            // geocentric latitude
7127
0
            return SphericalCS::create(csMap, axisList[0], axisList[1]);
7128
0
        } else if (axisCount == 3) {
7129
0
            return SphericalCS::create(csMap, axisList[0], axisList[1],
7130
0
                                       axisList[2]);
7131
0
        }
7132
0
        throw ParsingException("Expected 2 or 3 axis");
7133
0
    }
7134
0
    if (subtype == OrdinalCS::WKT2_TYPE) {
7135
0
        return OrdinalCS::create(csMap, axisList);
7136
0
    }
7137
0
    if (subtype == ParametricCS::WKT2_TYPE) {
7138
0
        if (axisCount == 1) {
7139
0
            return ParametricCS::create(csMap, axisList[0]);
7140
0
        }
7141
0
        throw ParsingException("Expected 1 axis");
7142
0
    }
7143
0
    if (subtype == DateTimeTemporalCS::WKT2_2019_TYPE) {
7144
0
        if (axisCount == 1) {
7145
0
            return DateTimeTemporalCS::create(csMap, axisList[0]);
7146
0
        }
7147
0
        throw ParsingException("Expected 1 axis");
7148
0
    }
7149
0
    if (subtype == TemporalCountCS::WKT2_2019_TYPE) {
7150
0
        if (axisCount == 1) {
7151
0
            return TemporalCountCS::create(csMap, axisList[0]);
7152
0
        }
7153
0
        throw ParsingException("Expected 1 axis");
7154
0
    }
7155
0
    if (subtype == TemporalMeasureCS::WKT2_2019_TYPE) {
7156
0
        if (axisCount == 1) {
7157
0
            return TemporalMeasureCS::create(csMap, axisList[0]);
7158
0
        }
7159
0
        throw ParsingException("Expected 1 axis");
7160
0
    }
7161
0
    throw ParsingException("Unhandled value for subtype");
7162
0
}
7163
7164
// ---------------------------------------------------------------------------
7165
7166
0
DatumEnsembleNNPtr JSONParser::buildDatumEnsemble(const json &j) {
7167
0
    std::vector<DatumNNPtr> datums;
7168
0
    if (j.contains("members")) {
7169
0
        auto membersJ = getArray(j, "members");
7170
0
        const bool hasEllipsoid(j.contains("ellipsoid"));
7171
0
        for (const auto &memberJ : membersJ) {
7172
0
            if (!memberJ.is_object()) {
7173
0
                throw ParsingException(
7174
0
                    "Unexpected type for value of a \"members\" member");
7175
0
            }
7176
0
            auto datumName(getName(memberJ));
7177
0
            bool datumAdded = false;
7178
0
            if (dbContext_ && memberJ.contains("id")) {
7179
0
                auto id = getObject(memberJ, "id");
7180
0
                auto authority = getString(id, "authority");
7181
0
                auto authFactory = AuthorityFactory::create(
7182
0
                    NN_NO_CHECK(dbContext_), authority);
7183
0
                auto code = id["code"];
7184
0
                std::string codeStr;
7185
0
                if (code.is_string()) {
7186
0
                    codeStr = code.get<std::string>();
7187
0
                } else if (code.is_number_integer()) {
7188
0
                    codeStr = internal::toString(code.get<int>());
7189
0
                } else {
7190
0
                    throw ParsingException(
7191
0
                        "Unexpected type for value of \"code\"");
7192
0
                }
7193
0
                try {
7194
0
                    datums.push_back(authFactory->createDatum(codeStr));
7195
0
                    datumAdded = true;
7196
0
                } catch (const std::exception &) {
7197
                    // Silently ignore, as this isn't necessary an error.
7198
                    // If an older PROJ version parses a DatumEnsemble object of
7199
                    // a more recent PROJ version where the datum ensemble got
7200
                    // a new member, it might be unknown from the older PROJ.
7201
0
                }
7202
0
            }
7203
7204
0
            if (dbContext_ && !datumAdded) {
7205
0
                auto authFactory = AuthorityFactory::create(
7206
0
                    NN_NO_CHECK(dbContext_), std::string());
7207
0
                auto list = authFactory->createObjectsFromName(
7208
0
                    datumName, {AuthorityFactory::ObjectType::DATUM},
7209
0
                    false /* approximate=false*/);
7210
0
                if (!list.empty()) {
7211
0
                    auto datum =
7212
0
                        util::nn_dynamic_pointer_cast<Datum>(list.front());
7213
0
                    if (!datum)
7214
0
                        throw ParsingException(
7215
0
                            "DatumEnsemble member is not a datum");
7216
0
                    datums.push_back(NN_NO_CHECK(datum));
7217
0
                    datumAdded = true;
7218
0
                }
7219
0
            }
7220
7221
0
            if (!datumAdded) {
7222
                // Fallback if no db match
7223
0
                if (hasEllipsoid) {
7224
0
                    datums.emplace_back(GeodeticReferenceFrame::create(
7225
0
                        buildProperties(memberJ),
7226
0
                        buildEllipsoid(getObject(j, "ellipsoid")),
7227
0
                        optional<std::string>(), PrimeMeridian::GREENWICH));
7228
0
                } else {
7229
0
                    datums.emplace_back(VerticalReferenceFrame::create(
7230
0
                        buildProperties(memberJ)));
7231
0
                }
7232
0
            }
7233
0
        }
7234
0
    } else {
7235
0
        auto name = getString(j, "name");
7236
0
        if (dbContext_) {
7237
0
            auto authFactory = AuthorityFactory::create(NN_NO_CHECK(dbContext_),
7238
0
                                                        std::string());
7239
0
            auto res = authFactory->createObjectsFromName(
7240
0
                name, {AuthorityFactory::ObjectType::DATUM_ENSEMBLE}, true, 1);
7241
0
            if (res.size() == 1) {
7242
0
                auto datumEnsemble =
7243
0
                    dynamic_cast<const DatumEnsemble *>(res.front().get());
7244
0
                if (datumEnsemble) {
7245
0
                    datums = datumEnsemble->datums();
7246
0
                }
7247
0
            } else {
7248
0
                throw ParsingException(
7249
0
                    "No entry for datum ensemble '" + name +
7250
0
                    "' in database, and no explicit member specified");
7251
0
            }
7252
0
        } else {
7253
0
            throw ParsingException("Datum ensemble '" + name +
7254
0
                                   "' has no explicit member specified and no "
7255
0
                                   "connection to database");
7256
0
        }
7257
0
    }
7258
7259
0
    return DatumEnsemble::create(
7260
0
        buildProperties(j), datums,
7261
0
        PositionalAccuracy::create(getString(j, "accuracy")));
7262
0
}
7263
7264
// ---------------------------------------------------------------------------
7265
7266
GeodeticReferenceFrameNNPtr
7267
0
JSONParser::buildGeodeticReferenceFrame(const json &j) {
7268
0
    auto ellipsoidJ = getObject(j, "ellipsoid");
7269
0
    auto pm = j.contains("prime_meridian")
7270
0
                  ? buildPrimeMeridian(getObject(j, "prime_meridian"))
7271
0
                  : PrimeMeridian::GREENWICH;
7272
0
    return GeodeticReferenceFrame::create(buildProperties(j),
7273
0
                                          buildEllipsoid(ellipsoidJ),
7274
0
                                          getAnchor(j), getAnchorEpoch(j), pm);
7275
0
}
7276
7277
// ---------------------------------------------------------------------------
7278
7279
DynamicGeodeticReferenceFrameNNPtr
7280
0
JSONParser::buildDynamicGeodeticReferenceFrame(const json &j) {
7281
0
    auto ellipsoidJ = getObject(j, "ellipsoid");
7282
0
    auto pm = j.contains("prime_meridian")
7283
0
                  ? buildPrimeMeridian(getObject(j, "prime_meridian"))
7284
0
                  : PrimeMeridian::GREENWICH;
7285
0
    Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
7286
0
                                UnitOfMeasure::YEAR);
7287
0
    optional<std::string> deformationModel;
7288
0
    if (j.contains("deformation_model")) {
7289
        // Before PROJJSON v0.5 / PROJ 9.1
7290
0
        deformationModel = getString(j, "deformation_model");
7291
0
    } else if (!deformationModelName_.empty()) {
7292
0
        deformationModel = deformationModelName_;
7293
0
    }
7294
0
    return DynamicGeodeticReferenceFrame::create(
7295
0
        buildProperties(j), buildEllipsoid(ellipsoidJ), getAnchor(j), pm,
7296
0
        frameReferenceEpoch, deformationModel);
7297
0
}
7298
7299
// ---------------------------------------------------------------------------
7300
7301
VerticalReferenceFrameNNPtr
7302
1
JSONParser::buildVerticalReferenceFrame(const json &j) {
7303
1
    return VerticalReferenceFrame::create(buildProperties(j), getAnchor(j),
7304
1
                                          getAnchorEpoch(j));
7305
1
}
7306
7307
// ---------------------------------------------------------------------------
7308
7309
DynamicVerticalReferenceFrameNNPtr
7310
0
JSONParser::buildDynamicVerticalReferenceFrame(const json &j) {
7311
0
    Measure frameReferenceEpoch(getNumber(j, "frame_reference_epoch"),
7312
0
                                UnitOfMeasure::YEAR);
7313
0
    optional<std::string> deformationModel;
7314
0
    if (j.contains("deformation_model")) {
7315
        // Before PROJJSON v0.5 / PROJ 9.1
7316
0
        deformationModel = getString(j, "deformation_model");
7317
0
    } else if (!deformationModelName_.empty()) {
7318
0
        deformationModel = deformationModelName_;
7319
0
    }
7320
0
    return DynamicVerticalReferenceFrame::create(
7321
0
        buildProperties(j), getAnchor(j), util::optional<RealizationMethod>(),
7322
0
        frameReferenceEpoch, deformationModel);
7323
0
}
7324
7325
// ---------------------------------------------------------------------------
7326
7327
0
PrimeMeridianNNPtr JSONParser::buildPrimeMeridian(const json &j) {
7328
0
    if (!j.contains("longitude")) {
7329
0
        throw ParsingException("Missing \"longitude\" key");
7330
0
    }
7331
0
    auto longitude = j["longitude"];
7332
0
    if (longitude.is_number()) {
7333
0
        return PrimeMeridian::create(
7334
0
            buildProperties(j),
7335
0
            Angle(longitude.get<double>(), UnitOfMeasure::DEGREE));
7336
0
    } else if (longitude.is_object()) {
7337
0
        return PrimeMeridian::create(buildProperties(j),
7338
0
                                     Angle(getMeasure(longitude)));
7339
0
    }
7340
0
    throw ParsingException("Unexpected type for value of \"longitude\"");
7341
0
}
7342
7343
// ---------------------------------------------------------------------------
7344
7345
0
EllipsoidNNPtr JSONParser::buildEllipsoid(const json &j) {
7346
0
    if (j.contains("semi_major_axis")) {
7347
0
        auto semiMajorAxis = getLength(j, "semi_major_axis");
7348
0
        const auto ellpsProperties = buildProperties(j);
7349
0
        std::string ellpsName;
7350
0
        ellpsProperties.getStringValue(IdentifiedObject::NAME_KEY, ellpsName);
7351
0
        const auto celestialBody(Ellipsoid::guessBodyName(
7352
0
            dbContext_, semiMajorAxis.getSIValue(), ellpsName));
7353
0
        if (j.contains("semi_minor_axis")) {
7354
0
            return Ellipsoid::createTwoAxis(ellpsProperties, semiMajorAxis,
7355
0
                                            getLength(j, "semi_minor_axis"),
7356
0
                                            celestialBody);
7357
0
        } else if (j.contains("inverse_flattening")) {
7358
0
            return Ellipsoid::createFlattenedSphere(
7359
0
                ellpsProperties, semiMajorAxis,
7360
0
                Scale(getNumber(j, "inverse_flattening")), celestialBody);
7361
0
        } else {
7362
0
            throw ParsingException(
7363
0
                "Missing semi_minor_axis or inverse_flattening");
7364
0
        }
7365
0
    } else if (j.contains("radius")) {
7366
0
        auto radius = getLength(j, "radius");
7367
0
        const auto celestialBody(
7368
0
            Ellipsoid::guessBodyName(dbContext_, radius.getSIValue()));
7369
0
        return Ellipsoid::createSphere(buildProperties(j), radius,
7370
0
                                       celestialBody);
7371
0
    }
7372
0
    throw ParsingException("Missing semi_major_axis or radius");
7373
0
}
7374
7375
//! @endcond
7376
7377
// ---------------------------------------------------------------------------
7378
7379
//! @cond Doxygen_Suppress
7380
7381
// import a CRS encoded as OGC Best Practice document 11-135.
7382
7383
static const char *const crsURLPrefixes[] = {
7384
    "http://opengis.net/def/crs",     "https://opengis.net/def/crs",
7385
    "http://www.opengis.net/def/crs", "https://www.opengis.net/def/crs",
7386
    "www.opengis.net/def/crs",
7387
};
7388
7389
3.67k
static bool isCRSURL(const std::string &text) {
7390
18.3k
    for (const auto crsURLPrefix : crsURLPrefixes) {
7391
18.3k
        if (starts_with(text, crsURLPrefix)) {
7392
42
            return true;
7393
42
        }
7394
18.3k
    }
7395
3.63k
    return false;
7396
3.67k
}
7397
7398
static CRSNNPtr importFromCRSURL(const std::string &text,
7399
37
                                 const DatabaseContextNNPtr &dbContext) {
7400
    // e.g http://www.opengis.net/def/crs/EPSG/0/4326
7401
37
    std::vector<std::string> parts;
7402
138
    for (const auto crsURLPrefix : crsURLPrefixes) {
7403
138
        if (starts_with(text, crsURLPrefix)) {
7404
37
            parts = split(text.substr(strlen(crsURLPrefix)), '/');
7405
37
            break;
7406
37
        }
7407
138
    }
7408
7409
    // e.g
7410
    // "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"
7411
37
    if (!parts.empty() && starts_with(parts[0], "-compound?")) {
7412
8
        parts = split(text.substr(text.find('?') + 1), '&');
7413
8
        std::map<int, std::string> mapParts;
7414
8
        for (const auto &part : parts) {
7415
8
            const auto queryParam = split(part, '=');
7416
8
            if (queryParam.size() != 2) {
7417
5
                throw ParsingException("invalid OGC CRS URL");
7418
5
            }
7419
3
            try {
7420
3
                mapParts[std::stoi(queryParam[0])] = queryParam[1];
7421
3
            } catch (const std::exception &) {
7422
3
                throw ParsingException("invalid OGC CRS URL");
7423
3
            }
7424
3
        }
7425
0
        std::vector<CRSNNPtr> components;
7426
0
        std::string name;
7427
0
        for (size_t i = 1; i <= mapParts.size(); ++i) {
7428
0
            const auto iter = mapParts.find(static_cast<int>(i));
7429
0
            if (iter == mapParts.end()) {
7430
0
                throw ParsingException("invalid OGC CRS URL");
7431
0
            }
7432
0
            components.emplace_back(importFromCRSURL(iter->second, dbContext));
7433
0
            if (!name.empty()) {
7434
0
                name += " + ";
7435
0
            }
7436
0
            name += components.back()->nameStr();
7437
0
        }
7438
0
        return CompoundCRS::create(
7439
0
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
7440
0
            components);
7441
0
    }
7442
7443
29
    if (parts.size() < 4) {
7444
23
        throw ParsingException("invalid OGC CRS URL");
7445
23
    }
7446
7447
6
    const auto &auth_name = parts[1];
7448
6
    const auto &code = parts[3];
7449
6
    try {
7450
6
        auto factoryCRS = AuthorityFactory::create(dbContext, auth_name);
7451
6
        return factoryCRS->createCoordinateReferenceSystem(code, true);
7452
6
    } catch (...) {
7453
6
        const auto &version = parts[2];
7454
6
        if (version.empty() || version == "0") {
7455
0
            const auto authoritiesFromAuthName =
7456
0
                dbContext->getVersionedAuthoritiesFromName(auth_name);
7457
0
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7458
0
                try {
7459
0
                    auto factoryCRS =
7460
0
                        AuthorityFactory::create(dbContext, authNameVersioned);
7461
0
                    return factoryCRS->createCoordinateReferenceSystem(code,
7462
0
                                                                       true);
7463
0
                } catch (...) {
7464
0
                }
7465
0
            }
7466
0
            throw;
7467
0
        }
7468
6
        std::string authNameWithVersion;
7469
6
        if (!dbContext->getVersionedAuthority(auth_name, version,
7470
6
                                              authNameWithVersion)) {
7471
6
            throw;
7472
6
        }
7473
0
        auto factoryCRS =
7474
0
            AuthorityFactory::create(dbContext, authNameWithVersion);
7475
0
        return factoryCRS->createCoordinateReferenceSystem(code, true);
7476
6
    }
7477
6
}
7478
7479
// ---------------------------------------------------------------------------
7480
7481
/* Import a CRS encoded as WMSAUTO string.
7482
 *
7483
 * Note that the WMS 1.3 specification does not include the
7484
 * units code, while apparently earlier specs do.  We try to
7485
 * guess around this.
7486
 *
7487
 * (code derived from GDAL's importFromWMSAUTO())
7488
 */
7489
7490
181
static CRSNNPtr importFromWMSAUTO(const std::string &text) {
7491
7492
181
    int nUnitsId = 9001;
7493
181
    double dfRefLong;
7494
181
    double dfRefLat = 0.0;
7495
7496
181
    assert(ci_starts_with(text, "AUTO:"));
7497
181
    const auto parts = split(text.substr(strlen("AUTO:")), ',');
7498
7499
181
    try {
7500
181
        constexpr int AUTO_MOLLWEIDE = 42005;
7501
181
        if (parts.size() == 4) {
7502
5
            nUnitsId = std::stoi(parts[1]);
7503
5
            dfRefLong = c_locale_stod(parts[2]);
7504
5
            dfRefLat = c_locale_stod(parts[3]);
7505
176
        } else if (parts.size() == 3 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
7506
6
            nUnitsId = std::stoi(parts[1]);
7507
6
            dfRefLong = c_locale_stod(parts[2]);
7508
170
        } else if (parts.size() == 3) {
7509
117
            dfRefLong = c_locale_stod(parts[1]);
7510
117
            dfRefLat = c_locale_stod(parts[2]);
7511
117
        } else if (parts.size() == 2 && std::stoi(parts[0]) == AUTO_MOLLWEIDE) {
7512
14
            dfRefLong = c_locale_stod(parts[1]);
7513
39
        } else {
7514
39
            throw ParsingException("invalid WMS AUTO CRS definition");
7515
39
        }
7516
7517
142
        const auto getConversion = [dfRefLong, dfRefLat, &parts]() {
7518
72
            const int nProjId = std::stoi(parts[0]);
7519
72
            switch (nProjId) {
7520
2
            case 42001: // Auto UTM
7521
2
                if (!(dfRefLong >= -180 && dfRefLong < 180)) {
7522
0
                    throw ParsingException("invalid WMS AUTO CRS definition: "
7523
0
                                           "invalid longitude");
7524
0
                }
7525
2
                return Conversion::createUTM(
7526
2
                    util::PropertyMap(),
7527
2
                    static_cast<int>(floor((dfRefLong + 180.0) / 6.0)) + 1,
7528
2
                    dfRefLat >= 0.0);
7529
7530
8
            case 42002: // Auto TM (strangely very UTM-like).
7531
8
                return Conversion::createTransverseMercator(
7532
8
                    util::PropertyMap(), common::Angle(0),
7533
8
                    common::Angle(dfRefLong), common::Scale(0.9996),
7534
8
                    common::Length(500000),
7535
8
                    common::Length((dfRefLat >= 0.0) ? 0.0 : 10000000.0));
7536
7537
28
            case 42003: // Auto Orthographic.
7538
28
                return Conversion::createOrthographic(
7539
28
                    util::PropertyMap(), common::Angle(dfRefLat),
7540
28
                    common::Angle(dfRefLong), common::Length(0),
7541
28
                    common::Length(0));
7542
7543
9
            case 42004: // Auto Equirectangular
7544
9
                return Conversion::createEquidistantCylindrical(
7545
9
                    util::PropertyMap(), common::Angle(dfRefLat),
7546
9
                    common::Angle(dfRefLong), common::Length(0),
7547
9
                    common::Length(0));
7548
7549
9
            case 42005: // MSVC 2015 thinks that AUTO_MOLLWEIDE is not constant
7550
9
                return Conversion::createMollweide(
7551
9
                    util::PropertyMap(), common::Angle(dfRefLong),
7552
9
                    common::Length(0), common::Length(0));
7553
7554
16
            default:
7555
16
                throw ParsingException("invalid WMS AUTO CRS definition: "
7556
16
                                       "unsupported projection id");
7557
72
            }
7558
72
        };
7559
7560
142
        const auto getUnits = [nUnitsId]() -> const UnitOfMeasure & {
7561
56
            switch (nUnitsId) {
7562
53
            case 9001:
7563
53
                return UnitOfMeasure::METRE;
7564
7565
0
            case 9002:
7566
0
                return UnitOfMeasure::FOOT;
7567
7568
0
            case 9003:
7569
0
                return UnitOfMeasure::US_FOOT;
7570
7571
3
            default:
7572
3
                throw ParsingException("invalid WMS AUTO CRS definition: "
7573
3
                                       "unsupported units code");
7574
56
            }
7575
56
        };
7576
7577
142
        return crs::ProjectedCRS::create(
7578
142
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, "unnamed"),
7579
142
            crs::GeographicCRS::EPSG_4326, getConversion(),
7580
142
            cs::CartesianCS::createEastingNorthing(getUnits()));
7581
7582
181
    } catch (const std::exception &) {
7583
128
        throw ParsingException("invalid WMS AUTO CRS definition");
7584
128
    }
7585
181
}
7586
7587
// ---------------------------------------------------------------------------
7588
7589
static BaseObjectNNPtr createFromURNPart(const DatabaseContextPtr &dbContext,
7590
                                         const std::string &type,
7591
                                         const std::string &authName,
7592
                                         const std::string &version,
7593
33
                                         const std::string &code) {
7594
33
    if (!dbContext) {
7595
6
        throw ParsingException("no database context specified");
7596
6
    }
7597
27
    try {
7598
27
        auto factory =
7599
27
            AuthorityFactory::create(NN_NO_CHECK(dbContext), authName);
7600
27
        if (type == "crs") {
7601
0
            return factory->createCoordinateReferenceSystem(code);
7602
0
        }
7603
27
        if (type == "coordinateOperation") {
7604
2
            return factory->createCoordinateOperation(code, true);
7605
2
        }
7606
25
        if (type == "datum") {
7607
0
            return factory->createDatum(code);
7608
0
        }
7609
25
        if (type == "ensemble") {
7610
0
            return factory->createDatumEnsemble(code);
7611
0
        }
7612
25
        if (type == "ellipsoid") {
7613
0
            return factory->createEllipsoid(code);
7614
0
        }
7615
25
        if (type == "meridian") {
7616
0
            return factory->createPrimeMeridian(code);
7617
0
        }
7618
        // Extension of OGC URN syntax to CoordinateMetadata
7619
25
        if (type == "coordinateMetadata") {
7620
0
            return factory->createCoordinateMetadata(code);
7621
0
        }
7622
25
        throw ParsingException(concat("unhandled object type: ", type));
7623
27
    } catch (...) {
7624
27
        if (version.empty()) {
7625
7
            const auto authoritiesFromAuthName =
7626
7
                dbContext->getVersionedAuthoritiesFromName(authName);
7627
7
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7628
0
                try {
7629
0
                    return createFromURNPart(dbContext, type, authNameVersioned,
7630
0
                                             std::string(), code);
7631
0
                } catch (...) {
7632
0
                }
7633
0
            }
7634
7
            throw;
7635
7
        }
7636
20
        std::string authNameWithVersion;
7637
20
        if (!dbContext->getVersionedAuthority(authName, version,
7638
20
                                              authNameWithVersion)) {
7639
20
            throw;
7640
20
        }
7641
0
        return createFromURNPart(dbContext, type, authNameWithVersion,
7642
0
                                 std::string(), code);
7643
20
    }
7644
27
}
7645
7646
// ---------------------------------------------------------------------------
7647
7648
static BaseObjectNNPtr createFromUserInput(const std::string &text,
7649
                                           const DatabaseContextPtr &dbContext,
7650
                                           bool usePROJ4InitRules,
7651
                                           PJ_CONTEXT *ctx,
7652
25.7k
                                           bool ignoreCoordinateEpoch) {
7653
25.7k
    std::size_t idxFirstCharNotSpace = text.find_first_not_of(" \t\r\n");
7654
25.7k
    if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) {
7655
372
        return createFromUserInput(text.substr(idxFirstCharNotSpace), dbContext,
7656
372
                                   usePROJ4InitRules, ctx,
7657
372
                                   ignoreCoordinateEpoch);
7658
372
    }
7659
7660
    // Parse strings like "ITRF2014 @ 2025.0"
7661
25.3k
    const auto posAt = text.find('@');
7662
25.3k
    if (!ignoreCoordinateEpoch && posAt != std::string::npos) {
7663
7664
        // Try first as if belonged to the name
7665
4.69k
        try {
7666
4.69k
            return createFromUserInput(text, dbContext, usePROJ4InitRules, ctx,
7667
4.69k
                                       /* ignoreCoordinateEpoch = */ true);
7668
4.69k
        } catch (...) {
7669
2.61k
        }
7670
7671
2.61k
        std::string leftPart = text.substr(0, posAt);
7672
2.98k
        while (!leftPart.empty() && leftPart.back() == ' ')
7673
375
            leftPart.resize(leftPart.size() - 1);
7674
2.61k
        const auto nonSpacePos = text.find_first_not_of(' ', posAt + 1);
7675
2.61k
        if (nonSpacePos != std::string::npos) {
7676
2.07k
            auto obj =
7677
2.07k
                createFromUserInput(leftPart, dbContext, usePROJ4InitRules, ctx,
7678
2.07k
                                    /* ignoreCoordinateEpoch = */ true);
7679
2.07k
            auto crs = nn_dynamic_pointer_cast<CRS>(obj);
7680
2.07k
            if (crs) {
7681
156
                double epoch;
7682
156
                try {
7683
156
                    epoch = c_locale_stod(text.substr(nonSpacePos));
7684
156
                } catch (const std::exception &) {
7685
83
                    throw ParsingException("non-numeric value after @");
7686
83
                }
7687
73
                try {
7688
73
                    return CoordinateMetadata::create(NN_NO_CHECK(crs), epoch,
7689
73
                                                      dbContext);
7690
73
                } catch (const std::exception &e) {
7691
7
                    throw ParsingException(
7692
7
                        std::string(
7693
7
                            "CoordinateMetadata::create() failed with: ") +
7694
7
                        e.what());
7695
7
                }
7696
73
            }
7697
2.07k
        }
7698
2.61k
    }
7699
7700
23.1k
    if (!text.empty() && text[0] == '{') {
7701
2.05k
        json j;
7702
2.05k
        try {
7703
2.05k
            j = json::parse(text);
7704
2.05k
        } catch (const std::exception &e) {
7705
1.96k
            throw ParsingException(e.what());
7706
1.96k
        }
7707
86
        return JSONParser().attachDatabaseContext(dbContext).create(j);
7708
2.05k
    }
7709
7710
21.0k
    if (!ci_starts_with(text, "step proj=") &&
7711
19.4k
        !ci_starts_with(text, "step +proj=")) {
7712
1.82M
        for (const auto &wktConstant : WKTConstants::constants()) {
7713
1.82M
            if (ci_starts_with(text, wktConstant)) {
7714
3.17k
                for (auto wkt = text.c_str() + wktConstant.size(); *wkt != '\0';
7715
3.16k
                     ++wkt) {
7716
3.16k
                    if (isspace(static_cast<unsigned char>(*wkt)))
7717
404
                        continue;
7718
2.76k
                    if (*wkt == '[') {
7719
2.66k
                        return WKTParser()
7720
2.66k
                            .attachDatabaseContext(dbContext)
7721
2.66k
                            .setStrict(false)
7722
2.66k
                            .createFromWKT(text);
7723
2.66k
                    }
7724
95
                    break;
7725
2.76k
                }
7726
2.77k
            }
7727
1.82M
        }
7728
19.4k
    }
7729
7730
18.3k
    const char *textWithoutPlusPrefix = text.c_str();
7731
18.3k
    if (textWithoutPlusPrefix[0] == '+')
7732
1.59k
        textWithoutPlusPrefix++;
7733
7734
18.3k
    if (strncmp(textWithoutPlusPrefix, "proj=", strlen("proj=")) == 0 ||
7735
7.24k
        text.find(" +proj=") != std::string::npos ||
7736
6.70k
        text.find(" proj=") != std::string::npos ||
7737
4.04k
        strncmp(textWithoutPlusPrefix, "init=", strlen("init=")) == 0 ||
7738
3.77k
        text.find(" +init=") != std::string::npos ||
7739
3.74k
        text.find(" init=") != std::string::npos ||
7740
13.1k
        strncmp(textWithoutPlusPrefix, "title=", strlen("title=")) == 0) {
7741
13.1k
        return PROJStringParser()
7742
13.1k
            .attachDatabaseContext(dbContext)
7743
13.1k
            .attachContext(ctx)
7744
13.1k
            .setUsePROJ4InitRules(ctx != nullptr
7745
13.1k
                                      ? (proj_context_get_use_proj4_init_rules(
7746
13.1k
                                             ctx, false) == TRUE)
7747
13.1k
                                      : usePROJ4InitRules)
7748
13.1k
            .createFromPROJString(text);
7749
13.1k
    }
7750
7751
5.23k
    if (isCRSURL(text) && dbContext) {
7752
37
        return importFromCRSURL(text, NN_NO_CHECK(dbContext));
7753
37
    }
7754
7755
5.19k
    if (ci_starts_with(text, "AUTO:")) {
7756
150
        return importFromWMSAUTO(text);
7757
150
    }
7758
7759
5.04k
    auto tokens = split(text, ':');
7760
5.04k
    if (tokens.size() == 2) {
7761
102
        if (!dbContext) {
7762
8
            throw ParsingException("no database context specified");
7763
8
        }
7764
94
        DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
7765
94
        const auto &authName = tokens[0];
7766
94
        const auto &code = tokens[1];
7767
94
        auto factory = AuthorityFactory::create(dbContextNNPtr, authName);
7768
94
        try {
7769
94
            return factory->createCoordinateReferenceSystem(code);
7770
94
        } catch (...) {
7771
7772
            // Convenience for well-known misused code
7773
            // See https://github.com/OSGeo/PROJ/issues/1730
7774
94
            if (ci_equal(authName, "EPSG") && code == "102100") {
7775
0
                factory = AuthorityFactory::create(dbContextNNPtr, "ESRI");
7776
0
                return factory->createCoordinateReferenceSystem(code);
7777
0
            }
7778
7779
94
            const auto authoritiesFromAuthName =
7780
94
                dbContextNNPtr->getVersionedAuthoritiesFromName(authName);
7781
94
            for (const auto &authNameVersioned : authoritiesFromAuthName) {
7782
0
                factory =
7783
0
                    AuthorityFactory::create(dbContextNNPtr, authNameVersioned);
7784
0
                try {
7785
0
                    return factory->createCoordinateReferenceSystem(code);
7786
0
                } catch (...) {
7787
0
                }
7788
0
            }
7789
7790
94
            const auto allAuthorities = dbContextNNPtr->getAuthorities();
7791
726
            for (const auto &authCandidate : allAuthorities) {
7792
726
                if (ci_equal(authCandidate, authName)) {
7793
10
                    factory =
7794
10
                        AuthorityFactory::create(dbContextNNPtr, authCandidate);
7795
10
                    try {
7796
10
                        return factory->createCoordinateReferenceSystem(code);
7797
10
                    } catch (...) {
7798
                        // EPSG:4326+3855
7799
10
                        auto tokensCode = split(code, '+');
7800
10
                        if (tokensCode.size() == 2) {
7801
1
                            auto crs1(factory->createCoordinateReferenceSystem(
7802
1
                                tokensCode[0], false));
7803
1
                            auto crs2(factory->createCoordinateReferenceSystem(
7804
1
                                tokensCode[1], false));
7805
1
                            return CompoundCRS::createLax(
7806
1
                                util::PropertyMap().set(
7807
1
                                    IdentifiedObject::NAME_KEY,
7808
1
                                    crs1->nameStr() + " + " + crs2->nameStr()),
7809
1
                                {crs1, crs2}, dbContext);
7810
1
                        }
7811
9
                        throw;
7812
10
                    }
7813
10
                }
7814
726
            }
7815
84
            throw;
7816
94
        }
7817
4.94k
    } else if (tokens.size() == 3) {
7818
        // ESRI:103668+EPSG:5703 ... compound
7819
31
        auto tokensCenter = split(tokens[1], '+');
7820
31
        if (tokensCenter.size() == 2) {
7821
7
            if (!dbContext) {
7822
4
                throw ParsingException("no database context specified");
7823
4
            }
7824
3
            DatabaseContextNNPtr dbContextNNPtr(NN_NO_CHECK(dbContext));
7825
7826
3
            const auto &authName1 = tokens[0];
7827
3
            const auto &code1 = tokensCenter[0];
7828
3
            const auto &authName2 = tokensCenter[1];
7829
3
            const auto &code2 = tokens[2];
7830
7831
3
            auto factory1 = AuthorityFactory::create(dbContextNNPtr, authName1);
7832
3
            auto crs1 = factory1->createCoordinateReferenceSystem(code1, false);
7833
3
            auto factory2 = AuthorityFactory::create(dbContextNNPtr, authName2);
7834
3
            auto crs2 = factory2->createCoordinateReferenceSystem(code2, false);
7835
3
            return CompoundCRS::createLax(
7836
3
                util::PropertyMap().set(IdentifiedObject::NAME_KEY,
7837
3
                                        crs1->nameStr() + " + " +
7838
3
                                            crs2->nameStr()),
7839
3
                {crs1, crs2}, dbContext);
7840
7
        }
7841
31
    }
7842
7843
4.93k
    if (starts_with(text, "urn:ogc:def:crs,")) {
7844
10
        if (!dbContext) {
7845
3
            throw ParsingException("no database context specified");
7846
3
        }
7847
7
        auto tokensComma = split(text, ',');
7848
7
        if (tokensComma.size() == 4 && starts_with(tokensComma[1], "crs:") &&
7849
0
            starts_with(tokensComma[2], "cs:") &&
7850
0
            starts_with(tokensComma[3], "coordinateOperation:")) {
7851
            // OGC 07-092r2: para 7.5.4
7852
            // URN combined references for projected or derived CRSs
7853
0
            const auto &crsPart = tokensComma[1];
7854
0
            const auto tokensCRS = split(crsPart, ':');
7855
0
            if (tokensCRS.size() != 4) {
7856
0
                throw ParsingException(
7857
0
                    concat("invalid crs component: ", crsPart));
7858
0
            }
7859
0
            auto factoryCRS =
7860
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCRS[1]);
7861
0
            auto baseCRS =
7862
0
                factoryCRS->createCoordinateReferenceSystem(tokensCRS[3], true);
7863
7864
0
            const auto &csPart = tokensComma[2];
7865
0
            auto tokensCS = split(csPart, ':');
7866
0
            if (tokensCS.size() != 4) {
7867
0
                throw ParsingException(
7868
0
                    concat("invalid cs component: ", csPart));
7869
0
            }
7870
0
            auto factoryCS =
7871
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensCS[1]);
7872
0
            auto cs = factoryCS->createCoordinateSystem(tokensCS[3]);
7873
7874
0
            const auto &opPart = tokensComma[3];
7875
0
            auto tokensOp = split(opPart, ':');
7876
0
            if (tokensOp.size() != 4) {
7877
0
                throw ParsingException(
7878
0
                    concat("invalid coordinateOperation component: ", opPart));
7879
0
            }
7880
0
            auto factoryOp =
7881
0
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokensOp[1]);
7882
0
            auto op = factoryOp->createCoordinateOperation(tokensOp[3], true);
7883
7884
0
            const auto &baseName = baseCRS->nameStr();
7885
0
            std::string name(baseName);
7886
0
            auto geogCRS =
7887
0
                util::nn_dynamic_pointer_cast<GeographicCRS>(baseCRS);
7888
0
            if (geogCRS &&
7889
0
                geogCRS->coordinateSystem()->axisList().size() == 3 &&
7890
0
                baseName.find("3D") == std::string::npos) {
7891
0
                name += " (3D)";
7892
0
            }
7893
0
            name += " / ";
7894
0
            name += op->nameStr();
7895
0
            auto props =
7896
0
                util::PropertyMap().set(IdentifiedObject::NAME_KEY, name);
7897
7898
0
            if (auto conv = util::nn_dynamic_pointer_cast<Conversion>(op)) {
7899
0
                auto convNN = NN_NO_CHECK(conv);
7900
0
                if (geogCRS != nullptr) {
7901
0
                    auto geogCRSNN = NN_NO_CHECK(geogCRS);
7902
0
                    if (CartesianCSPtr ccs =
7903
0
                            util::nn_dynamic_pointer_cast<CartesianCS>(cs)) {
7904
0
                        return ProjectedCRS::create(props, geogCRSNN, convNN,
7905
0
                                                    NN_NO_CHECK(ccs));
7906
0
                    }
7907
0
                    if (EllipsoidalCSPtr ecs =
7908
0
                            util::nn_dynamic_pointer_cast<EllipsoidalCS>(cs)) {
7909
0
                        return DerivedGeographicCRS::create(
7910
0
                            props, geogCRSNN, convNN, NN_NO_CHECK(ecs));
7911
0
                    }
7912
0
                } else if (dynamic_cast<GeodeticCRS *>(baseCRS.get()) &&
7913
0
                           dynamic_cast<CartesianCS *>(cs.get())) {
7914
0
                    return DerivedGeodeticCRS::create(
7915
0
                        props,
7916
0
                        NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
7917
0
                            baseCRS)),
7918
0
                        convNN,
7919
0
                        NN_NO_CHECK(
7920
0
                            util::nn_dynamic_pointer_cast<CartesianCS>(cs)));
7921
0
                } else if (auto pcrs =
7922
0
                               util::nn_dynamic_pointer_cast<ProjectedCRS>(
7923
0
                                   baseCRS)) {
7924
0
                    return DerivedProjectedCRS::create(props, NN_NO_CHECK(pcrs),
7925
0
                                                       convNN, cs);
7926
0
                } else if (auto vertBaseCRS =
7927
0
                               util::nn_dynamic_pointer_cast<VerticalCRS>(
7928
0
                                   baseCRS)) {
7929
0
                    if (auto vertCS =
7930
0
                            util::nn_dynamic_pointer_cast<VerticalCS>(cs)) {
7931
0
                        const int methodCode = convNN->method()->getEPSGCode();
7932
0
                        std::string newName(baseName);
7933
0
                        std::string unitNameSuffix;
7934
0
                        for (const char *suffix : {" (ft)", " (ftUS)"}) {
7935
0
                            if (ends_with(newName, suffix)) {
7936
0
                                unitNameSuffix = suffix;
7937
0
                                newName.resize(newName.size() - strlen(suffix));
7938
0
                                break;
7939
0
                            }
7940
0
                        }
7941
0
                        bool newNameOk = false;
7942
0
                        if (methodCode ==
7943
0
                                EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
7944
0
                            methodCode ==
7945
0
                                EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
7946
0
                            const auto &unitName =
7947
0
                                vertCS->axisList()[0]->unit().name();
7948
0
                            if (unitName == UnitOfMeasure::METRE.name()) {
7949
0
                                newNameOk = true;
7950
0
                            } else if (unitName == UnitOfMeasure::FOOT.name()) {
7951
0
                                newName += " (ft)";
7952
0
                                newNameOk = true;
7953
0
                            } else if (unitName ==
7954
0
                                       UnitOfMeasure::US_FOOT.name()) {
7955
0
                                newName += " (ftUS)";
7956
0
                                newNameOk = true;
7957
0
                            }
7958
0
                        } else if (methodCode ==
7959
0
                                   EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
7960
0
                            if (ends_with(newName, " height")) {
7961
0
                                newName.resize(newName.size() -
7962
0
                                               strlen(" height"));
7963
0
                                newName += " depth";
7964
0
                                newName += unitNameSuffix;
7965
0
                                newNameOk = true;
7966
0
                            } else if (ends_with(newName, " depth")) {
7967
0
                                newName.resize(newName.size() -
7968
0
                                               strlen(" depth"));
7969
0
                                newName += " height";
7970
0
                                newName += unitNameSuffix;
7971
0
                                newNameOk = true;
7972
0
                            }
7973
0
                        }
7974
0
                        if (newNameOk) {
7975
0
                            props.set(IdentifiedObject::NAME_KEY, newName);
7976
0
                        }
7977
0
                        return DerivedVerticalCRS::create(
7978
0
                            props, NN_NO_CHECK(vertBaseCRS), convNN,
7979
0
                            NN_NO_CHECK(vertCS));
7980
0
                    }
7981
0
                }
7982
0
            }
7983
7984
0
            throw ParsingException("unsupported combination of baseCRS, CS "
7985
0
                                   "and coordinateOperation for a "
7986
0
                                   "DerivedCRS");
7987
0
        }
7988
7989
        // OGC 07-092r2: para 7.5.2
7990
        // URN combined references for compound coordinate reference systems
7991
7
        std::vector<CRSNNPtr> components;
7992
7
        std::string name;
7993
7
        for (size_t i = 1; i < tokensComma.size(); i++) {
7994
7
            tokens = split(tokensComma[i], ':');
7995
7
            if (tokens.size() != 4) {
7996
6
                throw ParsingException(
7997
6
                    concat("invalid crs component: ", tokensComma[i]));
7998
6
            }
7999
1
            const auto &type = tokens[0];
8000
1
            auto factory =
8001
1
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
8002
1
            const auto &code = tokens[3];
8003
1
            if (type == "crs") {
8004
0
                auto crs(factory->createCoordinateReferenceSystem(code, false));
8005
0
                components.emplace_back(crs);
8006
0
                if (!name.empty()) {
8007
0
                    name += " + ";
8008
0
                }
8009
0
                name += crs->nameStr();
8010
1
            } else {
8011
1
                throw ParsingException(
8012
1
                    concat("unexpected object type: ", type));
8013
1
            }
8014
1
        }
8015
0
        return CompoundCRS::create(
8016
0
            util::PropertyMap().set(IdentifiedObject::NAME_KEY, name),
8017
0
            components);
8018
7
    }
8019
8020
    // OGC 07-092r2: para 7.5.3
8021
    // 7.5.3 URN combined references for concatenated operations
8022
4.92k
    if (starts_with(text, "urn:ogc:def:coordinateOperation,")) {
8023
12
        if (!dbContext) {
8024
3
            throw ParsingException("no database context specified");
8025
3
        }
8026
9
        auto tokensComma = split(text, ',');
8027
9
        std::vector<CoordinateOperationNNPtr> components;
8028
9
        for (size_t i = 1; i < tokensComma.size(); i++) {
8029
9
            tokens = split(tokensComma[i], ':');
8030
9
            if (tokens.size() != 4) {
8031
4
                throw ParsingException(concat(
8032
4
                    "invalid coordinateOperation component: ", tokensComma[i]));
8033
4
            }
8034
5
            const auto &type = tokens[0];
8035
5
            auto factory =
8036
5
                AuthorityFactory::create(NN_NO_CHECK(dbContext), tokens[1]);
8037
5
            const auto &code = tokens[3];
8038
5
            if (type == "coordinateOperation") {
8039
0
                auto op(factory->createCoordinateOperation(code, false));
8040
0
                components.emplace_back(op);
8041
5
            } else {
8042
5
                throw ParsingException(
8043
5
                    concat("unexpected object type: ", type));
8044
5
            }
8045
5
        }
8046
0
        return ConcatenatedOperation::createComputeMetadata(components, true);
8047
9
    }
8048
8049
    // urn:ogc:def:crs:EPSG::4326
8050
4.91k
    if (tokens.size() == 7 && tolower(tokens[0]) == "urn") {
8051
8052
11
        const std::string type(tokens[3] == "CRS" ? "crs" : tokens[3]);
8053
11
        const auto &authName = tokens[4];
8054
11
        const auto &version = tokens[5];
8055
11
        const auto &code = tokens[6];
8056
11
        return createFromURNPart(dbContext, type, authName, version, code);
8057
11
    }
8058
8059
    // urn:ogc:def:crs:OGC::AUTO42001:-117:33
8060
4.90k
    if (tokens.size() > 7 && tokens[0] == "urn" && tokens[4] == "OGC" &&
8061
34
        ci_starts_with(tokens[6], "AUTO")) {
8062
31
        const auto textAUTO = text.substr(text.find(":AUTO") + 5);
8063
31
        return importFromWMSAUTO("AUTO:" + replaceAll(textAUTO, ":", ","));
8064
31
    }
8065
8066
    // Legacy urn:opengis:crs:EPSG:0:4326 (note the missing def: compared to
8067
    // above)
8068
4.87k
    if (tokens.size() == 6 && tokens[0] == "urn" && tokens[2] != "def") {
8069
17
        const auto &type = tokens[2];
8070
17
        const auto &authName = tokens[3];
8071
17
        const auto &version = tokens[4];
8072
17
        const auto &code = tokens[5];
8073
17
        return createFromURNPart(dbContext, type, authName, version, code);
8074
17
    }
8075
8076
    // Legacy urn:x-ogc:def:crs:EPSG:4326 (note the missing version)
8077
4.85k
    if (tokens.size() == 6 && tokens[0] == "urn") {
8078
5
        const auto &type = tokens[3];
8079
5
        const auto &authName = tokens[4];
8080
5
        const auto &code = tokens[5];
8081
5
        return createFromURNPart(dbContext, type, authName, std::string(),
8082
5
                                 code);
8083
5
    }
8084
8085
4.84k
    if (dbContext) {
8086
3.20k
        auto factory =
8087
3.20k
            AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string());
8088
8089
3.20k
        const auto searchObject =
8090
3.20k
            [&factory](
8091
3.20k
                const std::string &objectName, bool approximateMatch,
8092
3.20k
                const std::vector<AuthorityFactory::ObjectType> &objectTypes)
8093
8.05k
            -> IdentifiedObjectPtr {
8094
8.05k
            constexpr size_t limitResultCount = 10;
8095
8.05k
            auto res = factory->createObjectsFromName(
8096
8.05k
                objectName, objectTypes, approximateMatch, limitResultCount);
8097
8.05k
            if (res.size() == 1) {
8098
838
                return res.front().as_nullable();
8099
838
            }
8100
7.21k
            if (res.size() > 1) {
8101
988
                if (objectTypes.size() == 1 &&
8102
715
                    objectTypes[0] == AuthorityFactory::ObjectType::CRS) {
8103
994
                    for (size_t ndim = 2; ndim <= 3; ndim++) {
8104
3.38k
                        for (const auto &obj : res) {
8105
3.38k
                            auto crs =
8106
3.38k
                                dynamic_cast<crs::GeographicCRS *>(obj.get());
8107
3.38k
                            if (crs &&
8108
900
                                crs->coordinateSystem()->axisList().size() ==
8109
900
                                    ndim) {
8110
581
                                return obj.as_nullable();
8111
581
                            }
8112
3.38k
                        }
8113
860
                    }
8114
715
                }
8115
8116
                // If there's exactly only one object whose name is equivalent
8117
                // to the user input, return it.
8118
1.21k
                for (int pass = 0; pass <= 1; ++pass) {
8119
812
                    IdentifiedObjectPtr identifiedObj;
8120
6.23k
                    for (const auto &obj : res) {
8121
6.23k
                        if (Identifier::isEquivalentName(
8122
6.23k
                                obj->nameStr().c_str(), objectName.c_str(),
8123
6.23k
                                /* biggerDifferencesAllowed = */ pass == 1)) {
8124
5
                            if (identifiedObj == nullptr) {
8125
5
                                identifiedObj = obj.as_nullable();
8126
5
                            } else {
8127
0
                                identifiedObj = nullptr;
8128
0
                                break;
8129
0
                            }
8130
5
                        }
8131
6.23k
                    }
8132
812
                    if (identifiedObj) {
8133
5
                        return identifiedObj;
8134
5
                    }
8135
812
                }
8136
8137
402
                std::string msg("several objects matching this name: ");
8138
402
                bool first = true;
8139
2.65k
                for (const auto &obj : res) {
8140
2.65k
                    if (msg.size() > 200) {
8141
230
                        msg += ", ...";
8142
230
                        break;
8143
230
                    }
8144
2.42k
                    if (!first) {
8145
2.02k
                        msg += ", ";
8146
2.02k
                    }
8147
2.42k
                    first = false;
8148
2.42k
                    msg += obj->nameStr();
8149
2.42k
                }
8150
402
                throw ParsingException(msg);
8151
407
            }
8152
6.22k
            return nullptr;
8153
7.21k
        };
8154
8155
3.20k
        const auto searchCRS = [&searchObject](const std::string &objectName) {
8156
41
            const auto objectTypes = std::vector<AuthorityFactory::ObjectType>{
8157
41
                AuthorityFactory::ObjectType::CRS};
8158
41
            {
8159
41
                constexpr bool approximateMatch = false;
8160
41
                auto ret =
8161
41
                    searchObject(objectName, approximateMatch, objectTypes);
8162
41
                if (ret)
8163
0
                    return ret;
8164
41
            }
8165
8166
41
            constexpr bool approximateMatch = true;
8167
41
            return searchObject(objectName, approximateMatch, objectTypes);
8168
41
        };
8169
8170
        // strings like "WGS 84 + EGM96 height"
8171
3.20k
        CompoundCRSPtr compoundCRS;
8172
3.20k
        try {
8173
3.20k
            const auto tokensCompound = split(text, " + ");
8174
3.20k
            if (tokensCompound.size() == 2) {
8175
21
                auto obj1 = searchCRS(tokensCompound[0]);
8176
21
                auto obj2 = searchCRS(tokensCompound[1]);
8177
21
                auto crs1 = std::dynamic_pointer_cast<CRS>(obj1);
8178
21
                auto crs2 = std::dynamic_pointer_cast<CRS>(obj2);
8179
21
                if (crs1 && crs2) {
8180
1
                    compoundCRS =
8181
1
                        CompoundCRS::create(
8182
1
                            util::PropertyMap().set(IdentifiedObject::NAME_KEY,
8183
1
                                                    crs1->nameStr() + " + " +
8184
1
                                                        crs2->nameStr()),
8185
1
                            {NN_NO_CHECK(crs1), NN_NO_CHECK(crs2)})
8186
1
                            .as_nullable();
8187
1
                }
8188
21
            }
8189
3.20k
        } catch (const std::exception &) {
8190
3
        }
8191
8192
        // First pass: exact match on CRS objects
8193
        // Second pass: exact match on other objects
8194
        // Third pass: approximate match on CRS objects
8195
        // Fourth pass: approximate match on other objects
8196
        // But only allow approximate matching if the size of the text is
8197
        // large enough (>= 5), otherwise we get a lot of false positives:
8198
        // "foo" -> "Amersfoort", "bar" -> "Barbados 1938"
8199
        // Also only accept approximate matching if the ratio between the
8200
        // input and match size is not too small, so that "omerc" doesn't match
8201
        // with "WGS 84 / Pseudo-Mercator"
8202
3.20k
        const int maxNumberPasses = text.size() <= 4 ? 2 : 4;
8203
10.0k
        for (int pass = 0; pass < maxNumberPasses; ++pass) {
8204
7.97k
            const bool approximateMatch = (pass >= 2);
8205
7.97k
            auto ret = searchObject(
8206
7.97k
                text, approximateMatch,
8207
7.97k
                (pass == 0 || pass == 2)
8208
7.97k
                    ? std::vector<
8209
4.61k
                          AuthorityFactory::ObjectType>{AuthorityFactory::
8210
4.61k
                                                            ObjectType::CRS}
8211
7.97k
                    : std::vector<AuthorityFactory::ObjectType>{
8212
3.36k
                          AuthorityFactory::ObjectType::ELLIPSOID,
8213
3.36k
                          AuthorityFactory::ObjectType::DATUM,
8214
3.36k
                          AuthorityFactory::ObjectType::DATUM_ENSEMBLE,
8215
3.36k
                          AuthorityFactory::ObjectType::COORDINATE_OPERATION});
8216
7.97k
            if (ret) {
8217
1.41k
                if (!approximateMatch ||
8218
680
                    ret->nameStr().size() < 2 * text.size())
8219
1.15k
                    return NN_NO_CHECK(ret);
8220
1.41k
            }
8221
6.81k
            if (compoundCRS) {
8222
0
                if (!approximateMatch ||
8223
0
                    compoundCRS->nameStr().size() < 2 * text.size())
8224
0
                    return NN_NO_CHECK(compoundCRS);
8225
0
            }
8226
6.81k
        }
8227
3.20k
    }
8228
8229
3.69k
    throw ParsingException("unrecognized format / unknown name");
8230
4.84k
}
8231
//! @endcond
8232
8233
// ---------------------------------------------------------------------------
8234
8235
/** \brief Instantiate a sub-class of BaseObject from a user specified text.
8236
 *
8237
 * The text can be a:
8238
 * <ul>
8239
 * <li>WKT string</li>
8240
 * <li>PROJ string</li>
8241
 * <li>database code, prefixed by its authority. e.g. "EPSG:4326"</li>
8242
 * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
8243
 *     "urn:ogc:def:coordinateOperation:EPSG::1671",
8244
 *     "urn:ogc:def:ellipsoid:EPSG::7001"
8245
 *     or "urn:ogc:def:datum:EPSG::6326"</li>
8246
 * <li> OGC URN combining references for compound coordinate reference systems
8247
 *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
8248
 *      We also accept a custom abbreviated syntax EPSG:2393+5717
8249
 *      or ESRI:103668+EPSG:5703
8250
 * </li>
8251
 * <li> OGC URN combining references for references for projected or derived
8252
 * CRSs
8253
 *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
8254
 *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
8255
 * </li>
8256
 * <li>Extension of OGC URN for CoordinateMetadata.
8257
 *     e.g.
8258
 * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"</li>
8259
 * <li> OGC URN combining references for concatenated operations
8260
 *      e.g.
8261
 * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
8262
 * <li>OGC URL for a single CRS. e.g.
8263
 * "http://www.opengis.net/def/crs/EPSG/0/4326"</li>
8264
 * <li>OGC URL for a compound
8265
 * CRS. e.g
8266
 * "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>
8267
 * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
8268
 *     uniqueness is not guaranteed, the function may apply heuristics to
8269
 *     determine the appropriate best match.</li>
8270
 * <li>a CRS name and a coordinate epoch, separated with '@'. For example
8271
 *     "ITRF2014@2025.0". (added in PROJ 9.2)</li>
8272
 * <li>a compound CRS made from two object names separated with " + ".
8273
 *     e.g. "WGS 84 + EGM96 height"</li>
8274
 * <li>PROJJSON string</li>
8275
 * </ul>
8276
 *
8277
 * @param text One of the above mentioned text format
8278
 * @param dbContext Database context, or nullptr (in which case database
8279
 * lookups will not work)
8280
 * @param usePROJ4InitRules When set to true,
8281
 * init=epsg:XXXX syntax will be allowed and will be interpreted according to
8282
 * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
8283
 * order and will expect/output coordinates in radians. ProjectedCRS will have
8284
 * easting, northing axis order (except the ones with Transverse Mercator South
8285
 * Orientated projection). In that mode, the epsg:XXXX syntax will be also
8286
 * interpreted the same way.
8287
 * @throw ParsingException if the string cannot be parsed.
8288
 */
8289
BaseObjectNNPtr createFromUserInput(const std::string &text,
8290
                                    const DatabaseContextPtr &dbContext,
8291
276
                                    bool usePROJ4InitRules) {
8292
276
    return createFromUserInput(text, dbContext, usePROJ4InitRules, nullptr,
8293
276
                               /* ignoreCoordinateEpoch = */ false);
8294
276
}
8295
8296
// ---------------------------------------------------------------------------
8297
8298
/** \brief Instantiate a sub-class of BaseObject from a user specified text.
8299
 *
8300
 * The text can be a:
8301
 * <ul>
8302
 * <li>WKT string</li>
8303
 * <li>PROJ string</li>
8304
 * <li>database code, prefixed by its authority. e.g. "EPSG:4326"</li>
8305
 * <li>OGC URN. e.g. "urn:ogc:def:crs:EPSG::4326",
8306
 *     "urn:ogc:def:coordinateOperation:EPSG::1671",
8307
 *     "urn:ogc:def:ellipsoid:EPSG::7001"
8308
 *     or "urn:ogc:def:datum:EPSG::6326"</li>
8309
 * <li> OGC URN combining references for compound coordinate reference systems
8310
 *      e.g. "urn:ogc:def:crs,crs:EPSG::2393,crs:EPSG::5717"
8311
 *      We also accept a custom abbreviated syntax EPSG:2393+5717
8312
 * </li>
8313
 * <li> OGC URN combining references for references for projected or derived
8314
 * CRSs
8315
 *      e.g. for Projected 3D CRS "UTM zone 31N / WGS 84 (3D)"
8316
 *      "urn:ogc:def:crs,crs:EPSG::4979,cs:PROJ::ENh,coordinateOperation:EPSG::16031"
8317
 * </li>
8318
 * <li>Extension of OGC URN for CoordinateMetadata.
8319
 *     e.g.
8320
 * "urn:ogc:def:coordinateMetadata:NRCAN::NAD83_CSRS_1997_MTM11_HT2_1997"</li>
8321
 * <li> OGC URN combining references for concatenated operations
8322
 *      e.g.
8323
 * "urn:ogc:def:coordinateOperation,coordinateOperation:EPSG::3895,coordinateOperation:EPSG::1618"</li>
8324
 * <li>an Object name. e.g "WGS 84", "WGS 84 / UTM zone 31N". In that case as
8325
 *     uniqueness is not guaranteed, the function may apply heuristics to
8326
 *     determine the appropriate best match.</li>
8327
 * <li>a compound CRS made from two object names separated with " + ".
8328
 *     e.g. "WGS 84 + EGM96 height"</li>
8329
 * <li>PROJJSON string</li>
8330
 * </ul>
8331
 *
8332
 * @param text One of the above mentioned text format
8333
 * @param ctx PROJ context
8334
 * @throw ParsingException if the string cannot be parsed.
8335
 */
8336
18.3k
BaseObjectNNPtr createFromUserInput(const std::string &text, PJ_CONTEXT *ctx) {
8337
18.3k
    DatabaseContextPtr dbContext;
8338
18.3k
    try {
8339
18.3k
        if (ctx != nullptr) {
8340
            // Only connect to proj.db if needed
8341
18.3k
            if (text.find("proj=") == std::string::npos ||
8342
11.9k
                text.find("init=") != std::string::npos) {
8343
6.81k
                dbContext =
8344
6.81k
                    ctx->get_cpp_context()->getDatabaseContext().as_nullable();
8345
6.81k
            }
8346
18.3k
        }
8347
18.3k
    } catch (const std::exception &) {
8348
0
    }
8349
18.3k
    return createFromUserInput(text, dbContext, false, ctx,
8350
18.3k
                               /* ignoreCoordinateEpoch = */ false);
8351
18.3k
}
8352
8353
// ---------------------------------------------------------------------------
8354
8355
/** \brief Instantiate a sub-class of BaseObject from a WKT string.
8356
 *
8357
 * By default, validation is strict (to the extent of the checks that are
8358
 * actually implemented. Currently only WKT1 strict grammar is checked), and
8359
 * any issue detected will cause an exception to be thrown, unless
8360
 * setStrict(false) is called priorly.
8361
 *
8362
 * In non-strict mode, non-fatal issues will be recovered and simply listed
8363
 * in warningList(). This does not prevent more severe errors to cause an
8364
 * exception to be thrown.
8365
 *
8366
 * @throw ParsingException if the string cannot be parsed.
8367
 */
8368
2.66k
BaseObjectNNPtr WKTParser::createFromWKT(const std::string &wkt) {
8369
8370
2.66k
    const auto dialect = guessDialect(wkt);
8371
2.66k
    d->maybeEsriStyle_ = (dialect == WKTGuessedDialect::WKT1_ESRI);
8372
2.66k
    if (d->maybeEsriStyle_) {
8373
1.48k
        if (wkt.find("PARAMETER[\"X_Scale\",") != std::string::npos) {
8374
34
            d->esriStyle_ = true;
8375
34
            d->maybeEsriStyle_ = false;
8376
34
        }
8377
1.48k
    }
8378
8379
2.66k
    const auto build = [this, &wkt]() -> BaseObjectNNPtr {
8380
2.66k
        size_t indexEnd;
8381
2.66k
        WKTNodeNNPtr root = WKTNode::createFrom(wkt, 0, 0, indexEnd);
8382
2.66k
        const std::string &name(root->GP()->value());
8383
2.66k
        if (ci_equal(name, WKTConstants::DATUM) ||
8384
2.29k
            ci_equal(name, WKTConstants::GEODETICDATUM) ||
8385
2.29k
            ci_equal(name, WKTConstants::TRF)) {
8386
8387
92
            auto primeMeridian = PrimeMeridian::GREENWICH;
8388
92
            if (indexEnd < wkt.size()) {
8389
79
                indexEnd = skipSpace(wkt, indexEnd);
8390
79
                if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
8391
59
                    ++indexEnd;
8392
59
                    indexEnd = skipSpace(wkt, indexEnd);
8393
59
                    if (indexEnd < wkt.size() &&
8394
56
                        ci_starts_with(wkt.c_str() + indexEnd,
8395
56
                                       WKTConstants::PRIMEM.c_str())) {
8396
21
                        primeMeridian = d->buildPrimeMeridian(
8397
21
                            WKTNode::createFrom(wkt, indexEnd, 0, indexEnd),
8398
21
                            UnitOfMeasure::DEGREE);
8399
21
                    }
8400
59
                }
8401
79
            }
8402
92
            return d->buildGeodeticReferenceFrame(root, primeMeridian,
8403
92
                                                  null_node);
8404
2.57k
        } else if (ci_equal(name, WKTConstants::GEOGCS) ||
8405
1.93k
                   ci_equal(name, WKTConstants::PROJCS)) {
8406
            // Parse implicit compoundCRS from ESRI that is
8407
            // "PROJCS[...],VERTCS[...]" or "GEOGCS[...],VERTCS[...]"
8408
1.02k
            if (indexEnd < wkt.size()) {
8409
741
                indexEnd = skipSpace(wkt, indexEnd);
8410
741
                if (indexEnd < wkt.size() && wkt[indexEnd] == ',') {
8411
250
                    ++indexEnd;
8412
250
                    indexEnd = skipSpace(wkt, indexEnd);
8413
250
                    if (indexEnd < wkt.size() &&
8414
246
                        ci_starts_with(wkt.c_str() + indexEnd,
8415
246
                                       WKTConstants::VERTCS.c_str())) {
8416
172
                        auto horizCRS = d->buildCRS(root);
8417
172
                        if (horizCRS) {
8418
144
                            auto vertCRS =
8419
144
                                d->buildVerticalCRS(WKTNode::createFrom(
8420
144
                                    wkt, indexEnd, 0, indexEnd));
8421
144
                            return CompoundCRS::createLax(
8422
144
                                util::PropertyMap().set(
8423
144
                                    IdentifiedObject::NAME_KEY,
8424
144
                                    horizCRS->nameStr() + " + " +
8425
144
                                        vertCRS->nameStr()),
8426
144
                                {NN_NO_CHECK(horizCRS), vertCRS},
8427
144
                                d->dbContext_);
8428
144
                        }
8429
172
                    }
8430
250
                }
8431
741
            }
8432
1.02k
        }
8433
2.43k
        return d->build(root);
8434
2.66k
    };
8435
8436
2.66k
    auto obj = build();
8437
8438
2.66k
    if (dialect == WKTGuessedDialect::WKT1_GDAL ||
8439
1.17k
        dialect == WKTGuessedDialect::WKT1_ESRI) {
8440
742
        auto errorMsg = pj_wkt1_parse(wkt);
8441
742
        if (!errorMsg.empty()) {
8442
460
            d->emitGrammarError(errorMsg);
8443
460
        }
8444
1.92k
    } else if (dialect == WKTGuessedDialect::WKT2_2015 ||
8445
517
               dialect == WKTGuessedDialect::WKT2_2019) {
8446
517
        auto errorMsg = pj_wkt2_parse(wkt);
8447
517
        if (!errorMsg.empty()) {
8448
464
            d->emitGrammarError(errorMsg);
8449
464
        }
8450
517
    }
8451
8452
2.66k
    return obj;
8453
2.66k
}
8454
8455
// ---------------------------------------------------------------------------
8456
8457
/** \brief Attach a database context, to allow queries in it if needed.
8458
 */
8459
WKTParser &
8460
2.66k
WKTParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
8461
2.66k
    d->dbContext_ = dbContext;
8462
2.66k
    return *this;
8463
2.66k
}
8464
8465
// ---------------------------------------------------------------------------
8466
8467
/** \brief Guess the "dialect" of the WKT string.
8468
 */
8469
WKTParser::WKTGuessedDialect
8470
2.66k
WKTParser::guessDialect(const std::string &inputWkt) noexcept {
8471
8472
    // cppcheck complains (rightly) that the method could be static
8473
2.66k
    (void)this;
8474
8475
2.66k
    std::string wkt = inputWkt;
8476
2.66k
    std::size_t idxFirstCharNotSpace = wkt.find_first_not_of(" \t\r\n");
8477
2.66k
    if (idxFirstCharNotSpace > 0 && idxFirstCharNotSpace != std::string::npos) {
8478
0
        wkt = wkt.substr(idxFirstCharNotSpace);
8479
0
    }
8480
2.66k
    if (ci_starts_with(wkt, WKTConstants::VERTCS)) {
8481
175
        return WKTGuessedDialect::WKT1_ESRI;
8482
175
    }
8483
2.49k
    const std::string *const wkt1_keywords[] = {
8484
2.49k
        &WKTConstants::GEOCCS, &WKTConstants::GEOGCS,  &WKTConstants::COMPD_CS,
8485
2.49k
        &WKTConstants::PROJCS, &WKTConstants::VERT_CS, &WKTConstants::LOCAL_CS};
8486
10.8k
    for (const auto &pointerKeyword : wkt1_keywords) {
8487
10.8k
        if (ci_starts_with(wkt, *pointerKeyword)) {
8488
8489
1.59k
            if ((ci_find(wkt, "GEOGCS[\"GCS_") != std::string::npos ||
8490
1.31k
                 (!ci_starts_with(wkt, WKTConstants::LOCAL_CS) &&
8491
1.16k
                  ci_find(wkt, "AXIS[") == std::string::npos &&
8492
1.07k
                  ci_find(wkt, "AUTHORITY[") == std::string::npos)) &&
8493
                // WKT1:GDAL and WKT1:ESRI have both a
8494
                // Hotine_Oblique_Mercator_Azimuth_Center If providing a
8495
                // WKT1:GDAL without AXIS, we may wrongly detect it as WKT1:ESRI
8496
                // and skip the rectified_grid_angle parameter cf
8497
                // https://github.com/OSGeo/PROJ/issues/3279
8498
1.31k
                ci_find(wkt, "PARAMETER[\"rectified_grid_angle") ==
8499
1.31k
                    std::string::npos) {
8500
1.31k
                return WKTGuessedDialect::WKT1_ESRI;
8501
1.31k
            }
8502
8503
281
            return WKTGuessedDialect::WKT1_GDAL;
8504
1.59k
        }
8505
10.8k
    }
8506
8507
901
    const std::string *const wkt2_2019_only_keywords[] = {
8508
901
        &WKTConstants::GEOGCRS,
8509
        // contained in previous one
8510
        // &WKTConstants::BASEGEOGCRS,
8511
901
        &WKTConstants::CONCATENATEDOPERATION, &WKTConstants::USAGE,
8512
901
        &WKTConstants::DYNAMIC, &WKTConstants::FRAMEEPOCH, &WKTConstants::MODEL,
8513
901
        &WKTConstants::VELOCITYGRID, &WKTConstants::ENSEMBLE,
8514
901
        &WKTConstants::DERIVEDPROJCRS, &WKTConstants::BASEPROJCRS,
8515
901
        &WKTConstants::GEOGRAPHICCRS, &WKTConstants::TRF, &WKTConstants::VRF,
8516
901
        &WKTConstants::POINTMOTIONOPERATION};
8517
8518
12.1k
    for (const auto &pointerKeyword : wkt2_2019_only_keywords) {
8519
12.1k
        auto pos = ci_find(wkt, *pointerKeyword);
8520
12.1k
        if (pos != std::string::npos &&
8521
277
            wkt[pos + pointerKeyword->size()] == '[') {
8522
226
            return WKTGuessedDialect::WKT2_2019;
8523
226
        }
8524
12.1k
    }
8525
675
    static const char *const wkt2_2019_only_substrings[] = {
8526
675
        "CS[TemporalDateTime,",
8527
675
        "CS[TemporalCount,",
8528
675
        "CS[TemporalMeasure,",
8529
675
    };
8530
2.00k
    for (const auto &substrings : wkt2_2019_only_substrings) {
8531
2.00k
        if (ci_find(wkt, substrings) != std::string::npos) {
8532
19
            return WKTGuessedDialect::WKT2_2019;
8533
19
        }
8534
2.00k
    }
8535
8536
24.2k
    for (const auto &wktConstant : WKTConstants::constants()) {
8537
24.2k
        if (ci_starts_with(wkt, wktConstant)) {
8538
657
            for (auto wktPtr = wkt.c_str() + wktConstant.size();
8539
959
                 *wktPtr != '\0'; ++wktPtr) {
8540
959
                if (isspace(static_cast<unsigned char>(*wktPtr)))
8541
302
                    continue;
8542
657
                if (*wktPtr == '[') {
8543
656
                    return WKTGuessedDialect::WKT2_2015;
8544
656
                }
8545
1
                break;
8546
657
            }
8547
657
        }
8548
24.2k
    }
8549
8550
0
    return WKTGuessedDialect::NOT_WKT;
8551
656
}
8552
8553
// ---------------------------------------------------------------------------
8554
8555
//! @cond Doxygen_Suppress
8556
FormattingException::FormattingException(const char *message)
8557
206
    : Exception(message) {}
8558
8559
// ---------------------------------------------------------------------------
8560
8561
FormattingException::FormattingException(const std::string &message)
8562
461
    : Exception(message) {}
8563
8564
// ---------------------------------------------------------------------------
8565
8566
0
FormattingException::FormattingException(const FormattingException &) = default;
8567
8568
// ---------------------------------------------------------------------------
8569
8570
667
FormattingException::~FormattingException() = default;
8571
8572
// ---------------------------------------------------------------------------
8573
8574
3
void FormattingException::Throw(const char *msg) {
8575
3
    throw FormattingException(msg);
8576
3
}
8577
8578
// ---------------------------------------------------------------------------
8579
8580
0
void FormattingException::Throw(const std::string &msg) {
8581
0
    throw FormattingException(msg);
8582
0
}
8583
8584
// ---------------------------------------------------------------------------
8585
8586
4.99k
ParsingException::ParsingException(const char *message) : Exception(message) {}
8587
8588
// ---------------------------------------------------------------------------
8589
8590
ParsingException::ParsingException(const std::string &message)
8591
3.57k
    : Exception(message) {}
8592
8593
// ---------------------------------------------------------------------------
8594
8595
0
ParsingException::ParsingException(const ParsingException &) = default;
8596
8597
// ---------------------------------------------------------------------------
8598
8599
8.57k
ParsingException::~ParsingException() = default;
8600
8601
// ---------------------------------------------------------------------------
8602
8603
1.27M
IPROJStringExportable::~IPROJStringExportable() = default;
8604
8605
// ---------------------------------------------------------------------------
8606
8607
std::string IPROJStringExportable::exportToPROJString(
8608
113k
    PROJStringFormatter *formatter) const {
8609
113k
    const bool bIsCRS = dynamic_cast<const crs::CRS *>(this) != nullptr;
8610
113k
    if (bIsCRS) {
8611
0
        formatter->setCRSExport(true);
8612
0
    }
8613
113k
    _exportToPROJString(formatter);
8614
113k
    if (formatter->getAddNoDefs() && bIsCRS) {
8615
0
        if (!formatter->hasParam("no_defs")) {
8616
0
            formatter->addParam("no_defs");
8617
0
        }
8618
0
    }
8619
113k
    if (bIsCRS) {
8620
0
        if (!formatter->hasParam("type")) {
8621
0
            formatter->addParam("type", "crs");
8622
0
        }
8623
0
        formatter->setCRSExport(false);
8624
0
    }
8625
113k
    return formatter->toString();
8626
113k
}
8627
//! @endcond
8628
8629
// ---------------------------------------------------------------------------
8630
8631
//! @cond Doxygen_Suppress
8632
8633
struct Step {
8634
    std::string name{};
8635
    bool isInit = false;
8636
    bool inverted{false};
8637
8638
    struct KeyValue {
8639
        std::string key{};
8640
        std::string value{};
8641
        bool usedByParser = false; // only for PROJStringParser used
8642
8643
3.72M
        explicit KeyValue(const std::string &keyIn) : key(keyIn) {}
8644
8645
        KeyValue(const char *keyIn, const std::string &valueIn);
8646
8647
        KeyValue(const std::string &keyIn, const std::string &valueIn)
8648
7.08M
            : key(keyIn), value(valueIn) {}
8649
8650
        // cppcheck-suppress functionStatic
8651
5.69M
        bool keyEquals(const char *otherKey) const noexcept {
8652
5.69M
            return key == otherKey;
8653
5.69M
        }
8654
8655
        // cppcheck-suppress functionStatic
8656
1.78M
        bool equals(const char *otherKey, const char *otherVal) const noexcept {
8657
1.78M
            return key == otherKey && value == otherVal;
8658
1.78M
        }
8659
8660
403
        bool operator==(const KeyValue &other) const noexcept {
8661
403
            return key == other.key && value == other.value;
8662
403
        }
8663
8664
109k
        bool operator!=(const KeyValue &other) const noexcept {
8665
109k
            return key != other.key || value != other.value;
8666
109k
        }
8667
    };
8668
8669
    std::vector<KeyValue> paramValues{};
8670
8671
74.0k
    bool hasKey(const char *keyName) const {
8672
663k
        for (const auto &kv : paramValues) {
8673
663k
            if (kv.key == keyName) {
8674
54
                return true;
8675
54
            }
8676
663k
        }
8677
74.0k
        return false;
8678
74.0k
    }
8679
};
8680
8681
Step::KeyValue::KeyValue(const char *keyIn, const std::string &valueIn)
8682
82.3k
    : key(keyIn), value(valueIn) {}
8683
8684
struct PROJStringFormatter::Private {
8685
    PROJStringFormatter::Convention convention_ =
8686
        PROJStringFormatter::Convention::PROJ_5;
8687
    std::vector<double> toWGS84Parameters_{};
8688
    std::string vDatumExtension_{};
8689
    std::string geoidCRSValue_{};
8690
    std::string hDatumExtension_{};
8691
    crs::GeographicCRSPtr geogCRSOfCompoundCRS_{};
8692
8693
    std::list<Step> steps_{};
8694
    std::vector<Step::KeyValue> globalParamValues_{};
8695
8696
    struct InversionStackElt {
8697
        std::list<Step>::iterator startIter{};
8698
        bool iterValid = false;
8699
        bool currentInversionState = false;
8700
    };
8701
    std::vector<InversionStackElt> inversionStack_{InversionStackElt()};
8702
    bool omitProjLongLatIfPossible_ = false;
8703
    std::vector<bool> omitZUnitConversion_{false};
8704
    std::vector<bool> omitHorizontalConversionInVertTransformation_{false};
8705
    DatabaseContextPtr dbContext_{};
8706
    bool useApproxTMerc_ = false;
8707
    bool addNoDefs_ = true;
8708
    bool coordOperationOptimizations_ = false;
8709
    bool crsExport_ = false;
8710
    bool legacyCRSToCRSContext_ = false;
8711
    bool multiLine_ = false;
8712
    bool normalizeOutput_ = false;
8713
    int indentWidth_ = 2;
8714
    int indentLevel_ = 0;
8715
    int maxLineLength_ = 80;
8716
8717
    std::string result_{};
8718
8719
    // cppcheck-suppress functionStatic
8720
    void appendToResult(const char *str);
8721
8722
    // cppcheck-suppress functionStatic
8723
    void addStep();
8724
};
8725
8726
//! @endcond
8727
8728
// ---------------------------------------------------------------------------
8729
8730
//! @cond Doxygen_Suppress
8731
PROJStringFormatter::PROJStringFormatter(Convention conventionIn,
8732
                                         const DatabaseContextPtr &dbContext)
8733
377k
    : d(std::make_unique<Private>()) {
8734
377k
    d->convention_ = conventionIn;
8735
377k
    d->dbContext_ = dbContext;
8736
377k
}
8737
//! @endcond
8738
8739
// ---------------------------------------------------------------------------
8740
8741
//! @cond Doxygen_Suppress
8742
377k
PROJStringFormatter::~PROJStringFormatter() = default;
8743
//! @endcond
8744
8745
// ---------------------------------------------------------------------------
8746
8747
/** \brief Constructs a new formatter.
8748
 *
8749
 * A formatter can be used only once (its internal state is mutated)
8750
 *
8751
 * Its default behavior can be adjusted with the different setters.
8752
 *
8753
 * @param conventionIn PROJ string flavor. Defaults to Convention::PROJ_5
8754
 * @param dbContext Database context (can help to find alternative grid names).
8755
 * May be nullptr
8756
 * @return new formatter.
8757
 */
8758
PROJStringFormatterNNPtr
8759
PROJStringFormatter::create(Convention conventionIn,
8760
377k
                            DatabaseContextPtr dbContext) {
8761
377k
    return NN_NO_CHECK(PROJStringFormatter::make_unique<PROJStringFormatter>(
8762
377k
        conventionIn, dbContext));
8763
377k
}
8764
8765
// ---------------------------------------------------------------------------
8766
8767
/** \brief Set whether approximate Transverse Mercator or UTM should be used */
8768
0
void PROJStringFormatter::setUseApproxTMerc(bool flag) {
8769
0
    d->useApproxTMerc_ = flag;
8770
0
}
8771
8772
// ---------------------------------------------------------------------------
8773
8774
/** \brief Whether to use multi line output or not. */
8775
PROJStringFormatter &
8776
0
PROJStringFormatter::setMultiLine(bool multiLine) noexcept {
8777
0
    d->multiLine_ = multiLine;
8778
0
    return *this;
8779
0
}
8780
8781
// ---------------------------------------------------------------------------
8782
8783
/** \brief Set number of spaces for each indentation level (defaults to 2).
8784
 */
8785
PROJStringFormatter &
8786
0
PROJStringFormatter::setIndentationWidth(int width) noexcept {
8787
0
    d->indentWidth_ = width;
8788
0
    return *this;
8789
0
}
8790
8791
// ---------------------------------------------------------------------------
8792
8793
/** \brief Set the maximum size of a line (when multiline output is enable).
8794
 * Can be set to 0 for unlimited length.
8795
 */
8796
PROJStringFormatter &
8797
0
PROJStringFormatter::setMaxLineLength(int maxLineLength) noexcept {
8798
0
    d->maxLineLength_ = maxLineLength;
8799
0
    return *this;
8800
0
}
8801
8802
// ---------------------------------------------------------------------------
8803
8804
/** \brief Returns the PROJ string. */
8805
254k
const std::string &PROJStringFormatter::toString() const {
8806
8807
254k
    assert(d->inversionStack_.size() == 1);
8808
8809
254k
    d->result_.clear();
8810
8811
254k
    auto &steps = d->steps_;
8812
8813
254k
    if (d->normalizeOutput_) {
8814
        // Sort +key=value options of each step in lexicographic order.
8815
19.5k
        for (auto &step : steps) {
8816
17.2k
            std::sort(step.paramValues.begin(), step.paramValues.end(),
8817
12.1M
                      [](const Step::KeyValue &a, const Step::KeyValue &b) {
8818
12.1M
                          return a.key < b.key;
8819
12.1M
                      });
8820
17.2k
        }
8821
19.5k
    }
8822
8823
3.35M
    for (auto iter = steps.begin(); iter != steps.end();) {
8824
        // Remove no-op helmert
8825
3.09M
        auto &step = *iter;
8826
3.09M
        const auto paramCount = step.paramValues.size();
8827
3.09M
        if (step.name == "helmert" && (paramCount == 3 || paramCount == 8) &&
8828
144k
            step.paramValues[0].equals("x", "0") &&
8829
24.4k
            step.paramValues[1].equals("y", "0") &&
8830
15.4k
            step.paramValues[2].equals("z", "0") &&
8831
15.3k
            (paramCount == 3 ||
8832
3.47k
             (step.paramValues[3].equals("rx", "0") &&
8833
3.00k
              step.paramValues[4].equals("ry", "0") &&
8834
3.00k
              step.paramValues[5].equals("rz", "0") &&
8835
2.99k
              step.paramValues[6].equals("s", "0") &&
8836
14.8k
              step.paramValues[7].keyEquals("convention")))) {
8837
14.8k
            iter = steps.erase(iter);
8838
3.08M
        } else if (d->coordOperationOptimizations_ &&
8839
2.68M
                   step.name == "unitconvert" && paramCount == 2 &&
8840
774k
                   step.paramValues[0].keyEquals("xy_in") &&
8841
763k
                   step.paramValues[1].keyEquals("xy_out") &&
8842
762k
                   step.paramValues[0].value == step.paramValues[1].value) {
8843
24
            iter = steps.erase(iter);
8844
3.08M
        } else if (step.name == "push" && step.inverted) {
8845
29.3k
            step.name = "pop";
8846
29.3k
            step.inverted = false;
8847
29.3k
            ++iter;
8848
3.05M
        } else if (step.name == "pop" && step.inverted) {
8849
36.7k
            step.name = "push";
8850
36.7k
            step.inverted = false;
8851
36.7k
            ++iter;
8852
3.01M
        } else if (step.name == "noop" && steps.size() > 1) {
8853
1.41k
            iter = steps.erase(iter);
8854
3.01M
        } else {
8855
3.01M
            ++iter;
8856
3.01M
        }
8857
3.09M
    }
8858
8859
3.08M
    for (auto &step : steps) {
8860
3.08M
        if (!step.inverted) {
8861
1.72M
            continue;
8862
1.72M
        }
8863
8864
1.36M
        const auto paramCount = step.paramValues.size();
8865
8866
        // axisswap order=2,1 (or 1,-2) is its own inverse
8867
1.36M
        if (step.name == "axisswap" && paramCount == 1 &&
8868
299k
            (step.paramValues[0].equals("order", "2,1") ||
8869
295k
             step.paramValues[0].equals("order", "1,-2"))) {
8870
295k
            step.inverted = false;
8871
295k
            continue;
8872
295k
        }
8873
8874
        // axisswap inv order=2,-1 ==> axisswap order -2,1
8875
1.06M
        if (step.name == "axisswap" && paramCount == 1 &&
8876
3.70k
            step.paramValues[0].equals("order", "2,-1")) {
8877
115
            step.inverted = false;
8878
115
            step.paramValues[0] = Step::KeyValue("order", "-2,1");
8879
115
            continue;
8880
115
        }
8881
8882
        // axisswap order=1,2,-3 is its own inverse
8883
1.06M
        if (step.name == "axisswap" && paramCount == 1 &&
8884
3.59k
            step.paramValues[0].equals("order", "1,2,-3")) {
8885
0
            step.inverted = false;
8886
0
            continue;
8887
0
        }
8888
8889
        // handle unitconvert inverse
8890
1.06M
        if (step.name == "unitconvert" && paramCount == 2 &&
8891
420k
            step.paramValues[0].keyEquals("xy_in") &&
8892
410k
            step.paramValues[1].keyEquals("xy_out")) {
8893
410k
            std::swap(step.paramValues[0].value, step.paramValues[1].value);
8894
410k
            step.inverted = false;
8895
410k
            continue;
8896
410k
        }
8897
8898
656k
        if (step.name == "unitconvert" && paramCount == 2 &&
8899
10.1k
            step.paramValues[0].keyEquals("z_in") &&
8900
7.91k
            step.paramValues[1].keyEquals("z_out")) {
8901
7.32k
            std::swap(step.paramValues[0].value, step.paramValues[1].value);
8902
7.32k
            step.inverted = false;
8903
7.32k
            continue;
8904
7.32k
        }
8905
8906
648k
        if (step.name == "unitconvert" && paramCount == 4 &&
8907
39.3k
            step.paramValues[0].keyEquals("xy_in") &&
8908
38.0k
            step.paramValues[1].keyEquals("z_in") &&
8909
37.5k
            step.paramValues[2].keyEquals("xy_out") &&
8910
37.4k
            step.paramValues[3].keyEquals("z_out")) {
8911
37.3k
            std::swap(step.paramValues[0].value, step.paramValues[2].value);
8912
37.3k
            std::swap(step.paramValues[1].value, step.paramValues[3].value);
8913
37.3k
            step.inverted = false;
8914
37.3k
            continue;
8915
37.3k
        }
8916
648k
    }
8917
8918
254k
    {
8919
254k
        auto iterCur = steps.begin();
8920
254k
        if (iterCur != steps.end()) {
8921
243k
            ++iterCur;
8922
243k
        }
8923
3.60M
        while (iterCur != steps.end()) {
8924
8925
3.35M
            assert(iterCur != steps.begin());
8926
3.35M
            auto iterPrev = std::prev(iterCur);
8927
3.35M
            auto &prevStep = *iterPrev;
8928
3.35M
            auto &curStep = *iterCur;
8929
8930
3.35M
            const auto curStepParamCount = curStep.paramValues.size();
8931
3.35M
            const auto prevStepParamCount = prevStep.paramValues.size();
8932
8933
3.35M
            const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() {
8934
697k
                iterCur = steps.erase(iterPrev, std::next(iterCur));
8935
697k
                if (iterCur != steps.begin())
8936
622k
                    iterCur = std::prev(iterCur);
8937
697k
                if (iterCur == steps.begin() && iterCur != steps.end())
8938
145k
                    ++iterCur;
8939
697k
            };
8940
8941
            // longlat (or its inverse) with ellipsoid only is a no-op
8942
            // do that only for an internal step
8943
3.35M
            if (std::next(iterCur) != steps.end() &&
8944
3.12M
                curStep.name == "longlat" && curStepParamCount == 1 &&
8945
347k
                curStep.paramValues[0].keyEquals("ellps")) {
8946
332k
                iterCur = steps.erase(iterCur);
8947
332k
                continue;
8948
332k
            }
8949
8950
            // push v_x followed by pop v_x is a no-op.
8951
3.01M
            if (curStep.name == "pop" && prevStep.name == "push" &&
8952
10.1k
                !curStep.inverted && !prevStep.inverted &&
8953
10.1k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
8954
9.15k
                curStep.paramValues[0].key == prevStep.paramValues[0].key) {
8955
9.07k
                deletePrevAndCurIter();
8956
9.07k
                continue;
8957
9.07k
            }
8958
8959
            // pop v_x followed by push v_x is, almost, a no-op. For our
8960
            // purposes,
8961
            // we consider it as a no-op for better pipeline optimizations.
8962
3.00M
            if (curStep.name == "push" && prevStep.name == "pop" &&
8963
7.86k
                !curStep.inverted && !prevStep.inverted &&
8964
7.86k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
8965
6.63k
                curStep.paramValues[0].key == prevStep.paramValues[0].key) {
8966
6.47k
                deletePrevAndCurIter();
8967
6.47k
                continue;
8968
6.47k
            }
8969
8970
            // unitconvert (xy) followed by its inverse is a no-op
8971
3.00M
            if (curStep.name == "unitconvert" &&
8972
1.13M
                prevStep.name == "unitconvert" && !curStep.inverted &&
8973
365k
                !prevStep.inverted && curStepParamCount == 2 &&
8974
329k
                prevStepParamCount == 2 &&
8975
291k
                curStep.paramValues[0].keyEquals("xy_in") &&
8976
287k
                prevStep.paramValues[0].keyEquals("xy_in") &&
8977
279k
                curStep.paramValues[1].keyEquals("xy_out") &&
8978
279k
                prevStep.paramValues[1].keyEquals("xy_out") &&
8979
279k
                curStep.paramValues[0].value == prevStep.paramValues[1].value &&
8980
279k
                curStep.paramValues[1].value == prevStep.paramValues[0].value) {
8981
279k
                deletePrevAndCurIter();
8982
279k
                continue;
8983
279k
            }
8984
8985
            // unitconvert (z) followed by its inverse is a no-op
8986
2.72M
            if (curStep.name == "unitconvert" &&
8987
851k
                prevStep.name == "unitconvert" && !curStep.inverted &&
8988
85.8k
                !prevStep.inverted && curStepParamCount == 2 &&
8989
49.6k
                prevStepParamCount == 2 &&
8990
12.4k
                curStep.paramValues[0].keyEquals("z_in") &&
8991
4.18k
                prevStep.paramValues[0].keyEquals("z_in") &&
8992
1.99k
                curStep.paramValues[1].keyEquals("z_out") &&
8993
1.73k
                prevStep.paramValues[1].keyEquals("z_out") &&
8994
1.62k
                curStep.paramValues[0].value == prevStep.paramValues[1].value &&
8995
1.51k
                curStep.paramValues[1].value == prevStep.paramValues[0].value) {
8996
1.24k
                deletePrevAndCurIter();
8997
1.24k
                continue;
8998
1.24k
            }
8999
9000
            // unitconvert (xyz) followed by its inverse is a no-op
9001
2.72M
            if (curStep.name == "unitconvert" &&
9002
849k
                prevStep.name == "unitconvert" && !curStep.inverted &&
9003
84.6k
                !prevStep.inverted && curStepParamCount == 4 &&
9004
33.5k
                prevStepParamCount == 4 &&
9005
16.2k
                curStep.paramValues[0].keyEquals("xy_in") &&
9006
15.9k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9007
15.7k
                curStep.paramValues[1].keyEquals("z_in") &&
9008
15.5k
                prevStep.paramValues[1].keyEquals("z_in") &&
9009
15.5k
                curStep.paramValues[2].keyEquals("xy_out") &&
9010
15.5k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9011
15.4k
                curStep.paramValues[3].keyEquals("z_out") &&
9012
15.4k
                prevStep.paramValues[3].keyEquals("z_out") &&
9013
15.3k
                curStep.paramValues[0].value == prevStep.paramValues[2].value &&
9014
14.6k
                curStep.paramValues[1].value == prevStep.paramValues[3].value &&
9015
14.6k
                curStep.paramValues[2].value == prevStep.paramValues[0].value &&
9016
14.5k
                curStep.paramValues[3].value == prevStep.paramValues[1].value) {
9017
13.0k
                deletePrevAndCurIter();
9018
13.0k
                continue;
9019
13.0k
            }
9020
9021
2.70M
            const auto deletePrevIter = [&steps, &iterPrev, &iterCur]() {
9022
19.7k
                steps.erase(iterPrev, iterCur);
9023
19.7k
                if (iterCur != steps.begin())
9024
17.5k
                    iterCur = std::prev(iterCur);
9025
19.7k
                if (iterCur == steps.begin())
9026
3.54k
                    ++iterCur;
9027
19.7k
            };
9028
9029
            // combine unitconvert (xy) and unitconvert (z)
9030
2.70M
            bool changeDone = false;
9031
8.11M
            for (int k = 0; k < 2; ++k) {
9032
5.41M
                auto &first = (k == 0) ? curStep : prevStep;
9033
5.41M
                auto &second = (k == 0) ? prevStep : curStep;
9034
5.41M
                if (first.name == "unitconvert" &&
9035
1.37M
                    second.name == "unitconvert" && !first.inverted &&
9036
141k
                    !second.inverted && first.paramValues.size() == 2 &&
9037
75.4k
                    second.paramValues.size() == 2 &&
9038
20.6k
                    second.paramValues[0].keyEquals("xy_in") &&
9039
10.7k
                    second.paramValues[1].keyEquals("xy_out") &&
9040
9.87k
                    first.paramValues[0].keyEquals("z_in") &&
9041
9.09k
                    first.paramValues[1].keyEquals("z_out")) {
9042
9043
8.73k
                    const std::string xy_in(second.paramValues[0].value);
9044
8.73k
                    const std::string xy_out(second.paramValues[1].value);
9045
8.73k
                    const std::string z_in(first.paramValues[0].value);
9046
8.73k
                    const std::string z_out(first.paramValues[1].value);
9047
9048
8.73k
                    iterCur->paramValues.clear();
9049
8.73k
                    iterCur->paramValues.emplace_back(
9050
8.73k
                        Step::KeyValue("xy_in", xy_in));
9051
8.73k
                    iterCur->paramValues.emplace_back(
9052
8.73k
                        Step::KeyValue("z_in", z_in));
9053
8.73k
                    iterCur->paramValues.emplace_back(
9054
8.73k
                        Step::KeyValue("xy_out", xy_out));
9055
8.73k
                    iterCur->paramValues.emplace_back(
9056
8.73k
                        Step::KeyValue("z_out", z_out));
9057
9058
8.73k
                    deletePrevIter();
9059
8.73k
                    changeDone = true;
9060
8.73k
                    break;
9061
8.73k
                }
9062
5.41M
            }
9063
2.70M
            if (changeDone) {
9064
8.73k
                continue;
9065
8.73k
            }
9066
9067
            // +step +proj=unitconvert +xy_in=X1 +xy_out=X2
9068
            //  +step +proj=unitconvert +xy_in=X2 +z_in=Z1 +xy_out=X1 +z_out=Z2
9069
            // ==> step +proj=unitconvert +z_in=Z1 +z_out=Z2
9070
8.03M
            for (int k = 0; k < 2; ++k) {
9071
5.38M
                auto &first = (k == 0) ? curStep : prevStep;
9072
5.38M
                auto &second = (k == 0) ? prevStep : curStep;
9073
5.38M
                if (first.name == "unitconvert" &&
9074
1.34M
                    second.name == "unitconvert" && !first.inverted &&
9075
109k
                    !second.inverted && first.paramValues.size() == 4 &&
9076
60.4k
                    second.paramValues.size() == 2 &&
9077
53.8k
                    first.paramValues[0].keyEquals("xy_in") &&
9078
52.7k
                    first.paramValues[1].keyEquals("z_in") &&
9079
52.2k
                    first.paramValues[2].keyEquals("xy_out") &&
9080
52.1k
                    first.paramValues[3].keyEquals("z_out") &&
9081
52.0k
                    second.paramValues[0].keyEquals("xy_in") &&
9082
50.7k
                    second.paramValues[1].keyEquals("xy_out") &&
9083
50.7k
                    first.paramValues[0].value == second.paramValues[1].value &&
9084
49.8k
                    first.paramValues[2].value == second.paramValues[0].value) {
9085
49.8k
                    const std::string z_in(first.paramValues[1].value);
9086
49.8k
                    const std::string z_out(first.paramValues[3].value);
9087
49.8k
                    if (z_in != z_out) {
9088
8.54k
                        iterCur->paramValues.clear();
9089
8.54k
                        iterCur->paramValues.emplace_back(
9090
8.54k
                            Step::KeyValue("z_in", z_in));
9091
8.54k
                        iterCur->paramValues.emplace_back(
9092
8.54k
                            Step::KeyValue("z_out", z_out));
9093
8.54k
                        deletePrevIter();
9094
41.3k
                    } else {
9095
41.3k
                        deletePrevAndCurIter();
9096
41.3k
                    }
9097
49.8k
                    changeDone = true;
9098
49.8k
                    break;
9099
49.8k
                }
9100
5.38M
            }
9101
2.69M
            if (changeDone) {
9102
49.8k
                continue;
9103
49.8k
            }
9104
9105
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9106
            // +step +proj=unitconvert +z_in=Z2 +z_out=Z3
9107
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
9108
            // +z_out=Z3
9109
2.64M
            if (prevStep.name == "unitconvert" &&
9110
482k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9111
12.8k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9112
5.58k
                curStep.paramValues.size() == 2 &&
9113
2.27k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9114
1.65k
                prevStep.paramValues[1].keyEquals("z_in") &&
9115
1.42k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9116
1.37k
                prevStep.paramValues[3].keyEquals("z_out") &&
9117
1.33k
                curStep.paramValues[0].keyEquals("z_in") &&
9118
808
                curStep.paramValues[1].keyEquals("z_out") &&
9119
710
                prevStep.paramValues[3].value == curStep.paramValues[0].value) {
9120
602
                const std::string xy_in(prevStep.paramValues[0].value);
9121
602
                const std::string z_in(prevStep.paramValues[1].value);
9122
602
                const std::string xy_out(prevStep.paramValues[2].value);
9123
602
                const std::string z_out(curStep.paramValues[1].value);
9124
9125
602
                iterCur->paramValues.clear();
9126
602
                iterCur->paramValues.emplace_back(
9127
602
                    Step::KeyValue("xy_in", xy_in));
9128
602
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9129
602
                iterCur->paramValues.emplace_back(
9130
602
                    Step::KeyValue("xy_out", xy_out));
9131
602
                iterCur->paramValues.emplace_back(
9132
602
                    Step::KeyValue("z_out", z_out));
9133
9134
602
                deletePrevIter();
9135
602
                continue;
9136
602
            }
9137
9138
            // +step +proj=unitconvert +z_in=Z1 +z_out=Z2
9139
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X2 +z_out=Z3
9140
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2
9141
            // +z_out=Z3
9142
2.64M
            if (prevStep.name == "unitconvert" &&
9143
481k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9144
12.2k
                !curStep.inverted && prevStep.paramValues.size() == 2 &&
9145
4.62k
                curStep.paramValues.size() == 4 &&
9146
1.70k
                prevStep.paramValues[0].keyEquals("z_in") &&
9147
442
                prevStep.paramValues[1].keyEquals("z_out") &&
9148
386
                curStep.paramValues[0].keyEquals("xy_in") &&
9149
339
                curStep.paramValues[1].keyEquals("z_in") &&
9150
315
                curStep.paramValues[2].keyEquals("xy_out") &&
9151
307
                curStep.paramValues[3].keyEquals("z_out") &&
9152
307
                prevStep.paramValues[1].value == curStep.paramValues[1].value) {
9153
140
                const std::string xy_in(curStep.paramValues[0].value);
9154
140
                const std::string z_in(prevStep.paramValues[0].value);
9155
140
                const std::string xy_out(curStep.paramValues[2].value);
9156
140
                const std::string z_out(curStep.paramValues[3].value);
9157
9158
140
                iterCur->paramValues.clear();
9159
140
                iterCur->paramValues.emplace_back(
9160
140
                    Step::KeyValue("xy_in", xy_in));
9161
140
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9162
140
                iterCur->paramValues.emplace_back(
9163
140
                    Step::KeyValue("xy_out", xy_out));
9164
140
                iterCur->paramValues.emplace_back(
9165
140
                    Step::KeyValue("z_out", z_out));
9166
9167
140
                deletePrevIter();
9168
140
                continue;
9169
140
            }
9170
9171
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9172
            // +step +proj=unitconvert +xy_in=X2 +xy_out=X3
9173
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3
9174
            // +z_out=Z2
9175
2.64M
            if (prevStep.name == "unitconvert" &&
9176
481k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9177
12.0k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9178
4.97k
                curStep.paramValues.size() == 2 &&
9179
1.67k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9180
1.05k
                prevStep.paramValues[1].keyEquals("z_in") &&
9181
825
                prevStep.paramValues[2].keyEquals("xy_out") &&
9182
769
                prevStep.paramValues[3].keyEquals("z_out") &&
9183
736
                curStep.paramValues[0].keyEquals("xy_in") &&
9184
489
                curStep.paramValues[1].keyEquals("xy_out") &&
9185
424
                prevStep.paramValues[2].value == curStep.paramValues[0].value) {
9186
44
                const std::string xy_in(prevStep.paramValues[0].value);
9187
44
                const std::string z_in(prevStep.paramValues[1].value);
9188
44
                const std::string xy_out(curStep.paramValues[1].value);
9189
44
                const std::string z_out(prevStep.paramValues[3].value);
9190
9191
44
                iterCur->paramValues.clear();
9192
44
                iterCur->paramValues.emplace_back(
9193
44
                    Step::KeyValue("xy_in", xy_in));
9194
44
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9195
44
                iterCur->paramValues.emplace_back(
9196
44
                    Step::KeyValue("xy_out", xy_out));
9197
44
                iterCur->paramValues.emplace_back(
9198
44
                    Step::KeyValue("z_out", z_out));
9199
9200
44
                deletePrevIter();
9201
44
                continue;
9202
44
            }
9203
9204
            // clang-format off
9205
            // A bit odd. Used to simplify geog3d_feet -> EPSG:6318+6360
9206
            // of https://github.com/OSGeo/PROJ/issues/3938
9207
            // where we get originally
9208
            // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=rad +z_out=us-ft
9209
            // +step +proj=unitconvert +xy_in=rad +z_in=m +xy_out=deg +z_out=m
9210
            // and want it simplified as:
9211
            // +step +proj=unitconvert +xy_in=deg +z_in=ft +xy_out=deg +z_out=us-ft
9212
            //
9213
            // More generally:
9214
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z2
9215
            // +step +proj=unitconvert +xy_in=X2 +z_in=Z3 +xy_out=X3 +z_out=Z3
9216
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X3 +z_out=Z2
9217
            // clang-format on
9218
2.64M
            if (prevStep.name == "unitconvert" &&
9219
481k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9220
12.0k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9221
4.93k
                curStep.paramValues.size() == 4 &&
9222
3.26k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9223
2.84k
                prevStep.paramValues[1].keyEquals("z_in") &&
9224
2.72k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9225
2.69k
                prevStep.paramValues[3].keyEquals("z_out") &&
9226
2.63k
                curStep.paramValues[0].keyEquals("xy_in") &&
9227
2.46k
                curStep.paramValues[1].keyEquals("z_in") &&
9228
2.41k
                curStep.paramValues[2].keyEquals("xy_out") &&
9229
2.39k
                curStep.paramValues[3].keyEquals("z_out") &&
9230
2.37k
                prevStep.paramValues[2].value == curStep.paramValues[0].value &&
9231
1.66k
                curStep.paramValues[1].value == curStep.paramValues[3].value) {
9232
773
                const std::string xy_in(prevStep.paramValues[0].value);
9233
773
                const std::string z_in(prevStep.paramValues[1].value);
9234
773
                const std::string xy_out(curStep.paramValues[2].value);
9235
773
                const std::string z_out(prevStep.paramValues[3].value);
9236
9237
773
                iterCur->paramValues.clear();
9238
773
                iterCur->paramValues.emplace_back(
9239
773
                    Step::KeyValue("xy_in", xy_in));
9240
773
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9241
773
                iterCur->paramValues.emplace_back(
9242
773
                    Step::KeyValue("xy_out", xy_out));
9243
773
                iterCur->paramValues.emplace_back(
9244
773
                    Step::KeyValue("z_out", z_out));
9245
9246
773
                deletePrevIter();
9247
773
                continue;
9248
773
            }
9249
9250
            // clang-format off
9251
            // Variant of above
9252
            // +step +proj=unitconvert +xy_in=X1 +z_in=Z1 +xy_out=X2 +z_out=Z1
9253
            // +step +proj=unitconvert +xy_in=X2 +z_in=Z2 +xy_out=X3 +z_out=Z3
9254
            // ==> +step +proj=unitconvert +xy_in=X1 +z_in=Z2 +xy_out=X3 +z_out=Z3
9255
            // clang-format on
9256
2.64M
            if (prevStep.name == "unitconvert" &&
9257
480k
                curStep.name == "unitconvert" && !prevStep.inverted &&
9258
11.2k
                !curStep.inverted && prevStep.paramValues.size() == 4 &&
9259
4.16k
                curStep.paramValues.size() == 4 &&
9260
2.49k
                prevStep.paramValues[0].keyEquals("xy_in") &&
9261
2.07k
                prevStep.paramValues[1].keyEquals("z_in") &&
9262
1.95k
                prevStep.paramValues[2].keyEquals("xy_out") &&
9263
1.92k
                prevStep.paramValues[3].keyEquals("z_out") &&
9264
1.85k
                curStep.paramValues[0].keyEquals("xy_in") &&
9265
1.68k
                curStep.paramValues[1].keyEquals("z_in") &&
9266
1.64k
                curStep.paramValues[2].keyEquals("xy_out") &&
9267
1.61k
                curStep.paramValues[3].keyEquals("z_out") &&
9268
1.59k
                prevStep.paramValues[1].value ==
9269
1.59k
                    prevStep.paramValues[3].value &&
9270
1.41k
                curStep.paramValues[0].value == prevStep.paramValues[2].value) {
9271
844
                const std::string xy_in(prevStep.paramValues[0].value);
9272
844
                const std::string z_in(curStep.paramValues[1].value);
9273
844
                const std::string xy_out(curStep.paramValues[2].value);
9274
844
                const std::string z_out(curStep.paramValues[3].value);
9275
9276
844
                iterCur->paramValues.clear();
9277
844
                iterCur->paramValues.emplace_back(
9278
844
                    Step::KeyValue("xy_in", xy_in));
9279
844
                iterCur->paramValues.emplace_back(Step::KeyValue("z_in", z_in));
9280
844
                iterCur->paramValues.emplace_back(
9281
844
                    Step::KeyValue("xy_out", xy_out));
9282
844
                iterCur->paramValues.emplace_back(
9283
844
                    Step::KeyValue("z_out", z_out));
9284
9285
844
                deletePrevIter();
9286
844
                continue;
9287
844
            }
9288
9289
            // unitconvert (1), axisswap order=2,1, unitconvert(2)  ==>
9290
            // axisswap order=2,1, unitconvert (1), unitconvert(2) which
9291
            // will get further optimized by previous case
9292
2.64M
            if (std::next(iterCur) != steps.end() &&
9293
2.42M
                prevStep.name == "unitconvert" && curStep.name == "axisswap" &&
9294
266k
                curStepParamCount == 1 &&
9295
266k
                curStep.paramValues[0].equals("order", "2,1")) {
9296
263k
                auto iterNext = std::next(iterCur);
9297
263k
                auto &nextStep = *iterNext;
9298
263k
                if (nextStep.name == "unitconvert") {
9299
2.71k
                    std::swap(*iterPrev, *iterCur);
9300
2.71k
                    ++iterCur;
9301
2.71k
                    continue;
9302
2.71k
                }
9303
263k
            }
9304
9305
            // axisswap order=2,1 followed by itself is a no-op
9306
2.64M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9307
301k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9308
301k
                curStep.paramValues[0].equals("order", "2,1") &&
9309
300k
                prevStep.paramValues[0].equals("order", "2,1")) {
9310
300k
                deletePrevAndCurIter();
9311
300k
                continue;
9312
300k
            }
9313
9314
            // axisswap order=2,-1 followed by axisswap order=-2,1 is a no-op
9315
2.34M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9316
1.43k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9317
1.20k
                !prevStep.inverted &&
9318
1.05k
                prevStep.paramValues[0].equals("order", "2,-1") &&
9319
54
                !curStep.inverted &&
9320
54
                curStep.paramValues[0].equals("order", "-2,1")) {
9321
54
                deletePrevAndCurIter();
9322
54
                continue;
9323
54
            }
9324
9325
            // axisswap order=2,-1 followed by axisswap order=1,-2 is
9326
            // equivalent to axisswap order=2,1
9327
2.34M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9328
1.37k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9329
1.15k
                !prevStep.inverted &&
9330
1.00k
                prevStep.paramValues[0].equals("order", "2,-1") &&
9331
0
                !curStep.inverted &&
9332
0
                curStep.paramValues[0].equals("order", "1,-2")) {
9333
0
                prevStep.inverted = false;
9334
0
                prevStep.paramValues[0] = Step::KeyValue("order", "2,1");
9335
                // Delete this iter
9336
0
                iterCur = steps.erase(iterCur);
9337
0
                continue;
9338
0
            }
9339
9340
            // axisswap order=2,1 followed by axisswap order=2,-1 is
9341
            // equivalent to axisswap order=1,-2
9342
            // Same for axisswap order=-2,1 followed by axisswap order=2,1
9343
2.34M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9344
1.37k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9345
1.15k
                ((prevStep.paramValues[0].equals("order", "2,1") &&
9346
0
                  !curStep.inverted &&
9347
0
                  curStep.paramValues[0].equals("order", "2,-1")) ||
9348
1.15k
                 (prevStep.paramValues[0].equals("order", "-2,1") &&
9349
43
                  !prevStep.inverted &&
9350
40
                  curStep.paramValues[0].equals("order", "2,1")))) {
9351
9352
18
                prevStep.inverted = false;
9353
18
                prevStep.paramValues[0] = Step::KeyValue("order", "1,-2");
9354
                // Delete this iter
9355
18
                iterCur = steps.erase(iterCur);
9356
18
                continue;
9357
18
            }
9358
9359
            // axisswap order=2,1, unitconvert, axisswap order=2,1 -> can
9360
            // suppress axisswap
9361
2.34M
            if (std::next(iterCur) != steps.end() &&
9362
2.15M
                prevStep.name == "axisswap" && curStep.name == "unitconvert" &&
9363
48.3k
                prevStepParamCount == 1 &&
9364
48.2k
                prevStep.paramValues[0].equals("order", "2,1")) {
9365
43.6k
                auto iterNext = std::next(iterCur);
9366
43.6k
                auto &nextStep = *iterNext;
9367
43.6k
                if (nextStep.name == "axisswap" &&
9368
1.45k
                    nextStep.paramValues.size() == 1 &&
9369
1.44k
                    nextStep.paramValues[0].equals("order", "2,1")) {
9370
1.44k
                    steps.erase(iterPrev);
9371
1.44k
                    steps.erase(iterNext);
9372
                    // Coverity complains about invalid usage of iterCur
9373
                    // due to the above erase(iterNext). To the best of our
9374
                    // understanding, this is a false-positive.
9375
                    // coverity[use_iterator]
9376
1.44k
                    if (iterCur != steps.begin())
9377
1.43k
                        iterCur = std::prev(iterCur);
9378
1.44k
                    if (iterCur == steps.begin())
9379
7
                        ++iterCur;
9380
1.44k
                    continue;
9381
1.44k
                }
9382
43.6k
            }
9383
9384
            // for practical purposes WGS84 and GRS80 ellipsoids are
9385
            // equivalents (cartesian transform between both lead to differences
9386
            // of the order of 1e-14 deg..).
9387
            // No need to do a cart roundtrip for that...
9388
            // and actually IGNF uses the GRS80 definition for the WGS84 datum
9389
2.34M
            if (curStep.name == "cart" && prevStep.name == "cart" &&
9390
37.0k
                curStep.inverted == !prevStep.inverted &&
9391
37.0k
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9392
36.6k
                ((curStep.paramValues[0].equals("ellps", "WGS84") &&
9393
15.5k
                  prevStep.paramValues[0].equals("ellps", "GRS80")) ||
9394
30.2k
                 (curStep.paramValues[0].equals("ellps", "GRS80") &&
9395
8.02k
                  prevStep.paramValues[0].equals("ellps", "WGS84")))) {
9396
8.02k
                deletePrevAndCurIter();
9397
8.02k
                continue;
9398
8.02k
            }
9399
9400
2.33M
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9401
20.5k
                !curStep.inverted && !prevStep.inverted &&
9402
12.0k
                curStepParamCount == 3 &&
9403
9.82k
                curStepParamCount == prevStepParamCount) {
9404
8.73k
                std::map<std::string, double> leftParamsMap;
9405
8.73k
                std::map<std::string, double> rightParamsMap;
9406
8.73k
                try {
9407
25.2k
                    for (const auto &kv : prevStep.paramValues) {
9408
25.2k
                        leftParamsMap[kv.key] = c_locale_stod(kv.value);
9409
25.2k
                    }
9410
24.6k
                    for (const auto &kv : curStep.paramValues) {
9411
24.6k
                        rightParamsMap[kv.key] = c_locale_stod(kv.value);
9412
24.6k
                    }
9413
8.73k
                } catch (const std::invalid_argument &) {
9414
530
                    break;
9415
530
                }
9416
8.20k
                const std::string x("x");
9417
8.20k
                const std::string y("y");
9418
8.20k
                const std::string z("z");
9419
8.20k
                if (leftParamsMap.find(x) != leftParamsMap.end() &&
9420
8.13k
                    leftParamsMap.find(y) != leftParamsMap.end() &&
9421
8.09k
                    leftParamsMap.find(z) != leftParamsMap.end() &&
9422
7.66k
                    rightParamsMap.find(x) != rightParamsMap.end() &&
9423
7.65k
                    rightParamsMap.find(y) != rightParamsMap.end() &&
9424
7.59k
                    rightParamsMap.find(z) != rightParamsMap.end()) {
9425
9426
7.55k
                    const double xSum = leftParamsMap[x] + rightParamsMap[x];
9427
7.55k
                    const double ySum = leftParamsMap[y] + rightParamsMap[y];
9428
7.55k
                    const double zSum = leftParamsMap[z] + rightParamsMap[z];
9429
7.55k
                    if (xSum == 0.0 && ySum == 0.0 && zSum == 0.0) {
9430
763
                        deletePrevAndCurIter();
9431
6.78k
                    } else {
9432
6.78k
                        prevStep.paramValues[0] =
9433
6.78k
                            Step::KeyValue("x", internal::toString(xSum));
9434
6.78k
                        prevStep.paramValues[1] =
9435
6.78k
                            Step::KeyValue("y", internal::toString(ySum));
9436
6.78k
                        prevStep.paramValues[2] =
9437
6.78k
                            Step::KeyValue("z", internal::toString(zSum));
9438
9439
                        // Delete this iter
9440
6.78k
                        iterCur = steps.erase(iterCur);
9441
6.78k
                    }
9442
7.55k
                    continue;
9443
7.55k
                }
9444
8.20k
            }
9445
9446
            // Helmert followed by its inverse is a no-op
9447
2.32M
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9448
12.4k
                !curStep.inverted && !prevStep.inverted &&
9449
3.96k
                curStepParamCount == prevStepParamCount) {
9450
1.63k
                std::set<std::string> leftParamsSet;
9451
1.63k
                std::set<std::string> rightParamsSet;
9452
1.63k
                std::map<std::string, std::string> leftParamsMap;
9453
1.63k
                std::map<std::string, std::string> rightParamsMap;
9454
7.86k
                for (const auto &kv : prevStep.paramValues) {
9455
7.86k
                    leftParamsSet.insert(kv.key);
9456
7.86k
                    leftParamsMap[kv.key] = kv.value;
9457
7.86k
                }
9458
7.86k
                for (const auto &kv : curStep.paramValues) {
9459
7.86k
                    rightParamsSet.insert(kv.key);
9460
7.86k
                    rightParamsMap[kv.key] = kv.value;
9461
7.86k
                }
9462
1.63k
                if (leftParamsSet == rightParamsSet) {
9463
541
                    bool doErase = true;
9464
541
                    try {
9465
965
                        for (const auto &param : leftParamsSet) {
9466
965
                            if (param == "convention" || param == "t_epoch" ||
9467
758
                                param == "t_obs") {
9468
207
                                if (leftParamsMap[param] !=
9469
207
                                    rightParamsMap[param]) {
9470
157
                                    doErase = false;
9471
157
                                    break;
9472
157
                                }
9473
758
                            } else if (c_locale_stod(leftParamsMap[param]) !=
9474
758
                                       -c_locale_stod(rightParamsMap[param])) {
9475
163
                                doErase = false;
9476
163
                                break;
9477
163
                            }
9478
965
                        }
9479
541
                    } catch (const std::invalid_argument &) {
9480
211
                        break;
9481
211
                    }
9482
330
                    if (doErase) {
9483
10
                        deletePrevAndCurIter();
9484
10
                        continue;
9485
10
                    }
9486
330
                }
9487
1.63k
            }
9488
9489
            // The following should be optimized as a no-op
9490
            // +step +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=-0.35
9491
            // +rz=-0.736 +s=0 +convention=coordinate_frame
9492
            // +step +inv +proj=helmert +x=25 +y=-141 +z=-78.5 +rx=0 +ry=0.35
9493
            // +rz=0.736 +s=0 +convention=position_vector
9494
2.32M
            if (curStep.name == "helmert" && prevStep.name == "helmert" &&
9495
12.2k
                ((curStep.inverted && !prevStep.inverted) ||
9496
6.95k
                 (!curStep.inverted && prevStep.inverted)) &&
9497
6.36k
                curStepParamCount == prevStepParamCount) {
9498
3.41k
                std::set<std::string> leftParamsSet;
9499
3.41k
                std::set<std::string> rightParamsSet;
9500
3.41k
                std::map<std::string, std::string> leftParamsMap;
9501
3.41k
                std::map<std::string, std::string> rightParamsMap;
9502
22.8k
                for (const auto &kv : prevStep.paramValues) {
9503
22.8k
                    leftParamsSet.insert(kv.key);
9504
22.8k
                    leftParamsMap[kv.key] = kv.value;
9505
22.8k
                }
9506
22.8k
                for (const auto &kv : curStep.paramValues) {
9507
22.8k
                    rightParamsSet.insert(kv.key);
9508
22.8k
                    rightParamsMap[kv.key] = kv.value;
9509
22.8k
                }
9510
3.41k
                if (leftParamsSet == rightParamsSet) {
9511
3.12k
                    bool doErase = true;
9512
3.12k
                    try {
9513
6.63k
                        for (const auto &param : leftParamsSet) {
9514
6.63k
                            if (param == "convention") {
9515
                                // Convention must be different
9516
1.19k
                                if (leftParamsMap[param] ==
9517
1.19k
                                    rightParamsMap[param]) {
9518
1.16k
                                    doErase = false;
9519
1.16k
                                    break;
9520
1.16k
                                }
9521
5.43k
                            } else if (param == "rx" || param == "ry" ||
9522
5.28k
                                       param == "rz" || param == "drx" ||
9523
5.27k
                                       param == "dry" || param == "drz") {
9524
                                // Rotational parameters should have opposite
9525
                                // value
9526
161
                                if (c_locale_stod(leftParamsMap[param]) !=
9527
161
                                    -c_locale_stod(rightParamsMap[param])) {
9528
71
                                    doErase = false;
9529
71
                                    break;
9530
71
                                }
9531
5.27k
                            } else {
9532
                                // Non rotational parameters should have the
9533
                                // same value
9534
5.27k
                                if (leftParamsMap[param] !=
9535
5.27k
                                    rightParamsMap[param]) {
9536
1.50k
                                    doErase = false;
9537
1.50k
                                    break;
9538
1.50k
                                }
9539
5.27k
                            }
9540
6.63k
                        }
9541
3.12k
                    } catch (const std::invalid_argument &) {
9542
41
                        break;
9543
41
                    }
9544
3.08k
                    if (doErase) {
9545
351
                        deletePrevAndCurIter();
9546
351
                        continue;
9547
351
                    }
9548
3.08k
                }
9549
3.41k
            }
9550
9551
            // Optimize patterns like Krovak (South West) to Krovak East North
9552
            // (also applies to Modified Krovak)
9553
            //   +step +inv +proj=krovak +axis=swu +lat_0=49.5
9554
            //   +lon_0=24.8333333333333
9555
            //     +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel
9556
            //   +step +proj=krovak +lat_0=49.5 +lon_0=24.8333333333333
9557
            //     +alpha=30.2881397527778 +k=0.9999 +x_0=0 +y_0=0 +ellps=bessel
9558
            // as:
9559
            //   +step +proj=axisswap +order=-2,-1
9560
            // Also applies for the symmetrical case where +axis=swu is on the
9561
            // second step.
9562
2.32M
            if (curStep.inverted != prevStep.inverted &&
9563
1.04M
                curStep.name == prevStep.name &&
9564
74.1k
                ((curStepParamCount + 1 == prevStepParamCount &&
9565
727
                  prevStep.paramValues[0].equals("axis", "swu")) ||
9566
74.0k
                 (prevStepParamCount + 1 == curStepParamCount &&
9567
1.24k
                  curStep.paramValues[0].equals("axis", "swu")))) {
9568
138
                const auto &swStep = (curStepParamCount < prevStepParamCount)
9569
138
                                         ? prevStep
9570
138
                                         : curStep;
9571
138
                const auto &enStep = (curStepParamCount < prevStepParamCount)
9572
138
                                         ? curStep
9573
138
                                         : prevStep;
9574
                // Check if all remaining parameters (except leading axis=swu
9575
                // in swStep) are identical.
9576
138
                bool allSame = true;
9577
138
                for (size_t j = 0;
9578
155
                     j < std::min(curStepParamCount, prevStepParamCount); j++) {
9579
63
                    if (enStep.paramValues[j] != swStep.paramValues[j + 1]) {
9580
46
                        allSame = false;
9581
46
                        break;
9582
46
                    }
9583
63
                }
9584
138
                if (allSame) {
9585
92
                    iterCur->inverted = false;
9586
92
                    iterCur->name = "axisswap";
9587
92
                    iterCur->paramValues.clear();
9588
92
                    iterCur->paramValues.emplace_back(
9589
92
                        Step::KeyValue("order", "-2,-1"));
9590
9591
92
                    deletePrevIter();
9592
92
                    continue;
9593
92
                }
9594
138
            }
9595
9596
            // detect a step and its inverse
9597
2.32M
            if (curStep.inverted != prevStep.inverted &&
9598
1.04M
                curStep.name == prevStep.name &&
9599
74.0k
                curStepParamCount == prevStepParamCount) {
9600
64.5k
                bool allSame = true;
9601
130k
                for (size_t j = 0; j < curStepParamCount; j++) {
9602
93.0k
                    if (curStep.paramValues[j] != prevStep.paramValues[j]) {
9603
27.3k
                        allSame = false;
9604
27.3k
                        break;
9605
27.3k
                    }
9606
93.0k
                }
9607
64.5k
                if (allSame) {
9608
37.2k
                    deletePrevAndCurIter();
9609
37.2k
                    continue;
9610
37.2k
                }
9611
64.5k
            }
9612
9613
2.28M
            ++iterCur;
9614
2.28M
        }
9615
254k
    }
9616
9617
254k
    {
9618
254k
        auto iterCur = steps.begin();
9619
254k
        if (iterCur != steps.end()) {
9620
236k
            ++iterCur;
9621
236k
        }
9622
1.34M
        while (iterCur != steps.end()) {
9623
9624
1.09M
            assert(iterCur != steps.begin());
9625
1.09M
            auto iterPrev = std::prev(iterCur);
9626
1.09M
            auto &prevStep = *iterPrev;
9627
1.09M
            auto &curStep = *iterCur;
9628
9629
1.09M
            const auto curStepParamCount = curStep.paramValues.size();
9630
1.09M
            const auto prevStepParamCount = prevStep.paramValues.size();
9631
9632
            // +step +proj=hgridshift +grids=grid_A
9633
            // +step +proj=vgridshift [...] <== curStep
9634
            // +step +inv +proj=hgridshift +grids=grid_A
9635
            // ==>
9636
            // +step +proj=push +v_1 +v_2
9637
            // +step +proj=hgridshift +grids=grid_A +omit_inv
9638
            // +step +proj=vgridshift [...]
9639
            // +step +inv +proj=hgridshift +grids=grid_A +omit_fwd
9640
            // +step +proj=pop +v_1 +v_2
9641
1.09M
            if (std::next(iterCur) != steps.end() &&
9642
896k
                prevStep.name == "hgridshift" && prevStepParamCount == 1 &&
9643
8.00k
                curStep.name == "vgridshift") {
9644
1.85k
                auto iterNext = std::next(iterCur);
9645
1.85k
                auto &nextStep = *iterNext;
9646
1.85k
                if (nextStep.name == "hgridshift" &&
9647
468
                    nextStep.inverted != prevStep.inverted &&
9648
435
                    nextStep.paramValues.size() == 1 &&
9649
403
                    prevStep.paramValues[0] == nextStep.paramValues[0]) {
9650
206
                    Step pushStep;
9651
206
                    pushStep.name = "push";
9652
206
                    pushStep.paramValues.emplace_back("v_1");
9653
206
                    pushStep.paramValues.emplace_back("v_2");
9654
206
                    steps.insert(iterPrev, pushStep);
9655
9656
206
                    prevStep.paramValues.emplace_back("omit_inv");
9657
9658
206
                    nextStep.paramValues.emplace_back("omit_fwd");
9659
9660
206
                    Step popStep;
9661
206
                    popStep.name = "pop";
9662
206
                    popStep.paramValues.emplace_back("v_1");
9663
206
                    popStep.paramValues.emplace_back("v_2");
9664
206
                    steps.insert(std::next(iterNext), popStep);
9665
9666
206
                    continue;
9667
206
                }
9668
1.85k
            }
9669
9670
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9671
            // +step +proj=axisswap +order=2,1
9672
            // +step +proj=push +v_1 +v_2
9673
            // +step +proj=axisswap +order=2,1
9674
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9675
            // +step +proj=vgridshift ...
9676
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9677
            // +step +proj=axisswap +order=2,1
9678
            // +step +proj=pop +v_1 +v_2
9679
            // ==>
9680
            // +step +proj=vgridshift ...
9681
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9682
            // +step +proj=axisswap +order=2,1
9683
1.09M
            if (prevStep.name == "unitconvert" && prevStepParamCount == 2 &&
9684
144k
                prevStep.paramValues[0].equals("xy_in", "rad") &&
9685
24.1k
                prevStep.paramValues[1].equals("xy_out", "deg") &&
9686
24.1k
                curStep.name == "axisswap" && curStepParamCount == 1 &&
9687
14.8k
                curStep.paramValues[0].equals("order", "2,1")) {
9688
12.0k
                auto iterNext = std::next(iterCur);
9689
12.0k
                bool ok = false;
9690
12.0k
                if (iterNext != steps.end()) {
9691
1.68k
                    auto &nextStep = *iterNext;
9692
1.68k
                    if (nextStep.name == "push" &&
9693
157
                        nextStep.paramValues.size() == 2 &&
9694
2
                        nextStep.paramValues[0].keyEquals("v_1") &&
9695
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9696
0
                        ok = true;
9697
0
                        iterNext = std::next(iterNext);
9698
0
                    }
9699
1.68k
                }
9700
12.0k
                ok &= iterNext != steps.end();
9701
12.0k
                if (ok) {
9702
0
                    ok = false;
9703
0
                    auto &nextStep = *iterNext;
9704
0
                    if (nextStep.name == "axisswap" &&
9705
0
                        nextStep.paramValues.size() == 1 &&
9706
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9707
0
                        ok = true;
9708
0
                        iterNext = std::next(iterNext);
9709
0
                    }
9710
0
                }
9711
12.0k
                ok &= iterNext != steps.end();
9712
12.0k
                if (ok) {
9713
0
                    ok = false;
9714
0
                    auto &nextStep = *iterNext;
9715
0
                    if (nextStep.name == "unitconvert" &&
9716
0
                        nextStep.paramValues.size() == 2 &&
9717
0
                        nextStep.paramValues[0].equals("xy_in", "deg") &&
9718
0
                        nextStep.paramValues[1].equals("xy_out", "rad")) {
9719
0
                        ok = true;
9720
0
                        iterNext = std::next(iterNext);
9721
0
                    }
9722
0
                }
9723
12.0k
                auto iterVgridshift = iterNext;
9724
12.0k
                ok &= iterNext != steps.end();
9725
12.0k
                if (ok) {
9726
0
                    ok = false;
9727
0
                    auto &nextStep = *iterNext;
9728
0
                    if (nextStep.name == "vgridshift") {
9729
0
                        ok = true;
9730
0
                        iterNext = std::next(iterNext);
9731
0
                    }
9732
0
                }
9733
12.0k
                ok &= iterNext != steps.end();
9734
12.0k
                if (ok) {
9735
0
                    ok = false;
9736
0
                    auto &nextStep = *iterNext;
9737
0
                    if (nextStep.name == "unitconvert" &&
9738
0
                        nextStep.paramValues.size() == 2 &&
9739
0
                        nextStep.paramValues[0].equals("xy_in", "rad") &&
9740
0
                        nextStep.paramValues[1].equals("xy_out", "deg")) {
9741
0
                        ok = true;
9742
0
                        iterNext = std::next(iterNext);
9743
0
                    }
9744
0
                }
9745
12.0k
                ok &= iterNext != steps.end();
9746
12.0k
                if (ok) {
9747
0
                    ok = false;
9748
0
                    auto &nextStep = *iterNext;
9749
0
                    if (nextStep.name == "axisswap" &&
9750
0
                        nextStep.paramValues.size() == 1 &&
9751
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9752
0
                        ok = true;
9753
0
                        iterNext = std::next(iterNext);
9754
0
                    }
9755
0
                }
9756
12.0k
                ok &= iterNext != steps.end();
9757
12.0k
                if (ok) {
9758
0
                    ok = false;
9759
0
                    auto &nextStep = *iterNext;
9760
0
                    if (nextStep.name == "pop" &&
9761
0
                        nextStep.paramValues.size() == 2 &&
9762
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9763
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9764
0
                        ok = true;
9765
                        // iterNext = std::next(iterNext);
9766
0
                    }
9767
0
                }
9768
12.0k
                if (ok) {
9769
0
                    steps.erase(iterPrev, iterVgridshift);
9770
0
                    steps.erase(iterNext, std::next(iterNext));
9771
0
                    iterPrev = std::prev(iterVgridshift);
9772
0
                    iterCur = iterVgridshift;
9773
0
                    continue;
9774
0
                }
9775
12.0k
            }
9776
9777
            // +step +proj=axisswap +order=2,1
9778
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9779
            // +step +proj=vgridshift ...
9780
            // +step +proj=unitconvert +xy_in=rad +xy_out=deg
9781
            // +step +proj=axisswap +order=2,1
9782
            // +step +proj=push +v_1 +v_2
9783
            // +step +proj=axisswap +order=2,1
9784
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9785
            // ==>
9786
            // +step +proj=push +v_1 +v_2
9787
            // +step +proj=axisswap +order=2,1
9788
            // +step +proj=unitconvert +xy_in=deg +xy_out=rad
9789
            // +step +proj=vgridshift ...
9790
9791
1.09M
            if (prevStep.name == "axisswap" && prevStepParamCount == 1 &&
9792
32.1k
                prevStep.paramValues[0].equals("order", "2,1") &&
9793
26.3k
                curStep.name == "unitconvert" && curStepParamCount == 2 &&
9794
21.6k
                !curStep.inverted &&
9795
21.6k
                curStep.paramValues[0].equals("xy_in", "deg") &&
9796
20.6k
                curStep.paramValues[1].equals("xy_out", "rad")) {
9797
20.6k
                auto iterNext = std::next(iterCur);
9798
20.6k
                bool ok = false;
9799
20.6k
                auto iterVgridshift = iterNext;
9800
20.6k
                if (iterNext != steps.end()) {
9801
20.6k
                    auto &nextStep = *iterNext;
9802
20.6k
                    if (nextStep.name == "vgridshift") {
9803
4.10k
                        ok = true;
9804
4.10k
                        iterNext = std::next(iterNext);
9805
4.10k
                    }
9806
20.6k
                }
9807
20.6k
                ok &= iterNext != steps.end();
9808
20.6k
                if (ok) {
9809
4.10k
                    ok = false;
9810
4.10k
                    auto &nextStep = *iterNext;
9811
4.10k
                    if (nextStep.name == "unitconvert" && !nextStep.inverted &&
9812
1.62k
                        nextStep.paramValues.size() == 2 &&
9813
1.48k
                        nextStep.paramValues[0].equals("xy_in", "rad") &&
9814
829
                        nextStep.paramValues[1].equals("xy_out", "deg")) {
9815
829
                        ok = true;
9816
829
                        iterNext = std::next(iterNext);
9817
829
                    }
9818
4.10k
                }
9819
20.6k
                ok &= iterNext != steps.end();
9820
20.6k
                if (ok) {
9821
247
                    ok = false;
9822
247
                    auto &nextStep = *iterNext;
9823
247
                    if (nextStep.name == "axisswap" &&
9824
210
                        nextStep.paramValues.size() == 1 &&
9825
210
                        nextStep.paramValues[0].equals("order", "2,1")) {
9826
202
                        ok = true;
9827
202
                        iterNext = std::next(iterNext);
9828
202
                    }
9829
247
                }
9830
20.6k
                auto iterPush = iterNext;
9831
20.6k
                ok &= iterNext != steps.end();
9832
20.6k
                if (ok) {
9833
120
                    ok = false;
9834
120
                    auto &nextStep = *iterNext;
9835
120
                    if (nextStep.name == "push" &&
9836
2
                        nextStep.paramValues.size() == 2 &&
9837
0
                        nextStep.paramValues[0].keyEquals("v_1") &&
9838
0
                        nextStep.paramValues[1].keyEquals("v_2")) {
9839
0
                        ok = true;
9840
0
                        iterNext = std::next(iterNext);
9841
0
                    }
9842
120
                }
9843
20.6k
                ok &= iterNext != steps.end();
9844
20.6k
                if (ok) {
9845
0
                    ok = false;
9846
0
                    auto &nextStep = *iterNext;
9847
0
                    if (nextStep.name == "axisswap" &&
9848
0
                        nextStep.paramValues.size() == 1 &&
9849
0
                        nextStep.paramValues[0].equals("order", "2,1")) {
9850
0
                        ok = true;
9851
0
                        iterNext = std::next(iterNext);
9852
0
                    }
9853
0
                }
9854
20.6k
                ok &= iterNext != steps.end();
9855
20.6k
                if (ok) {
9856
0
                    ok = false;
9857
0
                    auto &nextStep = *iterNext;
9858
0
                    if (nextStep.name == "unitconvert" &&
9859
0
                        nextStep.paramValues.size() == 2 &&
9860
0
                        !nextStep.inverted &&
9861
0
                        nextStep.paramValues[0].equals("xy_in", "deg") &&
9862
0
                        nextStep.paramValues[1].equals("xy_out", "rad")) {
9863
0
                        ok = true;
9864
                        // iterNext = std::next(iterNext);
9865
0
                    }
9866
0
                }
9867
9868
20.6k
                if (ok) {
9869
0
                    Step stepVgridshift(*iterVgridshift);
9870
0
                    steps.erase(iterPrev, iterPush);
9871
0
                    steps.insert(std::next(iterNext),
9872
0
                                 std::move(stepVgridshift));
9873
0
                    iterPrev = iterPush;
9874
0
                    iterCur = std::next(iterPush);
9875
0
                    continue;
9876
0
                }
9877
20.6k
            }
9878
9879
1.09M
            ++iterCur;
9880
1.09M
        }
9881
254k
    }
9882
9883
254k
    {
9884
254k
        auto iterCur = steps.begin();
9885
254k
        if (iterCur != steps.end()) {
9886
236k
            ++iterCur;
9887
236k
        }
9888
1.34M
        while (iterCur != steps.end()) {
9889
9890
1.09M
            assert(iterCur != steps.begin());
9891
1.09M
            auto iterPrev = std::prev(iterCur);
9892
1.09M
            auto &prevStep = *iterPrev;
9893
1.09M
            auto &curStep = *iterCur;
9894
9895
1.09M
            const auto curStepParamCount = curStep.paramValues.size();
9896
1.09M
            const auto prevStepParamCount = prevStep.paramValues.size();
9897
9898
1.09M
            const auto deletePrevAndCurIter = [&steps, &iterPrev, &iterCur]() {
9899
154
                iterCur = steps.erase(iterPrev, std::next(iterCur));
9900
154
                if (iterCur != steps.begin())
9901
154
                    iterCur = std::prev(iterCur);
9902
154
                if (iterCur == steps.begin() && iterCur != steps.end())
9903
34
                    ++iterCur;
9904
154
            };
9905
9906
            // axisswap order=2,1 followed by itself is a no-op
9907
1.09M
            if (curStep.name == "axisswap" && prevStep.name == "axisswap" &&
9908
519
                curStepParamCount == 1 && prevStepParamCount == 1 &&
9909
312
                curStep.paramValues[0].equals("order", "2,1") &&
9910
36
                prevStep.paramValues[0].equals("order", "2,1")) {
9911
16
                deletePrevAndCurIter();
9912
16
                continue;
9913
16
            }
9914
9915
            // detect a step and its inverse
9916
1.09M
            if (curStep.inverted != prevStep.inverted &&
9917
473k
                curStep.name == prevStep.name &&
9918
21.8k
                curStepParamCount == prevStepParamCount) {
9919
14.0k
                bool allSame = true;
9920
16.8k
                for (size_t j = 0; j < curStepParamCount; j++) {
9921
16.7k
                    if (curStep.paramValues[j] != prevStep.paramValues[j]) {
9922
13.9k
                        allSame = false;
9923
13.9k
                        break;
9924
13.9k
                    }
9925
16.7k
                }
9926
14.0k
                if (allSame) {
9927
138
                    deletePrevAndCurIter();
9928
138
                    continue;
9929
138
                }
9930
14.0k
            }
9931
9932
1.09M
            ++iterCur;
9933
1.09M
        }
9934
254k
    }
9935
9936
254k
    if (steps.size() > 1 ||
9937
59.9k
        (steps.size() == 1 &&
9938
42.1k
         (steps.front().inverted || steps.front().hasKey("omit_inv") ||
9939
37.0k
          steps.front().hasKey("omit_fwd") ||
9940
199k
          !d->globalParamValues_.empty()))) {
9941
199k
        d->appendToResult("+proj=pipeline");
9942
9943
555k
        for (const auto &paramValue : d->globalParamValues_) {
9944
555k
            d->appendToResult("+");
9945
555k
            d->result_ += paramValue.key;
9946
555k
            if (!paramValue.value.empty()) {
9947
311k
                d->result_ += '=';
9948
311k
                d->result_ +=
9949
311k
                    pj_double_quote_string_param_if_needed(paramValue.value);
9950
311k
            }
9951
555k
        }
9952
9953
199k
        if (d->multiLine_) {
9954
0
            d->indentLevel_++;
9955
0
        }
9956
199k
    }
9957
9958
1.32M
    for (const auto &step : steps) {
9959
1.32M
        std::string curLine;
9960
1.32M
        if (!d->result_.empty()) {
9961
1.29M
            if (d->multiLine_) {
9962
0
                curLine = std::string(static_cast<size_t>(d->indentLevel_) *
9963
0
                                          d->indentWidth_,
9964
0
                                      ' ');
9965
0
                curLine += "+step";
9966
1.29M
            } else {
9967
1.29M
                curLine = " +step";
9968
1.29M
            }
9969
1.29M
        }
9970
1.32M
        if (step.inverted) {
9971
412k
            curLine += " +inv";
9972
412k
        }
9973
1.32M
        if (!step.name.empty()) {
9974
1.31M
            if (!curLine.empty())
9975
1.27M
                curLine += ' ';
9976
1.31M
            curLine += step.isInit ? "+init=" : "+proj=";
9977
1.31M
            curLine += step.name;
9978
1.31M
        }
9979
4.95M
        for (const auto &paramValue : step.paramValues) {
9980
4.95M
            std::string newKV = "+";
9981
4.95M
            newKV += paramValue.key;
9982
4.95M
            if (!paramValue.value.empty()) {
9983
2.62M
                newKV += '=';
9984
2.62M
                newKV +=
9985
2.62M
                    pj_double_quote_string_param_if_needed(paramValue.value);
9986
2.62M
            }
9987
4.95M
            if (d->maxLineLength_ > 0 && d->multiLine_ &&
9988
0
                curLine.size() + newKV.size() >
9989
0
                    static_cast<size_t>(d->maxLineLength_)) {
9990
0
                if (!d->result_.empty())
9991
0
                    d->result_ += '\n';
9992
0
                d->result_ += curLine;
9993
0
                curLine = std::string(static_cast<size_t>(d->indentLevel_) *
9994
0
                                              d->indentWidth_ +
9995
0
                                          strlen("+step "),
9996
0
                                      ' ');
9997
4.95M
            } else {
9998
4.95M
                if (!curLine.empty())
9999
4.95M
                    curLine += ' ';
10000
4.95M
            }
10001
4.95M
            curLine += newKV;
10002
4.95M
        }
10003
1.32M
        if (d->multiLine_ && !d->result_.empty())
10004
0
            d->result_ += '\n';
10005
1.32M
        d->result_ += curLine;
10006
1.32M
    }
10007
10008
254k
    if (d->result_.empty()) {
10009
17.7k
        d->appendToResult("+proj=noop");
10010
17.7k
    }
10011
10012
254k
    return d->result_;
10013
254k
}
10014
10015
// ---------------------------------------------------------------------------
10016
10017
//! @cond Doxygen_Suppress
10018
10019
554k
PROJStringFormatter::Convention PROJStringFormatter::convention() const {
10020
554k
    return d->convention_;
10021
554k
}
10022
10023
// ---------------------------------------------------------------------------
10024
10025
// Return the number of steps in the pipeline.
10026
// Note: this value will change after calling toString() that will run
10027
// optimizations.
10028
41.7k
size_t PROJStringFormatter::getStepCount() const { return d->steps_.size(); }
10029
10030
// ---------------------------------------------------------------------------
10031
10032
9.85k
bool PROJStringFormatter::getUseApproxTMerc() const {
10033
9.85k
    return d->useApproxTMerc_;
10034
9.85k
}
10035
10036
// ---------------------------------------------------------------------------
10037
10038
349k
void PROJStringFormatter::setCoordinateOperationOptimizations(bool enable) {
10039
349k
    d->coordOperationOptimizations_ = enable;
10040
349k
}
10041
10042
// ---------------------------------------------------------------------------
10043
10044
773k
void PROJStringFormatter::Private::appendToResult(const char *str) {
10045
773k
    if (!result_.empty()) {
10046
555k
        result_ += ' ';
10047
555k
    }
10048
773k
    result_ += str;
10049
773k
}
10050
10051
// ---------------------------------------------------------------------------
10052
10053
static void
10054
PROJStringSyntaxParser(const std::string &projString, std::vector<Step> &steps,
10055
                       std::vector<Step::KeyValue> &globalParamValues,
10056
298k
                       std::string &title) {
10057
298k
    std::vector<std::string> tokens;
10058
10059
298k
    bool hasProj = false;
10060
298k
    bool hasInit = false;
10061
298k
    bool hasPipeline = false;
10062
10063
298k
    std::string projStringModified(projString);
10064
10065
    // Special case for "+title=several words +foo=bar"
10066
298k
    if (starts_with(projStringModified, "+title=") &&
10067
147
        projStringModified.size() > 7 && projStringModified[7] != '"') {
10068
142
        const auto plusPos = projStringModified.find(" +", 1);
10069
142
        const auto spacePos = projStringModified.find(' ');
10070
142
        if (plusPos != std::string::npos && spacePos != std::string::npos &&
10071
116
            spacePos < plusPos) {
10072
78
            std::string tmp("+title=");
10073
78
            tmp += pj_double_quote_string_param_if_needed(
10074
78
                projStringModified.substr(7, plusPos - 7));
10075
78
            tmp += projStringModified.substr(plusPos);
10076
78
            projStringModified = std::move(tmp);
10077
78
        }
10078
142
    }
10079
10080
298k
    size_t argc = pj_trim_argc(&projStringModified[0]);
10081
298k
    char **argv = pj_trim_argv(argc, &projStringModified[0]);
10082
9.12M
    for (size_t i = 0; i < argc; i++) {
10083
8.82M
        std::string token(argv[i]);
10084
8.82M
        if (!hasPipeline && token == "proj=pipeline") {
10085
220k
            hasPipeline = true;
10086
8.60M
        } else if (!hasProj && starts_with(token, "proj=")) {
10087
287k
            hasProj = true;
10088
8.32M
        } else if (!hasInit && starts_with(token, "init=")) {
10089
1.87k
            hasInit = true;
10090
1.87k
        }
10091
8.82M
        tokens.emplace_back(token);
10092
8.82M
    }
10093
298k
    free(argv);
10094
10095
298k
    if (!hasPipeline) {
10096
77.9k
        if (hasProj || hasInit) {
10097
67.9k
            steps.push_back(Step());
10098
67.9k
        }
10099
10100
711k
        for (auto &word : tokens) {
10101
711k
            if (starts_with(word, "proj=") && !hasInit &&
10102
86.2k
                steps.back().name.empty()) {
10103
67.1k
                assert(hasProj);
10104
67.1k
                auto stepName = word.substr(strlen("proj="));
10105
67.1k
                steps.back().name = std::move(stepName);
10106
644k
            } else if (starts_with(word, "init=")) {
10107
894
                assert(hasInit);
10108
894
                auto initName = word.substr(strlen("init="));
10109
894
                steps.back().name = std::move(initName);
10110
894
                steps.back().isInit = true;
10111
643k
            } else if (word == "inv") {
10112
3.19k
                if (!steps.empty()) {
10113
3.17k
                    steps.back().inverted = true;
10114
3.17k
                }
10115
640k
            } else if (starts_with(word, "title=")) {
10116
1.01k
                title = word.substr(strlen("title="));
10117
639k
            } else if (word != "step") {
10118
631k
                const auto pos = word.find('=');
10119
631k
                const auto key = word.substr(0, pos);
10120
10121
631k
                Step::KeyValue pair(
10122
631k
                    (pos != std::string::npos)
10123
631k
                        ? Step::KeyValue(key, word.substr(pos + 1))
10124
631k
                        : Step::KeyValue(key));
10125
631k
                if (steps.empty()) {
10126
680
                    globalParamValues.push_back(std::move(pair));
10127
630k
                } else {
10128
630k
                    steps.back().paramValues.push_back(std::move(pair));
10129
630k
                }
10130
631k
            }
10131
711k
        }
10132
77.9k
        return;
10133
77.9k
    }
10134
10135
220k
    bool inPipeline = false;
10136
220k
    bool invGlobal = false;
10137
8.11M
    for (auto &word : tokens) {
10138
8.11M
        if (word == "proj=pipeline") {
10139
220k
            if (inPipeline) {
10140
28
                throw ParsingException("nested pipeline not supported");
10141
28
            }
10142
220k
            inPipeline = true;
10143
7.89M
        } else if (word == "step") {
10144
1.00M
            if (!inPipeline) {
10145
10
                throw ParsingException("+step found outside pipeline");
10146
10
            }
10147
1.00M
            steps.push_back(Step());
10148
6.89M
        } else if (word == "inv") {
10149
296k
            if (steps.empty()) {
10150
3.70k
                invGlobal = true;
10151
293k
            } else {
10152
293k
                steps.back().inverted = true;
10153
293k
            }
10154
6.59M
        } else if (inPipeline && !steps.empty() && starts_with(word, "proj=") &&
10155
1.11M
                   steps.back().name.empty()) {
10156
970k
            auto stepName = word.substr(strlen("proj="));
10157
970k
            steps.back().name = std::move(stepName);
10158
5.62M
        } else if (inPipeline && !steps.empty() && starts_with(word, "init=") &&
10159
1.06k
                   steps.back().name.empty()) {
10160
754
            auto initName = word.substr(strlen("init="));
10161
754
            steps.back().name = std::move(initName);
10162
754
            steps.back().isInit = true;
10163
5.62M
        } else if (!inPipeline && starts_with(word, "title=")) {
10164
262
            title = word.substr(strlen("title="));
10165
5.62M
        } else {
10166
5.62M
            const auto pos = word.find('=');
10167
5.62M
            auto key = word.substr(0, pos);
10168
5.62M
            Step::KeyValue pair((pos != std::string::npos)
10169
5.62M
                                    ? Step::KeyValue(key, word.substr(pos + 1))
10170
5.62M
                                    : Step::KeyValue(key));
10171
5.62M
            if (steps.empty()) {
10172
930k
                globalParamValues.emplace_back(std::move(pair));
10173
4.69M
            } else {
10174
4.69M
                steps.back().paramValues.emplace_back(std::move(pair));
10175
4.69M
            }
10176
5.62M
        }
10177
8.11M
    }
10178
220k
    if (invGlobal) {
10179
32.6k
        for (auto &step : steps) {
10180
32.6k
            step.inverted = !step.inverted;
10181
32.6k
        }
10182
3.41k
        std::reverse(steps.begin(), steps.end());
10183
3.41k
    }
10184
220k
}
10185
10186
// ---------------------------------------------------------------------------
10187
10188
void PROJStringFormatter::ingestPROJString(
10189
    const std::string &str) // throw ParsingException
10190
284k
{
10191
284k
    std::vector<Step> steps;
10192
284k
    std::string title;
10193
284k
    PROJStringSyntaxParser(str, steps, d->globalParamValues_, title);
10194
284k
    d->steps_.insert(d->steps_.end(), steps.begin(), steps.end());
10195
284k
}
10196
10197
// ---------------------------------------------------------------------------
10198
10199
17.2k
void PROJStringFormatter::setCRSExport(bool b) { d->crsExport_ = b; }
10200
10201
// ---------------------------------------------------------------------------
10202
10203
1.87M
bool PROJStringFormatter::getCRSExport() const { return d->crsExport_; }
10204
10205
// ---------------------------------------------------------------------------
10206
10207
625k
void PROJStringFormatter::startInversion() {
10208
625k
    PROJStringFormatter::Private::InversionStackElt elt;
10209
625k
    elt.startIter = d->steps_.end();
10210
625k
    if (elt.startIter != d->steps_.begin()) {
10211
411k
        elt.iterValid = true;
10212
411k
        --elt.startIter; // point to the last valid element
10213
411k
    } else {
10214
213k
        elt.iterValid = false;
10215
213k
    }
10216
625k
    elt.currentInversionState =
10217
625k
        !d->inversionStack_.back().currentInversionState;
10218
625k
    d->inversionStack_.push_back(elt);
10219
625k
}
10220
10221
// ---------------------------------------------------------------------------
10222
10223
624k
void PROJStringFormatter::stopInversion() {
10224
624k
    assert(!d->inversionStack_.empty());
10225
624k
    auto startIter = d->inversionStack_.back().startIter;
10226
624k
    if (!d->inversionStack_.back().iterValid) {
10227
213k
        startIter = d->steps_.begin();
10228
411k
    } else {
10229
411k
        ++startIter; // advance after the last valid element we marked above
10230
411k
    }
10231
    // Invert the inversion status of the steps between the start point and
10232
    // the current end of steps
10233
2.83M
    for (auto iter = startIter; iter != d->steps_.end(); ++iter) {
10234
2.20M
        iter->inverted = !iter->inverted;
10235
4.89M
        for (auto &paramValue : iter->paramValues) {
10236
4.89M
            if (paramValue.key == "omit_fwd")
10237
399
                paramValue.key = "omit_inv";
10238
4.89M
            else if (paramValue.key == "omit_inv")
10239
255
                paramValue.key = "omit_fwd";
10240
4.89M
        }
10241
2.20M
    }
10242
    // And reverse the order of steps in that range as well.
10243
624k
    std::reverse(startIter, d->steps_.end());
10244
624k
    d->inversionStack_.pop_back();
10245
624k
}
10246
10247
// ---------------------------------------------------------------------------
10248
10249
0
bool PROJStringFormatter::isInverted() const {
10250
0
    return d->inversionStack_.back().currentInversionState;
10251
0
}
10252
10253
// ---------------------------------------------------------------------------
10254
10255
2.64M
void PROJStringFormatter::Private::addStep() { steps_.emplace_back(Step()); }
10256
10257
// ---------------------------------------------------------------------------
10258
10259
2.61M
void PROJStringFormatter::addStep(const char *stepName) {
10260
2.61M
    d->addStep();
10261
2.61M
    d->steps_.back().name.assign(stepName);
10262
2.61M
}
10263
10264
// ---------------------------------------------------------------------------
10265
10266
30.6k
void PROJStringFormatter::addStep(const std::string &stepName) {
10267
30.6k
    d->addStep();
10268
30.6k
    d->steps_.back().name = stepName;
10269
30.6k
}
10270
10271
// ---------------------------------------------------------------------------
10272
10273
135k
void PROJStringFormatter::setCurrentStepInverted(bool inverted) {
10274
135k
    assert(!d->steps_.empty());
10275
135k
    d->steps_.back().inverted = inverted;
10276
135k
}
10277
10278
// ---------------------------------------------------------------------------
10279
10280
0
bool PROJStringFormatter::hasParam(const char *paramName) const {
10281
0
    if (!d->steps_.empty()) {
10282
0
        for (const auto &paramValue : d->steps_.back().paramValues) {
10283
0
            if (paramValue.keyEquals(paramName)) {
10284
0
                return true;
10285
0
            }
10286
0
        }
10287
0
    }
10288
0
    return false;
10289
0
}
10290
10291
// ---------------------------------------------------------------------------
10292
10293
24.6k
void PROJStringFormatter::addNoDefs(bool b) { d->addNoDefs_ = b; }
10294
10295
// ---------------------------------------------------------------------------
10296
10297
112k
bool PROJStringFormatter::getAddNoDefs() const { return d->addNoDefs_; }
10298
10299
// ---------------------------------------------------------------------------
10300
10301
270k
void PROJStringFormatter::addParam(const std::string &paramName) {
10302
270k
    if (d->steps_.empty()) {
10303
0
        d->addStep();
10304
0
    }
10305
270k
    d->steps_.back().paramValues.push_back(Step::KeyValue(paramName));
10306
270k
}
10307
10308
// ---------------------------------------------------------------------------
10309
10310
730
void PROJStringFormatter::addParam(const char *paramName, int val) {
10311
730
    addParam(std::string(paramName), val);
10312
730
}
10313
10314
730
void PROJStringFormatter::addParam(const std::string &paramName, int val) {
10315
730
    addParam(paramName, internal::toString(val));
10316
730
}
10317
10318
// ---------------------------------------------------------------------------
10319
10320
883k
static std::string formatToString(double val, double precision) {
10321
883k
    if (std::abs(val * 10 - std::round(val * 10)) < precision) {
10322
        // For the purpose of
10323
        // https://www.epsg-registry.org/export.htm?wkt=urn:ogc:def:crs:EPSG::27561
10324
        // Latitude of natural of origin to be properly rounded from 55 grad
10325
        // to
10326
        // 49.5 deg
10327
692k
        val = std::round(val * 10) / 10;
10328
692k
    }
10329
883k
    return normalizeSerializedString(internal::toString(val));
10330
883k
}
10331
10332
// ---------------------------------------------------------------------------
10333
10334
873k
void PROJStringFormatter::addParam(const char *paramName, double val) {
10335
873k
    addParam(std::string(paramName), val);
10336
873k
}
10337
10338
882k
void PROJStringFormatter::addParam(const std::string &paramName, double val) {
10339
882k
    if (paramName == "dt") {
10340
0
        addParam(paramName,
10341
0
                 normalizeSerializedString(internal::toString(val, 7)));
10342
882k
    } else {
10343
882k
        addParam(paramName, formatToString(val, 1e-8));
10344
882k
    }
10345
882k
}
10346
10347
// ---------------------------------------------------------------------------
10348
10349
void PROJStringFormatter::addParam(const char *paramName,
10350
26
                                   const std::vector<double> &vals) {
10351
26
    std::string paramValue;
10352
208
    for (size_t i = 0; i < vals.size(); ++i) {
10353
182
        if (i > 0) {
10354
156
            paramValue += ',';
10355
156
        }
10356
182
        paramValue += formatToString(vals[i], 1e-8);
10357
182
    }
10358
26
    addParam(paramName, paramValue);
10359
26
}
10360
10361
// ---------------------------------------------------------------------------
10362
10363
1.61M
void PROJStringFormatter::addParam(const char *paramName, const char *val) {
10364
1.61M
    addParam(std::string(paramName), val);
10365
1.61M
}
10366
10367
void PROJStringFormatter::addParam(const char *paramName,
10368
1.74M
                                   const std::string &val) {
10369
1.74M
    addParam(std::string(paramName), val);
10370
1.74M
}
10371
10372
void PROJStringFormatter::addParam(const std::string &paramName,
10373
1.61M
                                   const char *val) {
10374
1.61M
    addParam(paramName, std::string(val));
10375
1.61M
}
10376
10377
// ---------------------------------------------------------------------------
10378
10379
void PROJStringFormatter::addParam(const std::string &paramName,
10380
4.28M
                                   const std::string &val) {
10381
4.28M
    if (d->steps_.empty()) {
10382
593
        d->addStep();
10383
593
    }
10384
4.28M
    d->steps_.back().paramValues.push_back(Step::KeyValue(paramName, val));
10385
4.28M
}
10386
10387
// ---------------------------------------------------------------------------
10388
10389
void PROJStringFormatter::setTOWGS84Parameters(
10390
547
    const std::vector<double> &params) {
10391
547
    d->toWGS84Parameters_ = params;
10392
547
}
10393
10394
// ---------------------------------------------------------------------------
10395
10396
452k
const std::vector<double> &PROJStringFormatter::getTOWGS84Parameters() const {
10397
452k
    return d->toWGS84Parameters_;
10398
452k
}
10399
10400
// ---------------------------------------------------------------------------
10401
10402
27.1k
std::set<std::string> PROJStringFormatter::getUsedGridNames() const {
10403
27.1k
    std::set<std::string> res;
10404
157k
    for (const auto &step : d->steps_) {
10405
441k
        for (const auto &param : step.paramValues) {
10406
441k
            if (param.keyEquals("grids") || param.keyEquals("file")) {
10407
18.4k
                const auto gridNames = split(param.value, ",");
10408
35.8k
                for (const auto &gridName : gridNames) {
10409
35.8k
                    res.insert(gridName);
10410
35.8k
                }
10411
18.4k
            }
10412
441k
        }
10413
157k
    }
10414
27.1k
    return res;
10415
27.1k
}
10416
10417
// ---------------------------------------------------------------------------
10418
10419
128k
bool PROJStringFormatter::requiresPerCoordinateInputTime() const {
10420
632k
    for (const auto &step : d->steps_) {
10421
632k
        if (step.name == "set" && !step.inverted) {
10422
22.2k
            for (const auto &param : step.paramValues) {
10423
22.2k
                if (param.keyEquals("v_4")) {
10424
1
                    return false;
10425
1
                }
10426
22.2k
            }
10427
629k
        } else if (step.name == "helmert") {
10428
193k
            for (const auto &param : step.paramValues) {
10429
193k
                if (param.keyEquals("t_epoch")) {
10430
1.22k
                    return true;
10431
1.22k
                }
10432
193k
            }
10433
582k
        } else if (step.name == "deformation") {
10434
23.2k
            for (const auto &param : step.paramValues) {
10435
23.2k
                if (param.keyEquals("t_epoch")) {
10436
7.42k
                    return true;
10437
7.42k
                }
10438
23.2k
            }
10439
568k
        } else if (step.name == "defmodel") {
10440
0
            return true;
10441
0
        }
10442
632k
    }
10443
119k
    return false;
10444
128k
}
10445
10446
// ---------------------------------------------------------------------------
10447
10448
void PROJStringFormatter::setVDatumExtension(const std::string &filename,
10449
896
                                             const std::string &geoidCRSValue) {
10450
896
    d->vDatumExtension_ = filename;
10451
896
    d->geoidCRSValue_ = geoidCRSValue;
10452
896
}
10453
10454
// ---------------------------------------------------------------------------
10455
10456
836
const std::string &PROJStringFormatter::getVDatumExtension() const {
10457
836
    return d->vDatumExtension_;
10458
836
}
10459
10460
// ---------------------------------------------------------------------------
10461
10462
836
const std::string &PROJStringFormatter::getGeoidCRSValue() const {
10463
836
    return d->geoidCRSValue_;
10464
836
}
10465
10466
// ---------------------------------------------------------------------------
10467
10468
386
void PROJStringFormatter::setHDatumExtension(const std::string &filename) {
10469
386
    d->hDatumExtension_ = filename;
10470
386
}
10471
10472
// ---------------------------------------------------------------------------
10473
10474
452k
const std::string &PROJStringFormatter::getHDatumExtension() const {
10475
452k
    return d->hDatumExtension_;
10476
452k
}
10477
10478
// ---------------------------------------------------------------------------
10479
10480
void PROJStringFormatter::setGeogCRSOfCompoundCRS(
10481
467
    const crs::GeographicCRSPtr &crs) {
10482
467
    d->geogCRSOfCompoundCRS_ = crs;
10483
467
}
10484
10485
// ---------------------------------------------------------------------------
10486
10487
const crs::GeographicCRSPtr &
10488
1.10k
PROJStringFormatter::getGeogCRSOfCompoundCRS() const {
10489
1.10k
    return d->geogCRSOfCompoundCRS_;
10490
1.10k
}
10491
10492
// ---------------------------------------------------------------------------
10493
10494
155k
void PROJStringFormatter::setOmitProjLongLatIfPossible(bool omit) {
10495
155k
    assert(d->omitProjLongLatIfPossible_ ^ omit);
10496
155k
    d->omitProjLongLatIfPossible_ = omit;
10497
155k
}
10498
10499
// ---------------------------------------------------------------------------
10500
10501
399k
bool PROJStringFormatter::omitProjLongLatIfPossible() const {
10502
399k
    return d->omitProjLongLatIfPossible_;
10503
399k
}
10504
10505
// ---------------------------------------------------------------------------
10506
10507
236k
void PROJStringFormatter::pushOmitZUnitConversion() {
10508
236k
    d->omitZUnitConversion_.push_back(true);
10509
236k
}
10510
10511
// ---------------------------------------------------------------------------
10512
10513
236k
void PROJStringFormatter::popOmitZUnitConversion() {
10514
236k
    assert(d->omitZUnitConversion_.size() > 1);
10515
236k
    d->omitZUnitConversion_.pop_back();
10516
236k
}
10517
10518
// ---------------------------------------------------------------------------
10519
10520
554k
bool PROJStringFormatter::omitZUnitConversion() const {
10521
554k
    return d->omitZUnitConversion_.back();
10522
554k
}
10523
10524
// ---------------------------------------------------------------------------
10525
10526
92.8k
void PROJStringFormatter::pushOmitHorizontalConversionInVertTransformation() {
10527
92.8k
    d->omitHorizontalConversionInVertTransformation_.push_back(true);
10528
92.8k
}
10529
10530
// ---------------------------------------------------------------------------
10531
10532
92.8k
void PROJStringFormatter::popOmitHorizontalConversionInVertTransformation() {
10533
92.8k
    assert(d->omitHorizontalConversionInVertTransformation_.size() > 1);
10534
92.8k
    d->omitHorizontalConversionInVertTransformation_.pop_back();
10535
92.8k
}
10536
10537
// ---------------------------------------------------------------------------
10538
10539
210k
bool PROJStringFormatter::omitHorizontalConversionInVertTransformation() const {
10540
210k
    return d->omitHorizontalConversionInVertTransformation_.back();
10541
210k
}
10542
10543
// ---------------------------------------------------------------------------
10544
10545
17.2k
void PROJStringFormatter::setLegacyCRSToCRSContext(bool legacyContext) {
10546
17.2k
    d->legacyCRSToCRSContext_ = legacyContext;
10547
17.2k
}
10548
10549
// ---------------------------------------------------------------------------
10550
10551
344k
bool PROJStringFormatter::getLegacyCRSToCRSContext() const {
10552
344k
    return d->legacyCRSToCRSContext_;
10553
344k
}
10554
10555
// ---------------------------------------------------------------------------
10556
10557
/** Asks for a "normalized" output during toString(), aimed at comparing two
10558
 * strings for equivalence.
10559
 *
10560
 * This consists for now in sorting the +key=value option in lexicographic
10561
 * order.
10562
 */
10563
19.5k
PROJStringFormatter &PROJStringFormatter::setNormalizeOutput() {
10564
19.5k
    d->normalizeOutput_ = true;
10565
19.5k
    return *this;
10566
19.5k
}
10567
10568
// ---------------------------------------------------------------------------
10569
10570
509k
const DatabaseContextPtr &PROJStringFormatter::databaseContext() const {
10571
509k
    return d->dbContext_;
10572
509k
}
10573
10574
//! @endcond
10575
10576
// ---------------------------------------------------------------------------
10577
10578
//! @cond Doxygen_Suppress
10579
10580
struct PROJStringParser::Private {
10581
    DatabaseContextPtr dbContext_{};
10582
    PJ_CONTEXT *ctx_{};
10583
    bool usePROJ4InitRules_ = false;
10584
    std::vector<std::string> warningList_{};
10585
10586
    std::string projString_{};
10587
10588
    std::vector<Step> steps_{};
10589
    std::vector<Step::KeyValue> globalParamValues_{};
10590
    std::string title_{};
10591
10592
    bool ignoreNadgrids_ = false;
10593
10594
    template <class T>
10595
    // cppcheck-suppress functionStatic
10596
89.1k
    bool hasParamValue(Step &step, const T key) {
10597
161k
        for (auto &pair : globalParamValues_) {
10598
161k
            if (ci_equal(pair.key, key)) {
10599
1.57k
                pair.usedByParser = true;
10600
1.57k
                return true;
10601
1.57k
            }
10602
161k
        }
10603
213k
        for (auto &pair : step.paramValues) {
10604
213k
            if (ci_equal(pair.key, key)) {
10605
8.36k
                pair.usedByParser = true;
10606
8.36k
                return true;
10607
8.36k
            }
10608
213k
        }
10609
79.2k
        return false;
10610
87.6k
    }
10611
10612
    template <class T>
10613
    // cppcheck-suppress functionStatic
10614
2.29k
    const std::string &getGlobalParamValue(T key) {
10615
15.0k
        for (auto &pair : globalParamValues_) {
10616
15.0k
            if (ci_equal(pair.key, key)) {
10617
1.40k
                pair.usedByParser = true;
10618
1.40k
                return pair.value;
10619
1.40k
            }
10620
15.0k
        }
10621
889
        return emptyString;
10622
2.29k
    }
10623
10624
    template <class T>
10625
    // cppcheck-suppress functionStatic
10626
437k
    const std::string &getParamValue(Step &step, const T key) {
10627
635k
        for (auto &pair : globalParamValues_) {
10628
635k
            if (ci_equal(pair.key, key)) {
10629
7.27k
                pair.usedByParser = true;
10630
7.27k
                return pair.value;
10631
7.27k
            }
10632
635k
        }
10633
943k
        for (auto &pair : step.paramValues) {
10634
943k
            if (ci_equal(pair.key, key)) {
10635
39.4k
                pair.usedByParser = true;
10636
39.4k
                return pair.value;
10637
39.4k
            }
10638
943k
        }
10639
390k
        return emptyString;
10640
430k
    }
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
10626
90.2k
    const std::string &getParamValue(Step &step, const T key) {
10627
144k
        for (auto &pair : globalParamValues_) {
10628
144k
            if (ci_equal(pair.key, key)) {
10629
251
                pair.usedByParser = true;
10630
251
                return pair.value;
10631
251
            }
10632
144k
        }
10633
230k
        for (auto &pair : step.paramValues) {
10634
230k
            if (ci_equal(pair.key, key)) {
10635
3.54k
                pair.usedByParser = true;
10636
3.54k
                return pair.value;
10637
3.54k
            }
10638
230k
        }
10639
86.4k
        return emptyString;
10640
89.9k
    }
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
10626
347k
    const std::string &getParamValue(Step &step, const T key) {
10627
491k
        for (auto &pair : globalParamValues_) {
10628
491k
            if (ci_equal(pair.key, key)) {
10629
7.02k
                pair.usedByParser = true;
10630
7.02k
                return pair.value;
10631
7.02k
            }
10632
491k
        }
10633
713k
        for (auto &pair : step.paramValues) {
10634
713k
            if (ci_equal(pair.key, key)) {
10635
35.8k
                pair.usedByParser = true;
10636
35.8k
                return pair.value;
10637
35.8k
            }
10638
713k
        }
10639
304k
        return emptyString;
10640
340k
    }
10641
10642
1.95k
    static const std::string &getParamValueK(Step &step) {
10643
3.58k
        for (auto &pair : step.paramValues) {
10644
3.58k
            if (ci_equal(pair.key, "k") || ci_equal(pair.key, "k_0")) {
10645
69
                pair.usedByParser = true;
10646
69
                return pair.value;
10647
69
            }
10648
3.58k
        }
10649
1.88k
        return emptyString;
10650
1.95k
    }
10651
10652
    // cppcheck-suppress functionStatic
10653
18.5k
    bool hasUnusedParameters(const Step &step) const {
10654
18.5k
        if (steps_.size() == 1) {
10655
16.0k
            for (const auto &pair : step.paramValues) {
10656
16.0k
                if (pair.key != "no_defs" && !pair.usedByParser) {
10657
5.51k
                    return true;
10658
5.51k
                }
10659
16.0k
            }
10660
15.6k
        }
10661
13.0k
        return false;
10662
18.5k
    }
10663
10664
    // cppcheck-suppress functionStatic
10665
    std::string guessBodyName(double a);
10666
10667
    PrimeMeridianNNPtr buildPrimeMeridian(Step &step);
10668
    GeodeticReferenceFrameNNPtr buildDatum(Step &step,
10669
                                           const std::string &title);
10670
    GeodeticCRSNNPtr buildGeodeticCRS(int iStep, int iUnitConvert,
10671
                                      int iAxisSwap, bool ignorePROJAxis);
10672
    GeodeticCRSNNPtr buildGeocentricCRS(int iStep, int iUnitConvert);
10673
    CRSNNPtr buildProjectedCRS(int iStep, const GeodeticCRSNNPtr &geogCRS,
10674
                               int iUnitConvert, int iAxisSwap);
10675
    CRSNNPtr buildBoundOrCompoundCRSIfNeeded(int iStep, CRSNNPtr crs);
10676
    UnitOfMeasure buildUnit(Step &step, const std::string &unitsParamName,
10677
                            const std::string &toMeterParamName);
10678
10679
    enum class AxisType { REGULAR, NORTH_POLE, SOUTH_POLE };
10680
10681
    std::vector<CoordinateSystemAxisNNPtr>
10682
    processAxisSwap(Step &step, const UnitOfMeasure &unit, int iAxisSwap,
10683
                    AxisType axisType, bool ignorePROJAxis);
10684
10685
    EllipsoidalCSNNPtr buildEllipsoidalCS(int iStep, int iUnitConvert,
10686
                                          int iAxisSwap, bool ignorePROJAxis);
10687
10688
    SphericalCSNNPtr buildSphericalCS(int iStep, int iUnitConvert,
10689
                                      int iAxisSwap, bool ignorePROJAxis);
10690
};
10691
10692
//! @endcond
10693
10694
// ---------------------------------------------------------------------------
10695
10696
13.2k
PROJStringParser::PROJStringParser() : d(std::make_unique<Private>()) {}
10697
10698
// ---------------------------------------------------------------------------
10699
10700
//! @cond Doxygen_Suppress
10701
13.2k
PROJStringParser::~PROJStringParser() = default;
10702
//! @endcond
10703
10704
// ---------------------------------------------------------------------------
10705
10706
/** \brief Attach a database context, to allow queries in it if needed.
10707
 */
10708
PROJStringParser &
10709
13.1k
PROJStringParser::attachDatabaseContext(const DatabaseContextPtr &dbContext) {
10710
13.1k
    d->dbContext_ = dbContext;
10711
13.1k
    return *this;
10712
13.1k
}
10713
10714
// ---------------------------------------------------------------------------
10715
10716
//! @cond Doxygen_Suppress
10717
13.1k
PROJStringParser &PROJStringParser::attachContext(PJ_CONTEXT *ctx) {
10718
13.1k
    d->ctx_ = ctx;
10719
13.1k
    return *this;
10720
13.1k
}
10721
//! @endcond
10722
10723
// ---------------------------------------------------------------------------
10724
10725
/** \brief Set how init=epsg:XXXX syntax should be interpreted.
10726
 *
10727
 * @param enable When set to true,
10728
 * init=epsg:XXXX syntax will be allowed and will be interpreted according to
10729
 * PROJ.4 and PROJ.5 rules, that is geodeticCRS will have longitude, latitude
10730
 * order and will expect/output coordinates in radians. ProjectedCRS will have
10731
 * easting, northing axis order (except the ones with Transverse Mercator South
10732
 * Orientated projection).
10733
 */
10734
13.1k
PROJStringParser &PROJStringParser::setUsePROJ4InitRules(bool enable) {
10735
13.1k
    d->usePROJ4InitRules_ = enable;
10736
13.1k
    return *this;
10737
13.1k
}
10738
10739
// ---------------------------------------------------------------------------
10740
10741
/** \brief Return the list of warnings found during parsing.
10742
 */
10743
0
std::vector<std::string> PROJStringParser::warningList() const {
10744
0
    return d->warningList_;
10745
0
}
10746
10747
// ---------------------------------------------------------------------------
10748
10749
//! @cond Doxygen_Suppress
10750
10751
// ---------------------------------------------------------------------------
10752
10753
static const struct LinearUnitDesc {
10754
    const char *projName;
10755
    const char *convToMeter;
10756
    const char *name;
10757
    int epsgCode;
10758
} linearUnitDescs[] = {
10759
    {"mm", "0.001", "millimetre", 1025},
10760
    {"cm", "0.01", "centimetre", 1033},
10761
    {"m", "1.0", "metre", 9001},
10762
    {"meter", "1.0", "metre", 9001}, // alternative
10763
    {"metre", "1.0", "metre", 9001}, // alternative
10764
    {"ft", "0.3048", "foot", 9002},
10765
    {"us-ft", "0.3048006096012192", "US survey foot", 9003},
10766
    {"fath", "1.8288", "fathom", 9014},
10767
    {"kmi", "1852", "nautical mile", 9030},
10768
    {"us-ch", "20.11684023368047", "US survey chain", 9033},
10769
    {"us-mi", "1609.347218694437", "US survey mile", 9035},
10770
    {"km", "1000.0", "kilometre", 9036},
10771
    {"ind-ft", "0.30479841", "Indian foot (1937)", 9081},
10772
    {"ind-yd", "0.91439523", "Indian yard (1937)", 9085},
10773
    {"mi", "1609.344", "Statute mile", 9093},
10774
    {"yd", "0.9144", "yard", 9096},
10775
    {"ch", "20.1168", "chain", 9097},
10776
    {"link", "0.201168", "link", 9098},
10777
    {"dm", "0.1", "decimetre", 0},                       // no EPSG equivalent
10778
    {"in", "0.0254", "inch", 0},                         // no EPSG equivalent
10779
    {"us-in", "0.025400050800101", "US survey inch", 0}, // no EPSG equivalent
10780
    {"us-yd", "0.914401828803658", "US survey yard", 0}, // no EPSG equivalent
10781
    {"ind-ch", "20.11669506", "Indian chain", 0},        // no EPSG equivalent
10782
};
10783
10784
3.01k
static const LinearUnitDesc *getLinearUnits(const std::string &projName) {
10785
14.0k
    for (const auto &desc : linearUnitDescs) {
10786
14.0k
        if (desc.projName == projName)
10787
2.97k
            return &desc;
10788
14.0k
    }
10789
35
    return nullptr;
10790
3.01k
}
10791
10792
193
static const LinearUnitDesc *getLinearUnits(double toMeter) {
10793
4.29k
    for (const auto &desc : linearUnitDescs) {
10794
4.29k
        if (std::fabs(c_locale_stod(desc.convToMeter) - toMeter) <
10795
4.29k
            1e-10 * toMeter) {
10796
7
            return &desc;
10797
7
        }
10798
4.29k
    }
10799
186
    return nullptr;
10800
193
}
10801
10802
// ---------------------------------------------------------------------------
10803
10804
2.98k
static UnitOfMeasure _buildUnit(const LinearUnitDesc *unitsMatch) {
10805
2.98k
    std::string unitsCode;
10806
2.98k
    if (unitsMatch->epsgCode) {
10807
2.97k
        std::ostringstream buffer;
10808
2.97k
        buffer.imbue(std::locale::classic());
10809
2.97k
        buffer << unitsMatch->epsgCode;
10810
2.97k
        unitsCode = buffer.str();
10811
2.97k
    }
10812
2.98k
    return UnitOfMeasure(
10813
2.98k
        unitsMatch->name, c_locale_stod(unitsMatch->convToMeter),
10814
2.98k
        UnitOfMeasure::Type::LINEAR,
10815
2.98k
        unitsMatch->epsgCode ? Identifier::EPSG : std::string(), unitsCode);
10816
2.98k
}
10817
10818
// ---------------------------------------------------------------------------
10819
10820
186
static UnitOfMeasure _buildUnit(double to_meter_value) {
10821
    // TODO: look-up in EPSG catalog
10822
186
    if (to_meter_value == 0) {
10823
0
        throw ParsingException("invalid unit value");
10824
0
    }
10825
186
    return UnitOfMeasure("unknown", to_meter_value,
10826
186
                         UnitOfMeasure::Type::LINEAR);
10827
186
}
10828
10829
// ---------------------------------------------------------------------------
10830
10831
UnitOfMeasure
10832
PROJStringParser::Private::buildUnit(Step &step,
10833
                                     const std::string &unitsParamName,
10834
36.4k
                                     const std::string &toMeterParamName) {
10835
36.4k
    UnitOfMeasure unit = UnitOfMeasure::METRE;
10836
36.4k
    const LinearUnitDesc *unitsMatch = nullptr;
10837
36.4k
    const auto &projUnits = getParamValue(step, unitsParamName);
10838
36.4k
    if (!projUnits.empty()) {
10839
3.00k
        unitsMatch = getLinearUnits(projUnits);
10840
3.00k
        if (unitsMatch == nullptr) {
10841
24
            throw ParsingException("unhandled " + unitsParamName + "=" +
10842
24
                                   projUnits);
10843
24
        }
10844
3.00k
    }
10845
10846
36.3k
    const auto &toMeter = getParamValue(step, toMeterParamName);
10847
36.3k
    if (!toMeter.empty()) {
10848
203
        double to_meter_value;
10849
203
        try {
10850
203
            to_meter_value = c_locale_stod(toMeter);
10851
203
        } catch (const std::invalid_argument &) {
10852
10
            throw ParsingException("invalid value for " + toMeterParamName);
10853
10
        }
10854
193
        unitsMatch = getLinearUnits(to_meter_value);
10855
193
        if (unitsMatch == nullptr) {
10856
186
            unit = _buildUnit(to_meter_value);
10857
186
        }
10858
193
    }
10859
10860
36.3k
    if (unitsMatch) {
10861
2.98k
        unit = _buildUnit(unitsMatch);
10862
2.98k
    }
10863
10864
36.3k
    return unit;
10865
36.3k
}
10866
10867
// ---------------------------------------------------------------------------
10868
10869
static const struct DatumDesc {
10870
    const char *projName;
10871
    const char *gcsName;
10872
    int gcsCode;
10873
    const char *datumName;
10874
    int datumCode;
10875
    const char *ellipsoidName;
10876
    int ellipsoidCode;
10877
    double a;
10878
    double rf;
10879
} datumDescs[] = {
10880
    {"GGRS87", "GGRS87", 4121, "Greek Geodetic Reference System 1987", 6121,
10881
     "GRS 1980", 7019, 6378137, 298.257222101},
10882
    {"potsdam", "DHDN", 4314, "Deutsches Hauptdreiecksnetz", 6314,
10883
     "Bessel 1841", 7004, 6377397.155, 299.1528128},
10884
    {"carthage", "Carthage", 4223, "Carthage", 6223, "Clarke 1880 (IGN)", 7011,
10885
     6378249.2, 293.4660213},
10886
    {"hermannskogel", "MGI", 4312, "Militar-Geographische Institut", 6312,
10887
     "Bessel 1841", 7004, 6377397.155, 299.1528128},
10888
    {"ire65", "TM65", 4299, "TM65", 6299, "Airy Modified 1849", 7002,
10889
     6377340.189, 299.3249646},
10890
    {"nzgd49", "NZGD49", 4272, "New Zealand Geodetic Datum 1949", 6272,
10891
     "International 1924", 7022, 6378388, 297},
10892
    {"OSGB36", "OSGB 1936", 4277, "OSGB 1936", 6277, "Airy 1830", 7001,
10893
     6377563.396, 299.3249646},
10894
};
10895
10896
// ---------------------------------------------------------------------------
10897
10898
30.8k
static bool isGeographicStep(const std::string &name) {
10899
30.8k
    return name == "longlat" || name == "lonlat" || name == "latlong" ||
10900
18.4k
           name == "latlon";
10901
30.8k
}
10902
10903
// ---------------------------------------------------------------------------
10904
10905
9.39k
static bool isGeocentricStep(const std::string &name) {
10906
9.39k
    return name == "geocent" || name == "cart";
10907
9.39k
}
10908
10909
// ---------------------------------------------------------------------------
10910
10911
19.8k
static bool isTopocentricStep(const std::string &name) {
10912
19.8k
    return name == "topocentric";
10913
19.8k
}
10914
10915
// ---------------------------------------------------------------------------
10916
10917
9.77k
static bool isProjectedStep(const std::string &name) {
10918
9.77k
    if (name == "etmerc" || name == "utm" ||
10919
9.69k
        !getMappingsFromPROJName(name).empty()) {
10920
3.15k
        return true;
10921
3.15k
    }
10922
    // IMPROVE ME: have a better way of distinguishing projections from
10923
    // other
10924
    // transformations.
10925
6.61k
    if (name == "pipeline" || name == "geoc" || name == "deformation" ||
10926
6.60k
        name == "helmert" || name == "hgridshift" || name == "molodensky" ||
10927
6.48k
        name == "vgridshift") {
10928
219
        return false;
10929
219
    }
10930
6.39k
    const auto *operations = proj_list_operations();
10931
594k
    for (int i = 0; operations[i].id != nullptr; ++i) {
10932
594k
        if (name == operations[i].id) {
10933
5.84k
            return true;
10934
5.84k
        }
10935
594k
    }
10936
557
    return false;
10937
6.39k
}
10938
10939
// ---------------------------------------------------------------------------
10940
10941
34.8k
static PropertyMap createMapWithUnknownName() {
10942
34.8k
    return PropertyMap().set(common::IdentifiedObject::NAME_KEY, "unknown");
10943
34.8k
}
10944
10945
// ---------------------------------------------------------------------------
10946
10947
28.4k
PrimeMeridianNNPtr PROJStringParser::Private::buildPrimeMeridian(Step &step) {
10948
10949
28.4k
    PrimeMeridianNNPtr pm = PrimeMeridian::GREENWICH;
10950
28.4k
    const auto &pmStr = getParamValue(step, "pm");
10951
28.4k
    if (!pmStr.empty()) {
10952
2.62k
        char *end;
10953
2.62k
        double pmValue = dmstor(pmStr.c_str(), &end) * RAD_TO_DEG;
10954
2.62k
        if (pmValue != HUGE_VAL && *end == '\0') {
10955
2.54k
            pm = PrimeMeridian::create(createMapWithUnknownName(),
10956
2.54k
                                       Angle(pmValue));
10957
2.54k
        } else {
10958
80
            bool found = false;
10959
80
            if (pmStr == "paris") {
10960
35
                found = true;
10961
35
                pm = PrimeMeridian::PARIS;
10962
35
            }
10963
80
            auto proj_prime_meridians = proj_list_prime_meridians();
10964
494
            for (int i = 0; !found && proj_prime_meridians[i].id != nullptr;
10965
458
                 i++) {
10966
458
                if (pmStr == proj_prime_meridians[i].id) {
10967
44
                    found = true;
10968
44
                    std::string name = static_cast<char>(::toupper(pmStr[0])) +
10969
44
                                       pmStr.substr(1);
10970
44
                    pmValue = dmstor(proj_prime_meridians[i].defn, nullptr) *
10971
44
                              RAD_TO_DEG;
10972
44
                    pm = PrimeMeridian::create(
10973
44
                        PropertyMap().set(IdentifiedObject::NAME_KEY, name),
10974
44
                        Angle(pmValue));
10975
44
                    break;
10976
44
                }
10977
458
            }
10978
80
            if (!found) {
10979
1
                throw ParsingException("unknown pm " + pmStr);
10980
1
            }
10981
80
        }
10982
2.62k
    }
10983
28.4k
    return pm;
10984
28.4k
}
10985
10986
// ---------------------------------------------------------------------------
10987
10988
945
std::string PROJStringParser::Private::guessBodyName(double a) {
10989
10990
945
    auto ret = Ellipsoid::guessBodyName(dbContext_, a);
10991
945
    if (ret == NON_EARTH_BODY && dbContext_ == nullptr && ctx_ != nullptr) {
10992
67
        dbContext_ =
10993
67
            ctx_->get_cpp_context()->getDatabaseContext().as_nullable();
10994
67
        if (dbContext_) {
10995
67
            ret = Ellipsoid::guessBodyName(dbContext_, a);
10996
67
        }
10997
67
    }
10998
945
    return ret;
10999
945
}
11000
11001
// ---------------------------------------------------------------------------
11002
11003
GeodeticReferenceFrameNNPtr
11004
18.5k
PROJStringParser::Private::buildDatum(Step &step, const std::string &title) {
11005
11006
18.5k
    std::string ellpsStr = getParamValue(step, "ellps");
11007
18.5k
    const auto &datumStr = getParamValue(step, "datum");
11008
18.5k
    const auto &RStr = getParamValue(step, "R");
11009
18.5k
    const auto &aStr = getParamValue(step, "a");
11010
18.5k
    const auto &bStr = getParamValue(step, "b");
11011
18.5k
    const auto &rfStr = getParamValue(step, "rf");
11012
18.5k
    const auto &fStr = getParamValue(step, "f");
11013
18.5k
    const auto &esStr = getParamValue(step, "es");
11014
18.5k
    const auto &eStr = getParamValue(step, "e");
11015
18.5k
    double a = -1.0;
11016
18.5k
    double b = -1.0;
11017
18.5k
    double rf = -1.0;
11018
18.5k
    const util::optional<std::string> optionalEmptyString{};
11019
18.5k
    const bool numericParamPresent =
11020
18.5k
        !RStr.empty() || !aStr.empty() || !bStr.empty() || !rfStr.empty() ||
11021
17.7k
        !fStr.empty() || !esStr.empty() || !eStr.empty();
11022
11023
18.5k
    if (!numericParamPresent && ellpsStr.empty() && datumStr.empty() &&
11024
15.5k
        (step.name == "krovak" || step.name == "mod_krovak")) {
11025
693
        ellpsStr = "bessel";
11026
693
    }
11027
11028
18.5k
    PrimeMeridianNNPtr pm(buildPrimeMeridian(step));
11029
18.5k
    PropertyMap grfMap;
11030
11031
18.5k
    const auto &nadgrids = getParamValue(step, "nadgrids");
11032
18.5k
    const auto &towgs84 = getParamValue(step, "towgs84");
11033
18.5k
    std::string datumNameSuffix;
11034
18.5k
    if (!nadgrids.empty()) {
11035
1.33k
        datumNameSuffix = " using nadgrids=" + nadgrids;
11036
17.1k
    } else if (!towgs84.empty()) {
11037
1.11k
        datumNameSuffix = " using towgs84=" + towgs84;
11038
1.11k
    }
11039
11040
    // It is arguable that we allow the prime meridian of a datum defined by
11041
    // its name to be overridden, but this is found at least in a regression
11042
    // test
11043
    // of GDAL. So let's keep the ellipsoid part of the datum in that case and
11044
    // use the specified prime meridian.
11045
18.5k
    const auto overridePmIfNeeded =
11046
18.5k
        [&pm, &datumNameSuffix](const GeodeticReferenceFrameNNPtr &grf) {
11047
15.8k
            if (pm->_isEquivalentTo(PrimeMeridian::GREENWICH.get())) {
11048
14.8k
                return grf;
11049
14.8k
            } else {
11050
986
                return GeodeticReferenceFrame::create(
11051
986
                    PropertyMap().set(IdentifiedObject::NAME_KEY,
11052
986
                                      UNKNOWN_BASED_ON +
11053
986
                                          grf->ellipsoid()->nameStr() +
11054
986
                                          " ellipsoid" + datumNameSuffix),
11055
986
                    grf->ellipsoid(), grf->anchorDefinition(), pm);
11056
986
            }
11057
15.8k
        };
11058
11059
    // R take precedence
11060
18.5k
    if (!RStr.empty()) {
11061
137
        double R;
11062
137
        try {
11063
137
            R = c_locale_stod(RStr);
11064
137
        } catch (const std::invalid_argument &) {
11065
22
            throw ParsingException("Invalid R value");
11066
22
        }
11067
115
        auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
11068
115
                                                 Length(R), guessBodyName(R));
11069
115
        return GeodeticReferenceFrame::create(
11070
115
            grfMap.set(IdentifiedObject::NAME_KEY,
11071
115
                       title.empty() ? "unknown" + datumNameSuffix : title),
11072
115
            ellipsoid, optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
11073
137
    }
11074
11075
18.3k
    if (!datumStr.empty()) {
11076
1.81k
        auto l_datum = [&datumStr, &overridePmIfNeeded, &grfMap,
11077
1.81k
                        &optionalEmptyString, &pm]() {
11078
1.81k
            if (datumStr == "WGS84") {
11079
3
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
11080
1.80k
            } else if (datumStr == "NAD83") {
11081
317
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6269);
11082
1.49k
            } else if (datumStr == "NAD27") {
11083
679
                return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6267);
11084
813
            } else {
11085
11086
4.53k
                for (const auto &datumDesc : datumDescs) {
11087
4.53k
                    if (datumStr == datumDesc.projName) {
11088
792
                        (void)datumDesc.gcsName; // to please cppcheck
11089
792
                        (void)datumDesc.gcsCode; // to please cppcheck
11090
792
                        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11091
792
                            grfMap
11092
792
                                .set(IdentifiedObject::NAME_KEY,
11093
792
                                     datumDesc.ellipsoidName)
11094
792
                                .set(Identifier::CODESPACE_KEY,
11095
792
                                     Identifier::EPSG)
11096
792
                                .set(Identifier::CODE_KEY,
11097
792
                                     datumDesc.ellipsoidCode),
11098
792
                            Length(datumDesc.a), Scale(datumDesc.rf));
11099
792
                        return GeodeticReferenceFrame::create(
11100
792
                            grfMap
11101
792
                                .set(IdentifiedObject::NAME_KEY,
11102
792
                                     datumDesc.datumName)
11103
792
                                .set(Identifier::CODESPACE_KEY,
11104
792
                                     Identifier::EPSG)
11105
792
                                .set(Identifier::CODE_KEY, datumDesc.datumCode),
11106
792
                            ellipsoid, optionalEmptyString, pm);
11107
792
                    }
11108
4.53k
                }
11109
813
            }
11110
21
            throw ParsingException("unknown datum " + datumStr);
11111
1.81k
        }();
11112
1.81k
        if (!numericParamPresent) {
11113
1.34k
            return l_datum;
11114
1.34k
        }
11115
472
        a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
11116
472
        rf = l_datum->ellipsoid()->computedInverseFlattening();
11117
472
    }
11118
11119
16.5k
    else if (!ellpsStr.empty()) {
11120
1.33k
        auto l_datum = [&ellpsStr, &title, &grfMap, &optionalEmptyString, &pm,
11121
1.33k
                        &datumNameSuffix]() {
11122
1.33k
            if (ellpsStr == "WGS84") {
11123
42
                return GeodeticReferenceFrame::create(
11124
42
                    grfMap.set(IdentifiedObject::NAME_KEY,
11125
42
                               title.empty() ? std::string(UNKNOWN_BASED_ON)
11126
36
                                                   .append("WGS 84 ellipsoid")
11127
36
                                                   .append(datumNameSuffix)
11128
42
                                             : title),
11129
42
                    Ellipsoid::WGS84, optionalEmptyString, pm);
11130
1.29k
            } else if (ellpsStr == "GRS80") {
11131
27
                return GeodeticReferenceFrame::create(
11132
27
                    grfMap.set(IdentifiedObject::NAME_KEY,
11133
27
                               title.empty() ? std::string(UNKNOWN_BASED_ON)
11134
9
                                                   .append("GRS 1980 ellipsoid")
11135
9
                                                   .append(datumNameSuffix)
11136
27
                                             : title),
11137
27
                    Ellipsoid::GRS1980, optionalEmptyString, pm);
11138
1.27k
            } else {
11139
1.27k
                auto proj_ellps = proj_list_ellps();
11140
18.0k
                for (int i = 0; proj_ellps[i].id != nullptr; i++) {
11141
18.0k
                    if (ellpsStr == proj_ellps[i].id) {
11142
1.25k
                        assert(strncmp(proj_ellps[i].major, "a=", 2) == 0);
11143
1.25k
                        const double a_iter =
11144
1.25k
                            c_locale_stod(proj_ellps[i].major + 2);
11145
1.25k
                        EllipsoidPtr ellipsoid;
11146
1.25k
                        PropertyMap ellpsMap;
11147
1.25k
                        if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) {
11148
46
                            const double b_iter =
11149
46
                                c_locale_stod(proj_ellps[i].ell + 2);
11150
46
                            ellipsoid =
11151
46
                                Ellipsoid::createTwoAxis(
11152
46
                                    ellpsMap.set(IdentifiedObject::NAME_KEY,
11153
46
                                                 proj_ellps[i].name),
11154
46
                                    Length(a_iter), Length(b_iter))
11155
46
                                    .as_nullable();
11156
1.20k
                        } else {
11157
1.20k
                            assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0);
11158
1.20k
                            const double rf_iter =
11159
1.20k
                                c_locale_stod(proj_ellps[i].ell + 3);
11160
1.20k
                            ellipsoid =
11161
1.20k
                                Ellipsoid::createFlattenedSphere(
11162
1.20k
                                    ellpsMap.set(IdentifiedObject::NAME_KEY,
11163
1.20k
                                                 proj_ellps[i].name),
11164
1.20k
                                    Length(a_iter), Scale(rf_iter))
11165
1.20k
                                    .as_nullable();
11166
1.20k
                        }
11167
1.25k
                        return GeodeticReferenceFrame::create(
11168
1.25k
                            grfMap.set(IdentifiedObject::NAME_KEY,
11169
1.25k
                                       title.empty()
11170
1.25k
                                           ? std::string(UNKNOWN_BASED_ON)
11171
1.13k
                                                 .append(proj_ellps[i].name)
11172
1.13k
                                                 .append(" ellipsoid")
11173
1.13k
                                                 .append(datumNameSuffix)
11174
1.25k
                                           : title),
11175
1.25k
                            NN_NO_CHECK(ellipsoid), optionalEmptyString, pm);
11176
1.25k
                    }
11177
18.0k
                }
11178
15
                throw ParsingException("unknown ellipsoid " + ellpsStr);
11179
1.27k
            }
11180
1.33k
        }();
11181
1.33k
        if (!numericParamPresent) {
11182
1.24k
            return l_datum;
11183
1.24k
        }
11184
97
        a = l_datum->ellipsoid()->semiMajorAxis().getSIValue();
11185
97
        if (l_datum->ellipsoid()->semiMinorAxis().has_value()) {
11186
28
            b = l_datum->ellipsoid()->semiMinorAxis()->getSIValue();
11187
69
        } else {
11188
69
            rf = l_datum->ellipsoid()->computedInverseFlattening();
11189
69
        }
11190
97
    }
11191
11192
15.8k
    if (!aStr.empty()) {
11193
555
        try {
11194
555
            a = c_locale_stod(aStr);
11195
555
        } catch (const std::invalid_argument &) {
11196
46
            throw ParsingException("Invalid a value");
11197
46
        }
11198
555
    }
11199
11200
15.7k
    const auto createGRF = [&grfMap, &title, &optionalEmptyString,
11201
15.7k
                            &datumNameSuffix,
11202
15.7k
                            &pm](const EllipsoidNNPtr &ellipsoid) {
11203
830
        std::string datumName(title);
11204
830
        if (title.empty()) {
11205
663
            if (ellipsoid->nameStr() != "unknown") {
11206
79
                datumName = UNKNOWN_BASED_ON;
11207
79
                datumName += ellipsoid->nameStr();
11208
79
                datumName += " ellipsoid";
11209
584
            } else {
11210
584
                datumName = "unknown";
11211
584
            }
11212
663
            datumName += datumNameSuffix;
11213
663
        }
11214
830
        return GeodeticReferenceFrame::create(
11215
830
            grfMap.set(IdentifiedObject::NAME_KEY, datumName), ellipsoid,
11216
830
            optionalEmptyString, fixupPrimeMeridan(ellipsoid, pm));
11217
830
    };
11218
11219
15.7k
    if (a > 0 && (b > 0 || !bStr.empty())) {
11220
158
        if (!bStr.empty()) {
11221
130
            try {
11222
130
                b = c_locale_stod(bStr);
11223
130
            } catch (const std::invalid_argument &) {
11224
8
                throw ParsingException("Invalid b value");
11225
8
            }
11226
130
        }
11227
150
        auto ellipsoid =
11228
150
            Ellipsoid::createTwoAxis(createMapWithUnknownName(), Length(a),
11229
150
                                     Length(b), guessBodyName(a))
11230
150
                ->identify();
11231
150
        return createGRF(ellipsoid);
11232
158
    }
11233
11234
15.6k
    else if (a > 0 && (rf >= 0 || !rfStr.empty())) {
11235
476
        if (!rfStr.empty()) {
11236
17
            try {
11237
17
                rf = c_locale_stod(rfStr);
11238
17
            } catch (const std::invalid_argument &) {
11239
7
                throw ParsingException("Invalid rf value");
11240
7
            }
11241
17
        }
11242
469
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11243
469
                             createMapWithUnknownName(), Length(a), Scale(rf),
11244
469
                             guessBodyName(a))
11245
469
                             ->identify();
11246
469
        return createGRF(ellipsoid);
11247
476
    }
11248
11249
15.1k
    else if (a > 0 && !fStr.empty()) {
11250
10
        double f;
11251
10
        try {
11252
10
            f = c_locale_stod(fStr);
11253
10
        } catch (const std::invalid_argument &) {
11254
3
            throw ParsingException("Invalid f value");
11255
3
        }
11256
7
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11257
7
                             createMapWithUnknownName(), Length(a),
11258
7
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11259
7
                             ->identify();
11260
7
        return createGRF(ellipsoid);
11261
10
    }
11262
11263
15.1k
    else if (a > 0 && !eStr.empty()) {
11264
9
        double e;
11265
9
        try {
11266
9
            e = c_locale_stod(eStr);
11267
9
        } catch (const std::invalid_argument &) {
11268
4
            throw ParsingException("Invalid e value");
11269
4
        }
11270
5
        double alpha = asin(e);    /* angular eccentricity */
11271
5
        double f = 1 - cos(alpha); /* = 1 - sqrt (1 - es); */
11272
5
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11273
5
                             createMapWithUnknownName(), Length(a),
11274
5
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11275
5
                             ->identify();
11276
5
        return createGRF(ellipsoid);
11277
9
    }
11278
11279
15.1k
    else if (a > 0 && !esStr.empty()) {
11280
3
        double es;
11281
3
        try {
11282
3
            es = c_locale_stod(esStr);
11283
3
        } catch (const std::invalid_argument &) {
11284
3
            throw ParsingException("Invalid es value");
11285
3
        }
11286
0
        double f = 1 - sqrt(1 - es);
11287
0
        auto ellipsoid = Ellipsoid::createFlattenedSphere(
11288
0
                             createMapWithUnknownName(), Length(a),
11289
0
                             Scale(f != 0.0 ? 1.0 / f : 0.0), guessBodyName(a))
11290
0
                             ->identify();
11291
0
        return createGRF(ellipsoid);
11292
3
    }
11293
11294
    // If only a is specified, create a sphere
11295
15.1k
    if (a > 0 && bStr.empty() && rfStr.empty() && eStr.empty() &&
11296
199
        esStr.empty()) {
11297
199
        auto ellipsoid = Ellipsoid::createSphere(createMapWithUnknownName(),
11298
199
                                                 Length(a), guessBodyName(a));
11299
199
        return createGRF(ellipsoid);
11300
199
    }
11301
11302
14.9k
    if (!bStr.empty() && aStr.empty()) {
11303
6
        throw ParsingException("b found, but a missing");
11304
6
    }
11305
11306
14.8k
    if (!rfStr.empty() && aStr.empty()) {
11307
4
        throw ParsingException("rf found, but a missing");
11308
4
    }
11309
11310
14.8k
    if (!fStr.empty() && aStr.empty()) {
11311
4
        throw ParsingException("f found, but a missing");
11312
4
    }
11313
11314
14.8k
    if (!eStr.empty() && aStr.empty()) {
11315
7
        throw ParsingException("e found, but a missing");
11316
7
    }
11317
11318
14.8k
    if (!esStr.empty() && aStr.empty()) {
11319
3
        throw ParsingException("es found, but a missing");
11320
3
    }
11321
11322
14.8k
    return overridePmIfNeeded(GeodeticReferenceFrame::EPSG_6326);
11323
14.8k
}
11324
11325
// ---------------------------------------------------------------------------
11326
11327
static const MeridianPtr nullMeridian{};
11328
11329
static CoordinateSystemAxisNNPtr
11330
createAxis(const std::string &name, const std::string &abbreviation,
11331
           const AxisDirection &direction, const common::UnitOfMeasure &unit,
11332
102k
           const MeridianPtr &meridian = nullMeridian) {
11333
102k
    return CoordinateSystemAxis::create(
11334
102k
        PropertyMap().set(IdentifiedObject::NAME_KEY, name), abbreviation,
11335
102k
        direction, unit, meridian);
11336
102k
}
11337
11338
std::vector<CoordinateSystemAxisNNPtr>
11339
PROJStringParser::Private::processAxisSwap(Step &step,
11340
                                           const UnitOfMeasure &unit,
11341
                                           int iAxisSwap, AxisType axisType,
11342
25.6k
                                           bool ignorePROJAxis) {
11343
25.6k
    assert(iAxisSwap < 0 || ci_equal(steps_[iAxisSwap].name, "axisswap"));
11344
11345
25.6k
    const bool isGeographic = unit.type() == UnitOfMeasure::Type::ANGULAR;
11346
25.6k
    const bool isSpherical = isGeographic && hasParamValue(step, "geoc");
11347
25.6k
    const auto &eastName = isSpherical    ? "Planetocentric longitude"
11348
25.6k
                           : isGeographic ? AxisName::Longitude
11349
25.3k
                                          : AxisName::Easting;
11350
25.6k
    const auto &eastAbbev = isSpherical    ? "V"
11351
25.6k
                            : isGeographic ? AxisAbbreviation::lon
11352
25.3k
                                           : AxisAbbreviation::E;
11353
25.6k
    const auto &eastDir =
11354
25.6k
        isGeographic                         ? AxisDirection::EAST
11355
25.6k
        : (axisType == AxisType::NORTH_POLE) ? AxisDirection::SOUTH
11356
8.39k
        : (axisType == AxisType::SOUTH_POLE) ? AxisDirection::NORTH
11357
8.36k
                                             : AxisDirection::EAST;
11358
25.6k
    CoordinateSystemAxisNNPtr east = createAxis(
11359
25.6k
        eastName, eastAbbev, eastDir, unit,
11360
25.6k
        (!isGeographic &&
11361
8.39k
         (axisType == AxisType::NORTH_POLE || axisType == AxisType::SOUTH_POLE))
11362
25.6k
            ? Meridian::create(Angle(90, UnitOfMeasure::DEGREE)).as_nullable()
11363
25.6k
            : nullMeridian);
11364
11365
25.6k
    const auto &northName = isSpherical    ? "Planetocentric latitude"
11366
25.6k
                            : isGeographic ? AxisName::Latitude
11367
25.3k
                                           : AxisName::Northing;
11368
25.6k
    const auto &northAbbev = isSpherical    ? "U"
11369
25.6k
                             : isGeographic ? AxisAbbreviation::lat
11370
25.3k
                                            : AxisAbbreviation::N;
11371
25.6k
    const auto &northDir = isGeographic ? AxisDirection::NORTH
11372
25.6k
                           : (axisType == AxisType::NORTH_POLE)
11373
8.39k
                               ? AxisDirection::SOUTH
11374
                               /*: (axisType == AxisType::SOUTH_POLE)
11375
                                     ? AxisDirection::NORTH*/
11376
8.39k
                               : AxisDirection::NORTH;
11377
25.6k
    const CoordinateSystemAxisNNPtr north = createAxis(
11378
25.6k
        northName, northAbbev, northDir, unit,
11379
25.6k
        isGeographic ? nullMeridian
11380
25.6k
        : (axisType == AxisType::NORTH_POLE)
11381
8.39k
            ? Meridian::create(Angle(180, UnitOfMeasure::DEGREE)).as_nullable()
11382
8.39k
        : (axisType == AxisType::SOUTH_POLE)
11383
8.36k
            ? Meridian::create(Angle(0, UnitOfMeasure::DEGREE)).as_nullable()
11384
8.36k
            : nullMeridian);
11385
11386
25.6k
    CoordinateSystemAxisNNPtr west =
11387
25.6k
        createAxis(isSpherical    ? "Planetocentric longitude"
11388
25.6k
                   : isGeographic ? AxisName::Longitude
11389
25.3k
                                  : AxisName::Westing,
11390
25.6k
                   isSpherical    ? "V"
11391
25.6k
                   : isGeographic ? AxisAbbreviation::lon
11392
25.3k
                                  : std::string(),
11393
25.6k
                   AxisDirection::WEST, unit);
11394
11395
25.6k
    CoordinateSystemAxisNNPtr south =
11396
25.6k
        createAxis(isSpherical    ? "Planetocentric latitude"
11397
25.6k
                   : isGeographic ? AxisName::Latitude
11398
25.3k
                                  : AxisName::Southing,
11399
25.6k
                   isSpherical    ? "U"
11400
25.6k
                   : isGeographic ? AxisAbbreviation::lat
11401
25.3k
                                  : std::string(),
11402
25.6k
                   AxisDirection::SOUTH, unit);
11403
11404
25.6k
    std::vector<CoordinateSystemAxisNNPtr> axis{east, north};
11405
11406
25.6k
    const auto &axisStr = getParamValue(step, "axis");
11407
25.6k
    if (!ignorePROJAxis && !axisStr.empty()) {
11408
303
        if (axisStr.size() == 3) {
11409
887
            for (int i = 0; i < 2; i++) {
11410
602
                if (axisStr[i] == 'n') {
11411
150
                    axis[i] = north;
11412
452
                } else if (axisStr[i] == 's') {
11413
145
                    axis[i] = south;
11414
307
                } else if (axisStr[i] == 'e') {
11415
107
                    axis[i] = east;
11416
200
                } else if (axisStr[i] == 'w') {
11417
182
                    axis[i] = west;
11418
182
                } else {
11419
18
                    throw ParsingException("Unhandled axis=" + axisStr);
11420
18
                }
11421
602
            }
11422
303
        } else {
11423
0
            throw ParsingException("Unhandled axis=" + axisStr);
11424
0
        }
11425
25.3k
    } else if (iAxisSwap >= 0) {
11426
117
        auto &stepAxisSwap = steps_[iAxisSwap];
11427
117
        const auto &orderStr = getParamValue(stepAxisSwap, "order");
11428
117
        auto orderTab = split(orderStr, ',');
11429
117
        if (orderTab.size() != 2) {
11430
29
            throw ParsingException("Unhandled order=" + orderStr);
11431
29
        }
11432
88
        if (stepAxisSwap.inverted) {
11433
0
            throw ParsingException("Unhandled +inv for +proj=axisswap");
11434
0
        }
11435
11436
243
        for (size_t i = 0; i < 2; i++) {
11437
172
            if (orderTab[i] == "1") {
11438
30
                axis[i] = east;
11439
142
            } else if (orderTab[i] == "-1") {
11440
49
                axis[i] = west;
11441
93
            } else if (orderTab[i] == "2") {
11442
2
                axis[i] = north;
11443
91
            } else if (orderTab[i] == "-2") {
11444
74
                axis[i] = south;
11445
74
            } else {
11446
17
                throw ParsingException("Unhandled order=" + orderStr);
11447
17
            }
11448
172
        }
11449
25.1k
    } else if ((step.name == "krovak" || step.name == "mod_krovak") &&
11450
1.57k
               hasParamValue(step, "czech")) {
11451
70
        axis[0] = std::move(west);
11452
70
        axis[1] = std::move(south);
11453
70
    }
11454
25.5k
    return axis;
11455
25.6k
}
11456
11457
// ---------------------------------------------------------------------------
11458
11459
EllipsoidalCSNNPtr PROJStringParser::Private::buildEllipsoidalCS(
11460
17.0k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11461
17.0k
    auto &step = steps_[iStep];
11462
17.0k
    assert(iUnitConvert < 0 ||
11463
17.0k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11464
11465
17.0k
    UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
11466
17.0k
    if (iUnitConvert >= 0) {
11467
53
        auto &stepUnitConvert = steps_[iUnitConvert];
11468
53
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11469
53
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11470
53
        if (stepUnitConvert.inverted) {
11471
0
            std::swap(xy_in, xy_out);
11472
0
        }
11473
53
        if (iUnitConvert < iStep) {
11474
41
            std::swap(xy_in, xy_out);
11475
41
        }
11476
53
        if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
11477
44
            (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
11478
44
            throw ParsingException("unhandled values for xy_in and/or xy_out");
11479
44
        }
11480
9
        if (*xy_out == "rad") {
11481
5
            angularUnit = UnitOfMeasure::RADIAN;
11482
5
        } else if (*xy_out == "grad") {
11483
4
            angularUnit = UnitOfMeasure::GRAD;
11484
4
        }
11485
9
    }
11486
11487
17.0k
    std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
11488
17.0k
        step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
11489
17.0k
    CoordinateSystemAxisNNPtr up = CoordinateSystemAxis::create(
11490
17.0k
        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
11491
17.0k
                                AxisName::Ellipsoidal_height),
11492
17.0k
        AxisAbbreviation::h, AxisDirection::UP,
11493
17.0k
        buildUnit(step, "vunits", "vto_meter"));
11494
11495
17.0k
    return (!hasParamValue(step, "geoidgrids") &&
11496
9.71k
            (hasParamValue(step, "vunits") || hasParamValue(step, "vto_meter")))
11497
17.0k
               ? EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1], up)
11498
17.0k
               : EllipsoidalCS::create(emptyPropertyMap, axis[0], axis[1]);
11499
17.0k
}
11500
11501
// ---------------------------------------------------------------------------
11502
11503
SphericalCSNNPtr PROJStringParser::Private::buildSphericalCS(
11504
255
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11505
255
    auto &step = steps_[iStep];
11506
255
    assert(iUnitConvert < 0 ||
11507
255
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11508
11509
255
    UnitOfMeasure angularUnit = UnitOfMeasure::DEGREE;
11510
255
    if (iUnitConvert >= 0) {
11511
45
        auto &stepUnitConvert = steps_[iUnitConvert];
11512
45
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11513
45
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11514
45
        if (stepUnitConvert.inverted) {
11515
0
            std::swap(xy_in, xy_out);
11516
0
        }
11517
45
        if (iUnitConvert < iStep) {
11518
35
            std::swap(xy_in, xy_out);
11519
35
        }
11520
45
        if (xy_in->empty() || xy_out->empty() || *xy_in != "rad" ||
11521
43
            (*xy_out != "rad" && *xy_out != "deg" && *xy_out != "grad")) {
11522
43
            throw ParsingException("unhandled values for xy_in and/or xy_out");
11523
43
        }
11524
2
        if (*xy_out == "rad") {
11525
0
            angularUnit = UnitOfMeasure::RADIAN;
11526
2
        } else if (*xy_out == "grad") {
11527
2
            angularUnit = UnitOfMeasure::GRAD;
11528
2
        }
11529
2
    }
11530
11531
212
    std::vector<CoordinateSystemAxisNNPtr> axis = processAxisSwap(
11532
212
        step, angularUnit, iAxisSwap, AxisType::REGULAR, ignorePROJAxis);
11533
11534
212
    return SphericalCS::create(emptyPropertyMap, axis[0], axis[1]);
11535
255
}
11536
11537
// ---------------------------------------------------------------------------
11538
11539
static double getNumericValue(const std::string &paramValue,
11540
4.23k
                              bool *pHasError = nullptr) {
11541
4.23k
    bool success;
11542
4.23k
    double value = c_locale_stod(paramValue, success);
11543
4.23k
    if (pHasError)
11544
338
        *pHasError = !success;
11545
4.23k
    return value;
11546
4.23k
}
11547
11548
// ---------------------------------------------------------------------------
11549
namespace {
11550
16.2k
template <class T> inline void ignoreRetVal(T) {}
11551
} // namespace
11552
11553
GeodeticCRSNNPtr PROJStringParser::Private::buildGeodeticCRS(
11554
16.2k
    int iStep, int iUnitConvert, int iAxisSwap, bool ignorePROJAxis) {
11555
16.2k
    auto &step = steps_[iStep];
11556
11557
16.2k
    const bool l_isGeographicStep = isGeographicStep(step.name);
11558
16.2k
    const auto &title = l_isGeographicStep ? title_ : emptyString;
11559
11560
    // units=m is often found in the wild.
11561
    // No need to create a extension string for this
11562
16.2k
    ignoreRetVal(hasParamValue(step, "units"));
11563
11564
16.2k
    auto datum = buildDatum(step, title);
11565
11566
16.2k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
11567
16.2k
                                   title.empty() ? "unknown" : title);
11568
11569
16.2k
    if (l_isGeographicStep &&
11570
7.98k
        (hasUnusedParameters(step) ||
11571
5.74k
         getNumericValue(getParamValue(step, "lon_0")) != 0.0)) {
11572
5.74k
        props.set("EXTENSION_PROJ4", projString_);
11573
5.74k
    }
11574
16.2k
    props.set("IMPLICIT_CS", true);
11575
11576
16.2k
    if (!hasParamValue(step, "geoc")) {
11577
15.8k
        auto cs =
11578
15.8k
            buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
11579
11580
15.8k
        return GeographicCRS::create(props, datum, cs);
11581
15.8k
    } else {
11582
404
        auto cs =
11583
404
            buildSphericalCS(iStep, iUnitConvert, iAxisSwap, ignorePROJAxis);
11584
11585
404
        return GeodeticCRS::create(props, datum, cs);
11586
404
    }
11587
16.2k
}
11588
11589
// ---------------------------------------------------------------------------
11590
11591
GeodeticCRSNNPtr
11592
2.29k
PROJStringParser::Private::buildGeocentricCRS(int iStep, int iUnitConvert) {
11593
2.29k
    auto &step = steps_[iStep];
11594
11595
2.29k
    assert(isGeocentricStep(step.name) || isTopocentricStep(step.name));
11596
2.29k
    assert(iUnitConvert < 0 ||
11597
2.29k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11598
11599
2.29k
    const auto &title = title_;
11600
11601
2.29k
    auto datum = buildDatum(step, title);
11602
11603
2.29k
    UnitOfMeasure unit = buildUnit(step, "units", "");
11604
2.29k
    if (iUnitConvert >= 0) {
11605
21
        auto &stepUnitConvert = steps_[iUnitConvert];
11606
21
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
11607
21
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
11608
21
        const std::string *z_in = &getParamValue(stepUnitConvert, "z_in");
11609
21
        const std::string *z_out = &getParamValue(stepUnitConvert, "z_out");
11610
21
        if (stepUnitConvert.inverted) {
11611
0
            std::swap(xy_in, xy_out);
11612
0
            std::swap(z_in, z_out);
11613
0
        }
11614
21
        if (xy_in->empty() || xy_out->empty() || *xy_in != "m" ||
11615
21
            *z_in != "m" || *xy_out != *z_out) {
11616
21
            throw ParsingException(
11617
21
                "unhandled values for xy_in, z_in, xy_out or z_out");
11618
21
        }
11619
11620
0
        const LinearUnitDesc *unitsMatch = nullptr;
11621
0
        try {
11622
0
            double to_meter_value = c_locale_stod(*xy_out);
11623
0
            unitsMatch = getLinearUnits(to_meter_value);
11624
0
            if (unitsMatch == nullptr) {
11625
0
                unit = _buildUnit(to_meter_value);
11626
0
            }
11627
0
        } catch (const std::invalid_argument &) {
11628
0
            unitsMatch = getLinearUnits(*xy_out);
11629
0
            if (!unitsMatch) {
11630
0
                throw ParsingException(
11631
0
                    "unhandled values for xy_in, z_in, xy_out or z_out");
11632
0
            }
11633
0
            unit = _buildUnit(unitsMatch);
11634
0
        }
11635
0
    }
11636
11637
2.27k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
11638
2.27k
                                   title.empty() ? "unknown" : title);
11639
2.27k
    auto cs = CartesianCS::createGeocentric(unit);
11640
11641
2.27k
    if (hasUnusedParameters(step)) {
11642
86
        props.set("EXTENSION_PROJ4", projString_);
11643
86
    }
11644
11645
2.27k
    return GeodeticCRS::create(props, datum, cs);
11646
2.29k
}
11647
11648
// ---------------------------------------------------------------------------
11649
11650
CRSNNPtr
11651
PROJStringParser::Private::buildBoundOrCompoundCRSIfNeeded(int iStep,
11652
17.8k
                                                           CRSNNPtr crs) {
11653
17.8k
    auto &step = steps_[iStep];
11654
17.8k
    const auto &nadgrids = getParamValue(step, "nadgrids");
11655
17.8k
    const auto &towgs84 = getParamValue(step, "towgs84");
11656
    // nadgrids has the priority over towgs84
11657
17.8k
    if (!ignoreNadgrids_ && !nadgrids.empty()) {
11658
1.36k
        crs = BoundCRS::createFromNadgrids(crs, nadgrids);
11659
16.5k
    } else if (!towgs84.empty()) {
11660
1.06k
        std::vector<double> towgs84Values;
11661
1.06k
        const auto tokens = split(towgs84, ',');
11662
3.31k
        for (const auto &str : tokens) {
11663
3.31k
            try {
11664
3.31k
                towgs84Values.push_back(c_locale_stod(str));
11665
3.31k
            } catch (const std::invalid_argument &) {
11666
133
                throw ParsingException("Non numerical value in towgs84 clause");
11667
133
            }
11668
3.31k
        }
11669
11670
929
        if (towgs84Values.size() == 7 && dbContext_) {
11671
4
            if (dbContext_->toWGS84AutocorrectWrongValues(
11672
4
                    towgs84Values[0], towgs84Values[1], towgs84Values[2],
11673
4
                    towgs84Values[3], towgs84Values[4], towgs84Values[5],
11674
4
                    towgs84Values[6])) {
11675
0
                for (auto &pair : step.paramValues) {
11676
0
                    if (ci_equal(pair.key, "towgs84")) {
11677
0
                        pair.value.clear();
11678
0
                        for (int i = 0; i < 7; ++i) {
11679
0
                            if (i > 0)
11680
0
                                pair.value += ',';
11681
0
                            pair.value += internal::toString(towgs84Values[i]);
11682
0
                        }
11683
0
                        break;
11684
0
                    }
11685
0
                }
11686
0
            }
11687
4
        }
11688
11689
929
        crs = BoundCRS::createFromTOWGS84(crs, towgs84Values);
11690
929
    }
11691
11692
17.7k
    const auto &geoidgrids = getParamValue(step, "geoidgrids");
11693
17.7k
    if (!geoidgrids.empty()) {
11694
7.16k
        auto vdatum = VerticalReferenceFrame::create(
11695
7.16k
            PropertyMap().set(common::IdentifiedObject::NAME_KEY,
11696
7.16k
                              "unknown using geoidgrids=" + geoidgrids));
11697
11698
7.16k
        const UnitOfMeasure unit = buildUnit(step, "vunits", "vto_meter");
11699
11700
7.16k
        auto vcrs =
11701
7.16k
            VerticalCRS::create(createMapWithUnknownName(), vdatum,
11702
7.16k
                                VerticalCS::createGravityRelatedHeight(unit));
11703
11704
7.16k
        CRSNNPtr geogCRS = GeographicCRS::EPSG_4979; // default
11705
7.16k
        const auto &geoid_crs = getParamValue(step, "geoid_crs");
11706
7.16k
        if (!geoid_crs.empty()) {
11707
2.07k
            if (geoid_crs == "WGS84") {
11708
                // nothing to do
11709
2.07k
            } else if (geoid_crs == "horizontal_crs") {
11710
2.00k
                auto geogCRSOfCompoundCRS = crs->extractGeographicCRS();
11711
2.00k
                if (geogCRSOfCompoundCRS &&
11712
1.99k
                    geogCRSOfCompoundCRS->primeMeridian()
11713
1.99k
                            ->longitude()
11714
1.99k
                            .getSIValue() == 0 &&
11715
1.94k
                    geogCRSOfCompoundCRS->coordinateSystem()
11716
1.94k
                            ->axisList()[0]
11717
1.94k
                            ->unit() == UnitOfMeasure::DEGREE) {
11718
1.94k
                    geogCRS = geogCRSOfCompoundCRS->promoteTo3D(std::string(),
11719
1.94k
                                                                nullptr);
11720
1.94k
                } else if (geogCRSOfCompoundCRS) {
11721
52
                    auto geogCRSOfCompoundCRSDatum =
11722
52
                        geogCRSOfCompoundCRS->datumNonNull(nullptr);
11723
52
                    geogCRS = GeographicCRS::create(
11724
52
                        createMapWithUnknownName(),
11725
52
                        datum::GeodeticReferenceFrame::create(
11726
52
                            util::PropertyMap().set(
11727
52
                                common::IdentifiedObject::NAME_KEY,
11728
52
                                geogCRSOfCompoundCRSDatum->nameStr() +
11729
52
                                    " (with Greenwich prime meridian)"),
11730
52
                            geogCRSOfCompoundCRSDatum->ellipsoid(),
11731
52
                            util::optional<std::string>(),
11732
52
                            datum::PrimeMeridian::GREENWICH),
11733
52
                        EllipsoidalCS::createLongitudeLatitudeEllipsoidalHeight(
11734
52
                            UnitOfMeasure::DEGREE, UnitOfMeasure::METRE));
11735
52
                }
11736
2.00k
            } else {
11737
69
                throw ParsingException("Unsupported value for geoid_crs: "
11738
69
                                       "should be 'WGS84' or 'horizontal_crs'");
11739
69
            }
11740
2.07k
        }
11741
7.09k
        auto transformation =
11742
7.09k
            Transformation::createGravityRelatedHeightToGeographic3D(
11743
7.09k
                PropertyMap().set(IdentifiedObject::NAME_KEY,
11744
7.09k
                                  "unknown to " + geogCRS->nameStr() +
11745
7.09k
                                      " ellipsoidal height"),
11746
7.09k
                VerticalCRS::create(createMapWithUnknownName(), vdatum,
11747
7.09k
                                    VerticalCS::createGravityRelatedHeight(
11748
7.09k
                                        common::UnitOfMeasure::METRE)),
11749
7.09k
                geogCRS, nullptr, geoidgrids,
11750
7.09k
                std::vector<PositionalAccuracyNNPtr>());
11751
7.09k
        auto boundvcrs = BoundCRS::create(vcrs, geogCRS, transformation);
11752
11753
7.09k
        crs = CompoundCRS::create(createMapWithUnknownName(),
11754
7.09k
                                  std::vector<CRSNNPtr>{crs, boundvcrs});
11755
7.09k
    }
11756
11757
17.6k
    return crs;
11758
17.7k
}
11759
11760
// ---------------------------------------------------------------------------
11761
11762
static double getAngularValue(const std::string &paramValue,
11763
3.16k
                              bool *pHasError = nullptr) {
11764
3.16k
    char *endptr = nullptr;
11765
3.16k
    double value = dmstor(paramValue.c_str(), &endptr) * RAD_TO_DEG;
11766
3.16k
    if (value == HUGE_VAL || endptr != paramValue.c_str() + paramValue.size()) {
11767
200
        if (pHasError)
11768
173
            *pHasError = true;
11769
200
        return 0.0;
11770
200
    }
11771
2.96k
    if (pHasError)
11772
2.82k
        *pHasError = false;
11773
2.96k
    return value;
11774
3.16k
}
11775
11776
// ---------------------------------------------------------------------------
11777
11778
32.3k
static bool is_in_stringlist(const std::string &str, const char *stringlist) {
11779
32.3k
    if (str.empty())
11780
4.87k
        return false;
11781
27.4k
    const char *haystack = stringlist;
11782
379k
    while (true) {
11783
379k
        const char *res = strstr(haystack, str.c_str());
11784
379k
        if (res == nullptr)
11785
24.2k
            return false;
11786
355k
        if ((res == stringlist || res[-1] == ',') &&
11787
122k
            (res[str.size()] == ',' || res[str.size()] == '\0'))
11788
3.19k
            return true;
11789
352k
        haystack += str.size();
11790
352k
    }
11791
27.4k
}
11792
11793
// ---------------------------------------------------------------------------
11794
11795
CRSNNPtr
11796
PROJStringParser::Private::buildProjectedCRS(int iStep,
11797
                                             const GeodeticCRSNNPtr &geodCRS,
11798
9.92k
                                             int iUnitConvert, int iAxisSwap) {
11799
9.92k
    auto &step = steps_[iStep];
11800
9.92k
    const auto mappings = getMappingsFromPROJName(step.name);
11801
9.92k
    const MethodMapping *mapping = mappings.empty() ? nullptr : mappings[0];
11802
11803
9.92k
    bool foundStrictlyMatchingMapping = false;
11804
9.92k
    if (mappings.size() >= 2) {
11805
        // To distinguish for example +ortho from +ortho +f=0
11806
2.15k
        bool allMappingsHaveAuxParam = true;
11807
5.42k
        for (const auto *mappingIter : mappings) {
11808
5.42k
            if (mappingIter->proj_name_aux == nullptr) {
11809
3.51k
                allMappingsHaveAuxParam = false;
11810
3.51k
            }
11811
5.42k
            if (mappingIter->proj_name_aux != nullptr &&
11812
1.91k
                strchr(mappingIter->proj_name_aux, '=') == nullptr &&
11813
547
                hasParamValue(step, mappingIter->proj_name_aux)) {
11814
39
                foundStrictlyMatchingMapping = true;
11815
39
                mapping = mappingIter;
11816
39
                break;
11817
5.38k
            } else if (mappingIter->proj_name_aux != nullptr &&
11818
1.87k
                       strchr(mappingIter->proj_name_aux, '=') != nullptr) {
11819
1.36k
                const auto tokens = split(mappingIter->proj_name_aux, '=');
11820
1.36k
                if (tokens.size() == 2 &&
11821
1.36k
                    getParamValue(step, tokens[0]) == tokens[1]) {
11822
60
                    foundStrictlyMatchingMapping = true;
11823
60
                    mapping = mappingIter;
11824
60
                    break;
11825
60
                }
11826
1.36k
            }
11827
5.42k
        }
11828
2.15k
        if (allMappingsHaveAuxParam && !foundStrictlyMatchingMapping) {
11829
46
            mapping = nullptr;
11830
46
        }
11831
2.15k
    }
11832
11833
9.92k
    if (mapping && !foundStrictlyMatchingMapping) {
11834
3.86k
        mapping = selectSphericalOrEllipsoidal(mapping, geodCRS);
11835
3.86k
    }
11836
11837
9.92k
    assert(isProjectedStep(step.name));
11838
9.92k
    assert(iUnitConvert < 0 ||
11839
9.92k
           ci_equal(steps_[iUnitConvert].name, "unitconvert"));
11840
11841
9.92k
    const auto &title = title_;
11842
11843
9.92k
    if (!buildPrimeMeridian(step)->longitude()._isEquivalentTo(
11844
9.92k
            geodCRS->primeMeridian()->longitude(),
11845
9.92k
            util::IComparable::Criterion::EQUIVALENT)) {
11846
3
        throw ParsingException("inconsistent pm values between projectedCRS "
11847
3
                               "and its base geographicalCRS");
11848
3
    }
11849
11850
9.92k
    auto axisType = AxisType::REGULAR;
11851
9.92k
    bool bWebMercator = false;
11852
9.92k
    std::string webMercatorName("WGS 84 / Pseudo-Mercator");
11853
11854
9.92k
    if (step.name == "tmerc" &&
11855
369
        ((getParamValue(step, "axis") == "wsu" && iAxisSwap < 0) ||
11856
351
         (iAxisSwap > 0 &&
11857
108
          getParamValue(steps_[iAxisSwap], "order") == "-1,-2"))) {
11858
29
        mapping =
11859
29
            getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED);
11860
9.89k
    } else if (step.name == "etmerc") {
11861
9
        mapping = getMapping(EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
11862
9.88k
    } else if (step.name == "lcc") {
11863
79
        const auto &lat_0 = getParamValue(step, "lat_0");
11864
79
        const auto &lat_1 = getParamValue(step, "lat_1");
11865
79
        const auto &lat_2 = getParamValue(step, "lat_2");
11866
79
        const auto &k = getParamValueK(step);
11867
79
        if (lat_2.empty() && !lat_0.empty() && !lat_1.empty()) {
11868
5
            if (lat_0 == lat_1 ||
11869
                // For some reason with gcc 5.3.1-14ubuntu2 32bit, the following
11870
                // comparison returns false even if lat_0 == lat_1. Smells like
11871
                // a compiler bug
11872
3
                getAngularValue(lat_0) == getAngularValue(lat_1)) {
11873
3
                mapping =
11874
3
                    getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
11875
3
            } else {
11876
2
                mapping = getMapping(
11877
2
                    EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP_VARIANT_B);
11878
2
            }
11879
74
        } else if (!k.empty() && getNumericValue(k) != 1.0) {
11880
5
            mapping = getMapping(
11881
5
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP_MICHIGAN);
11882
69
        } else {
11883
69
            mapping = getMapping(EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
11884
69
        }
11885
9.80k
    } else if (step.name == "aeqd" && hasParamValue(step, "guam")) {
11886
9
        mapping = getMapping(EPSG_CODE_METHOD_GUAM_PROJECTION);
11887
9.79k
    } else if (step.name == "geos" && getParamValue(step, "sweep") == "x") {
11888
10
        mapping =
11889
10
            getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X);
11890
9.78k
    } else if (step.name == "geos") {
11891
9
        mapping =
11892
9
            getMapping(PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_Y);
11893
9.78k
    } else if (step.name == "omerc") {
11894
78
        if (hasParamValue(step, "no_rot")) {
11895
0
            mapping = nullptr;
11896
78
        } else if (hasParamValue(step, "no_uoff") ||
11897
78
                   hasParamValue(step, "no_off")) {
11898
3
            mapping =
11899
3
                getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A);
11900
75
        } else if (hasParamValue(step, "lat_1") &&
11901
69
                   hasParamValue(step, "lon_1") &&
11902
25
                   hasParamValue(step, "lat_2") &&
11903
9
                   hasParamValue(step, "lon_2")) {
11904
0
            mapping = getMapping(
11905
0
                PROJ_WKT2_NAME_METHOD_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN);
11906
75
        } else {
11907
75
            mapping =
11908
75
                getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
11909
75
        }
11910
9.70k
    } else if (step.name == "somerc") {
11911
39
        mapping =
11912
39
            getMapping(EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
11913
39
        if (!hasParamValue(step, "alpha") && !hasParamValue(step, "gamma") &&
11914
22
            !hasParamValue(step, "lonc")) {
11915
22
            step.paramValues.emplace_back(Step::KeyValue("alpha", "90"));
11916
22
            step.paramValues.emplace_back(Step::KeyValue("gamma", "90"));
11917
22
            step.paramValues.emplace_back(
11918
22
                Step::KeyValue("lonc", getParamValue(step, "lon_0")));
11919
22
        }
11920
9.66k
    } else if (step.name == "krovak" &&
11921
742
               ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
11922
4
                 !hasParamValue(step, "czech")) ||
11923
738
                (iAxisSwap > 0 &&
11924
0
                 getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
11925
4
                 !hasParamValue(step, "czech")))) {
11926
4
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
11927
9.65k
    } else if (step.name == "krovak" && iAxisSwap < 0 &&
11928
738
               hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
11929
4
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK);
11930
9.65k
    } else if (step.name == "mod_krovak" &&
11931
63
               ((iAxisSwap < 0 && getParamValue(step, "axis") == "swu" &&
11932
4
                 !hasParamValue(step, "czech")) ||
11933
59
                (iAxisSwap > 0 &&
11934
0
                 getParamValue(steps_[iAxisSwap], "order") == "-2,-1" &&
11935
4
                 !hasParamValue(step, "czech")))) {
11936
4
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED);
11937
9.65k
    } else if (step.name == "mod_krovak" && iAxisSwap < 0 &&
11938
59
               hasParamValue(step, "czech") && !hasParamValue(step, "axis")) {
11939
32
        mapping = getMapping(EPSG_CODE_METHOD_KROVAK_MODIFIED);
11940
9.61k
    } else if (step.name == "merc") {
11941
149
        if (hasParamValue(step, "a") && hasParamValue(step, "b") &&
11942
10
            getParamValue(step, "a") == getParamValue(step, "b") &&
11943
4
            (!hasParamValue(step, "lat_ts") ||
11944
0
             getAngularValue(getParamValue(step, "lat_ts")) == 0.0) &&
11945
4
            getNumericValue(getParamValueK(step)) == 1.0 &&
11946
0
            getParamValue(step, "nadgrids") == "@null") {
11947
0
            mapping = getMapping(
11948
0
                EPSG_CODE_METHOD_POPULAR_VISUALISATION_PSEUDO_MERCATOR);
11949
0
            for (size_t i = 0; i < step.paramValues.size(); ++i) {
11950
0
                if (ci_equal(step.paramValues[i].key, "nadgrids")) {
11951
0
                    ignoreNadgrids_ = true;
11952
0
                    break;
11953
0
                }
11954
0
            }
11955
0
            if (getNumericValue(getParamValue(step, "a")) == 6378137 &&
11956
0
                getAngularValue(getParamValue(step, "lon_0")) == 0.0 &&
11957
0
                getAngularValue(getParamValue(step, "lat_0")) == 0.0 &&
11958
0
                getAngularValue(getParamValue(step, "x_0")) == 0.0 &&
11959
0
                getAngularValue(getParamValue(step, "y_0")) == 0.0) {
11960
0
                bWebMercator = true;
11961
0
                if (hasParamValue(step, "units") &&
11962
0
                    getParamValue(step, "units") != "m") {
11963
0
                    webMercatorName +=
11964
0
                        " (unit " + getParamValue(step, "units") + ')';
11965
0
                }
11966
0
            }
11967
149
        } else if (hasParamValue(step, "lat_ts")) {
11968
30
            if (hasParamValue(step, "R_C") &&
11969
8
                !geodCRS->ellipsoid()->isSphere() &&
11970
8
                getAngularValue(getParamValue(step, "lat_ts")) != 0) {
11971
0
                throw ParsingException("lat_ts != 0 not supported for "
11972
0
                                       "spherical Mercator on an ellipsoid");
11973
0
            }
11974
30
            mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
11975
119
        } else if (hasParamValue(step, "R_C")) {
11976
17
            const auto &k = getParamValueK(step);
11977
17
            if (!k.empty() && getNumericValue(k) != 1.0) {
11978
5
                if (geodCRS->ellipsoid()->isSphere()) {
11979
0
                    mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
11980
5
                } else {
11981
5
                    throw ParsingException(
11982
5
                        "k_0 != 1 not supported for spherical Mercator on an "
11983
5
                        "ellipsoid");
11984
5
                }
11985
12
            } else {
11986
12
                mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_SPHERICAL);
11987
12
            }
11988
102
        } else {
11989
102
            mapping = getMapping(EPSG_CODE_METHOD_MERCATOR_VARIANT_A);
11990
102
        }
11991
9.47k
    } else if (step.name == "stere") {
11992
192
        if (hasParamValue(step, "lat_0") &&
11993
68
            std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
11994
68
                      90.0) < 1e-10) {
11995
56
            const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
11996
56
            if (lat_0 > 0) {
11997
50
                axisType = AxisType::NORTH_POLE;
11998
50
            } else {
11999
6
                axisType = AxisType::SOUTH_POLE;
12000
6
            }
12001
56
            const auto &lat_ts = getParamValue(step, "lat_ts");
12002
56
            const auto &k = getParamValueK(step);
12003
56
            if (!lat_ts.empty() &&
12004
23
                std::fabs(getAngularValue(lat_ts) - lat_0) > 1e-10 &&
12005
17
                !k.empty() && std::fabs(getNumericValue(k) - 1) > 1e-10) {
12006
7
                throw ParsingException("lat_ts != lat_0 and k != 1 not "
12007
7
                                       "supported for Polar Stereographic");
12008
7
            }
12009
49
            if (!lat_ts.empty() &&
12010
16
                (k.empty() || std::fabs(getNumericValue(k) - 1) < 1e-10)) {
12011
12
                mapping =
12012
12
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_B);
12013
37
            } else {
12014
37
                mapping =
12015
37
                    getMapping(EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
12016
37
            }
12017
136
        } else {
12018
136
            mapping = getMapping(PROJ_WKT2_NAME_METHOD_STEREOGRAPHIC);
12019
136
        }
12020
9.27k
    } else if (step.name == "laea") {
12021
159
        if (hasParamValue(step, "lat_0") &&
12022
6
            std::fabs(std::fabs(getAngularValue(getParamValue(step, "lat_0"))) -
12023
6
                      90.0) < 1e-10) {
12024
2
            const double lat_0 = getAngularValue(getParamValue(step, "lat_0"));
12025
2
            if (lat_0 > 0) {
12026
2
                axisType = AxisType::NORTH_POLE;
12027
2
            } else {
12028
0
                axisType = AxisType::SOUTH_POLE;
12029
0
            }
12030
2
        }
12031
9.11k
    } else if (step.name == "ortho") {
12032
33
        const std::string &k = getParamValueK(step);
12033
33
        if ((!k.empty() && getNumericValue(k) != 1.0) ||
12034
33
            (hasParamValue(step, "alpha") &&
12035
8
             getNumericValue(getParamValue(step, "alpha")) != 0.0)) {
12036
6
            mapping = getMapping(EPSG_CODE_METHOD_LOCAL_ORTHOGRAPHIC);
12037
6
        }
12038
33
    }
12039
12040
9.91k
    UnitOfMeasure unit = buildUnit(step, "units", "to_meter");
12041
9.91k
    if (iUnitConvert >= 0) {
12042
36
        auto &stepUnitConvert = steps_[iUnitConvert];
12043
36
        const std::string *xy_in = &getParamValue(stepUnitConvert, "xy_in");
12044
36
        const std::string *xy_out = &getParamValue(stepUnitConvert, "xy_out");
12045
36
        if (stepUnitConvert.inverted) {
12046
0
            std::swap(xy_in, xy_out);
12047
0
        }
12048
36
        if (xy_in->empty() || xy_out->empty() || *xy_in != "m") {
12049
36
            if (step.name != "ob_tran") {
12050
25
                throw ParsingException(
12051
25
                    "unhandled values for xy_in and/or xy_out");
12052
25
            }
12053
36
        }
12054
12055
11
        const LinearUnitDesc *unitsMatch = nullptr;
12056
11
        try {
12057
11
            double to_meter_value = c_locale_stod(*xy_out);
12058
11
            unitsMatch = getLinearUnits(to_meter_value);
12059
11
            if (unitsMatch == nullptr) {
12060
0
                unit = _buildUnit(to_meter_value);
12061
0
            }
12062
11
        } catch (const std::invalid_argument &) {
12063
11
            unitsMatch = getLinearUnits(*xy_out);
12064
11
            if (!unitsMatch) {
12065
11
                if (step.name != "ob_tran") {
12066
0
                    throw ParsingException(
12067
0
                        "unhandled values for xy_in and/or xy_out");
12068
0
                }
12069
11
            } else {
12070
0
                unit = _buildUnit(unitsMatch);
12071
0
            }
12072
11
        }
12073
11
    }
12074
12075
9.88k
    ConversionPtr conv;
12076
12077
9.88k
    auto mapWithUnknownName = createMapWithUnknownName();
12078
12079
9.88k
    if (step.name == "utm") {
12080
58
        const int zone = std::atoi(getParamValue(step, "zone").c_str());
12081
58
        const bool north = !hasParamValue(step, "south");
12082
58
        conv =
12083
58
            Conversion::createUTM(emptyPropertyMap, zone, north).as_nullable();
12084
9.83k
    } else if (mapping) {
12085
12086
3.98k
        auto methodMap =
12087
3.98k
            PropertyMap().set(IdentifiedObject::NAME_KEY, mapping->wkt2_name);
12088
3.98k
        if (mapping->epsg_code) {
12089
2.00k
            methodMap.set(Identifier::CODESPACE_KEY, Identifier::EPSG)
12090
2.00k
                .set(Identifier::CODE_KEY, mapping->epsg_code);
12091
2.00k
        }
12092
3.98k
        std::vector<OperationParameterNNPtr> parameters;
12093
3.98k
        std::vector<ParameterValueNNPtr> values;
12094
22.4k
        for (int i = 0; mapping->params[i] != nullptr; i++) {
12095
18.6k
            const auto *param = mapping->params[i];
12096
18.6k
            std::string proj_name(param->proj_name ? param->proj_name : "");
12097
18.6k
            const std::string *paramValue =
12098
18.6k
                (proj_name == "k" || proj_name == "k_0") ? &getParamValueK(step)
12099
18.6k
                : !proj_name.empty() ? &getParamValue(step, proj_name)
12100
16.9k
                                     : &emptyString;
12101
18.6k
            double value = 0;
12102
18.6k
            if (!paramValue->empty()) {
12103
412
                bool hasError = false;
12104
412
                if (param->unit_type == UnitOfMeasure::Type::ANGULAR) {
12105
334
                    value = getAngularValue(*paramValue, &hasError);
12106
334
                } else {
12107
78
                    value = getNumericValue(*paramValue, &hasError);
12108
78
                }
12109
412
                if (hasError) {
12110
104
                    throw ParsingException("invalid value for " + proj_name);
12111
104
                }
12112
412
            }
12113
            // For omerc, if gamma is missing, the default value is
12114
            // alpha
12115
18.1k
            else if (step.name == "omerc" && proj_name == "gamma") {
12116
76
                paramValue = &getParamValue(step, "alpha");
12117
76
                if (!paramValue->empty()) {
12118
0
                    value = getAngularValue(*paramValue);
12119
0
                }
12120
18.1k
            } else if (step.name == "krovak" || step.name == "mod_krovak") {
12121
                // Keep it in sync with defaults of krovak.cpp
12122
5.54k
                if (param->epsg_code ==
12123
5.54k
                    EPSG_CODE_PARAMETER_LATITUDE_PROJECTION_CENTRE) {
12124
794
                    value = 49.5;
12125
4.75k
                } else if (param->epsg_code ==
12126
4.75k
                           EPSG_CODE_PARAMETER_LONGITUDE_OF_ORIGIN) {
12127
793
                    value = 24.833333333333333333;
12128
3.95k
                } else if (param->epsg_code ==
12129
3.95k
                           EPSG_CODE_PARAMETER_COLATITUDE_CONE_AXIS) {
12130
794
                    value = 30.28813975277777776;
12131
3.16k
                } else if (
12132
3.16k
                    param->epsg_code ==
12133
3.16k
                    EPSG_CODE_PARAMETER_LATITUDE_PSEUDO_STANDARD_PARALLEL) {
12134
794
                    value = 78.5;
12135
2.36k
                } else if (
12136
2.36k
                    param->epsg_code ==
12137
2.36k
                    EPSG_CODE_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL) {
12138
791
                    value = 0.9999;
12139
791
                }
12140
12.5k
            } else if (step.name == "cea" && proj_name == "lat_ts") {
12141
85
                paramValue = &getParamValueK(step);
12142
85
                if (!paramValue->empty()) {
12143
11
                    bool hasError = false;
12144
11
                    const double k = getNumericValue(*paramValue, &hasError);
12145
11
                    if (hasError) {
12146
5
                        throw ParsingException("invalid value for k/k_0");
12147
5
                    }
12148
6
                    if (k >= 0 && k <= 1) {
12149
3
                        const double es =
12150
3
                            geodCRS->ellipsoid()->squaredEccentricity();
12151
3
                        if (es < 0 || es == 1) {
12152
0
                            throw ParsingException("Invalid flattening");
12153
0
                        }
12154
3
                        value =
12155
3
                            Angle(acos(k * sqrt((1 - es) / (1 - k * k * es))),
12156
3
                                  UnitOfMeasure::RADIAN)
12157
3
                                .convertToUnit(UnitOfMeasure::DEGREE);
12158
3
                    } else {
12159
3
                        throw ParsingException("k/k_0 should be in [0,1]");
12160
3
                    }
12161
6
                }
12162
12.4k
            } else if (param->unit_type == UnitOfMeasure::Type::SCALE) {
12163
853
                value = 1;
12164
11.6k
            } else if (step.name == "peirce_q" && proj_name == "lat_0") {
12165
24
                value = 90;
12166
24
            }
12167
12168
18.4k
            PropertyMap propertiesParameter;
12169
18.4k
            propertiesParameter.set(IdentifiedObject::NAME_KEY,
12170
18.4k
                                    param->wkt2_name);
12171
18.4k
            if (param->epsg_code) {
12172
17.9k
                propertiesParameter.set(Identifier::CODE_KEY, param->epsg_code);
12173
17.9k
                propertiesParameter.set(Identifier::CODESPACE_KEY,
12174
17.9k
                                        Identifier::EPSG);
12175
17.9k
            }
12176
18.4k
            parameters.push_back(
12177
18.4k
                OperationParameter::create(propertiesParameter));
12178
            // In PROJ convention, angular parameters are always in degree
12179
            // and linear parameters always in metre.
12180
18.4k
            double valRounded =
12181
18.4k
                param->unit_type == UnitOfMeasure::Type::LINEAR
12182
18.4k
                    ? Length(value, UnitOfMeasure::METRE).convertToUnit(unit)
12183
18.4k
                    : value;
12184
18.4k
            if (std::fabs(valRounded - std::round(valRounded)) < 1e-8) {
12185
14.5k
                valRounded = std::round(valRounded);
12186
14.5k
            }
12187
18.4k
            values.push_back(ParameterValue::create(
12188
18.4k
                Measure(valRounded,
12189
18.4k
                        param->unit_type == UnitOfMeasure::Type::ANGULAR
12190
18.4k
                            ? UnitOfMeasure::DEGREE
12191
18.4k
                        : param->unit_type == UnitOfMeasure::Type::LINEAR ? unit
12192
9.43k
                        : param->unit_type == UnitOfMeasure::Type::SCALE
12193
1.65k
                            ? UnitOfMeasure::SCALE_UNITY
12194
1.65k
                            : UnitOfMeasure::NONE)));
12195
18.4k
        }
12196
12197
3.87k
        if (step.name == "tmerc" && hasParamValue(step, "approx")) {
12198
0
            methodMap.set("proj_method", "tmerc approx");
12199
3.87k
        } else if (step.name == "utm" && hasParamValue(step, "approx")) {
12200
0
            methodMap.set("proj_method", "utm approx");
12201
0
        }
12202
12203
3.87k
        conv = Conversion::create(mapWithUnknownName, methodMap, parameters,
12204
3.87k
                                  values)
12205
3.87k
                   .as_nullable();
12206
5.84k
    } else {
12207
5.84k
        std::vector<OperationParameterNNPtr> parameters;
12208
5.84k
        std::vector<ParameterValueNNPtr> values;
12209
5.84k
        std::string methodName = "PROJ " + step.name;
12210
26.5k
        for (const auto &param : step.paramValues) {
12211
26.5k
            if (is_in_stringlist(param.key,
12212
26.5k
                                 "wktext,no_defs,datum,ellps,a,b,R,f,rf,"
12213
26.5k
                                 "towgs84,nadgrids,geoidgrids,"
12214
26.5k
                                 "units,to_meter,vunits,vto_meter,type")) {
12215
2.94k
                continue;
12216
2.94k
            }
12217
23.6k
            if (param.value.empty()) {
12218
14.0k
                methodName += " " + param.key;
12219
14.0k
            } else if (isalpha(param.value[0])) {
12220
6.69k
                methodName += " " + param.key + "=" + param.value;
12221
6.69k
            } else {
12222
2.90k
                parameters.push_back(OperationParameter::create(
12223
2.90k
                    PropertyMap().set(IdentifiedObject::NAME_KEY, param.key)));
12224
2.90k
                bool hasError = false;
12225
2.90k
                if (is_in_stringlist(param.key, "x_0,y_0,h,h_0")) {
12226
66
                    double value = getNumericValue(param.value, &hasError);
12227
66
                    values.push_back(ParameterValue::create(
12228
66
                        Measure(value, UnitOfMeasure::METRE)));
12229
2.84k
                } else if (is_in_stringlist(
12230
2.84k
                               param.key,
12231
2.84k
                               "k,k_0,"
12232
2.84k
                               "north_square,south_square," // rhealpix
12233
2.84k
                               "n,m,"                       // sinu
12234
2.84k
                               "q,"                         // urm5
12235
2.84k
                               "path,lsat,"                 // lsat
12236
2.84k
                               "W,M,"                       // hammer
12237
2.84k
                               "aperture,resolution,"       // isea
12238
2.84k
                               )) {
12239
183
                    double value = getNumericValue(param.value, &hasError);
12240
183
                    values.push_back(ParameterValue::create(
12241
183
                        Measure(value, UnitOfMeasure::SCALE_UNITY)));
12242
2.66k
                } else {
12243
2.66k
                    double value = getAngularValue(param.value, &hasError);
12244
2.66k
                    values.push_back(ParameterValue::create(
12245
2.66k
                        Measure(value, UnitOfMeasure::DEGREE)));
12246
2.66k
                }
12247
2.90k
                if (hasError) {
12248
131
                    throw ParsingException("invalid value for " + param.key);
12249
131
                }
12250
2.90k
            }
12251
23.6k
        }
12252
5.71k
        conv = Conversion::create(
12253
5.71k
                   mapWithUnknownName,
12254
5.71k
                   PropertyMap().set(IdentifiedObject::NAME_KEY, methodName),
12255
5.71k
                   parameters, values)
12256
5.71k
                   .as_nullable();
12257
12258
5.71k
        for (const char *substr :
12259
5.71k
             {"PROJ ob_tran o_proj=longlat", "PROJ ob_tran o_proj=lonlat",
12260
20.3k
              "PROJ ob_tran o_proj=latlon", "PROJ ob_tran o_proj=latlong"}) {
12261
20.3k
            if (starts_with(methodName, substr)) {
12262
1.22k
                auto geogCRS =
12263
1.22k
                    util::nn_dynamic_pointer_cast<GeographicCRS>(geodCRS);
12264
1.22k
                if (geogCRS) {
12265
1.22k
                    return DerivedGeographicCRS::create(
12266
1.22k
                        PropertyMap().set(IdentifiedObject::NAME_KEY,
12267
1.22k
                                          "unnamed"),
12268
1.22k
                        NN_NO_CHECK(geogCRS), NN_NO_CHECK(conv),
12269
1.22k
                        buildEllipsoidalCS(iStep, iUnitConvert, iAxisSwap,
12270
1.22k
                                           false));
12271
1.22k
                }
12272
1.22k
            }
12273
20.3k
        }
12274
5.71k
    }
12275
12276
8.42k
    std::vector<CoordinateSystemAxisNNPtr> axis =
12277
8.42k
        processAxisSwap(step, unit, iAxisSwap, axisType, false);
12278
12279
8.42k
    auto csGeodCRS = geodCRS->coordinateSystem();
12280
8.42k
    auto cs = csGeodCRS->axisList().size() == 2
12281
8.42k
                  ? CartesianCS::create(emptyPropertyMap, axis[0], axis[1])
12282
8.42k
                  : CartesianCS::create(emptyPropertyMap, axis[0], axis[1],
12283
1.39k
                                        csGeodCRS->axisList()[2]);
12284
8.42k
    if (isTopocentricStep(step.name)) {
12285
8
        cs = CartesianCS::create(
12286
8
            emptyPropertyMap,
12287
8
            createAxis("topocentric East", "U", AxisDirection::EAST, unit),
12288
8
            createAxis("topocentric North", "V", AxisDirection::NORTH, unit),
12289
8
            createAxis("topocentric Up", "W", AxisDirection::UP, unit));
12290
8
    }
12291
12292
8.42k
    auto props = PropertyMap().set(IdentifiedObject::NAME_KEY,
12293
8.42k
                                   title.empty() ? "unknown" : title);
12294
8.42k
    if (hasUnusedParameters(step)) {
12295
1.30k
        props.set("EXTENSION_PROJ4", projString_);
12296
1.30k
    }
12297
12298
8.42k
    props.set("IMPLICIT_CS", true);
12299
12300
8.42k
    CRSNNPtr crs =
12301
8.42k
        bWebMercator
12302
8.42k
            ? createPseudoMercator(
12303
0
                  props.set(IdentifiedObject::NAME_KEY, webMercatorName), cs)
12304
8.42k
            : ProjectedCRS::create(props, geodCRS, NN_NO_CHECK(conv), cs);
12305
12306
8.42k
    return crs;
12307
9.88k
}
12308
12309
//! @endcond
12310
12311
// ---------------------------------------------------------------------------
12312
12313
//! @cond Doxygen_Suppress
12314
static const metadata::ExtentPtr nullExtent{};
12315
12316
0
static const metadata::ExtentPtr &getExtent(const crs::CRS *crs) {
12317
0
    const auto &domains = crs->domains();
12318
0
    if (!domains.empty()) {
12319
0
        return domains[0]->domainOfValidity();
12320
0
    }
12321
0
    return nullExtent;
12322
0
}
12323
12324
//! @endcond
12325
12326
namespace {
12327
struct PJContextHolder {
12328
    PJ_CONTEXT *ctx_;
12329
    bool bFree_;
12330
12331
427
    PJContextHolder(PJ_CONTEXT *ctx, bool bFree) : ctx_(ctx), bFree_(bFree) {}
12332
427
    ~PJContextHolder() {
12333
427
        if (bFree_)
12334
2
            proj_context_destroy(ctx_);
12335
427
    }
12336
    PJContextHolder(const PJContextHolder &) = delete;
12337
    PJContextHolder &operator=(const PJContextHolder &) = delete;
12338
};
12339
} // namespace
12340
12341
// ---------------------------------------------------------------------------
12342
12343
/** \brief Instantiate a sub-class of BaseObject from a PROJ string.
12344
 *
12345
 * The projString must contain +type=crs for the object to be detected as a
12346
 * CRS instead of a CoordinateOperation.
12347
 *
12348
 * @throw ParsingException if the string cannot be parsed.
12349
 */
12350
BaseObjectNNPtr
12351
13.5k
PROJStringParser::createFromPROJString(const std::string &projString) {
12352
12353
    // In some abnormal situations involving init=epsg:XXXX syntax, we could
12354
    // have infinite loop
12355
13.5k
    if (d->ctx_ &&
12356
13.4k
        d->ctx_->projStringParserCreateFromPROJStringRecursionCounter == 2) {
12357
0
        throw ParsingException(
12358
0
            "Infinite recursion in PROJStringParser::createFromPROJString()");
12359
0
    }
12360
12361
13.5k
    d->steps_.clear();
12362
13.5k
    d->title_.clear();
12363
13.5k
    d->globalParamValues_.clear();
12364
13.5k
    d->projString_ = projString;
12365
13.5k
    PROJStringSyntaxParser(projString, d->steps_, d->globalParamValues_,
12366
13.5k
                           d->title_);
12367
12368
13.5k
    if (d->steps_.empty()) {
12369
259
        const auto &vunits = d->getGlobalParamValue("vunits");
12370
259
        const auto &vto_meter = d->getGlobalParamValue("vto_meter");
12371
259
        if (!vunits.empty() || !vto_meter.empty()) {
12372
48
            Step fakeStep;
12373
48
            if (!vunits.empty()) {
12374
46
                fakeStep.paramValues.emplace_back(
12375
46
                    Step::KeyValue("vunits", vunits));
12376
46
            }
12377
48
            if (!vto_meter.empty()) {
12378
2
                fakeStep.paramValues.emplace_back(
12379
2
                    Step::KeyValue("vto_meter", vto_meter));
12380
2
            }
12381
48
            auto vdatum =
12382
48
                VerticalReferenceFrame::create(createMapWithUnknownName());
12383
48
            auto vcrs = VerticalCRS::create(
12384
48
                createMapWithUnknownName(), vdatum,
12385
48
                VerticalCS::createGravityRelatedHeight(
12386
48
                    d->buildUnit(fakeStep, "vunits", "vto_meter")));
12387
48
            return vcrs;
12388
48
        }
12389
259
    }
12390
12391
13.5k
    const bool isGeocentricCRS =
12392
13.5k
        ((d->steps_.size() == 1 &&
12393
11.5k
          d->getParamValue(d->steps_[0], "type") == "crs") ||
12394
4.12k
         (d->steps_.size() == 2 && d->steps_[1].name == "unitconvert")) &&
12395
9.45k
        !d->steps_[0].inverted && isGeocentricStep(d->steps_[0].name);
12396
12397
13.5k
    const bool isTopocentricCRS =
12398
13.5k
        (d->steps_.size() == 1 && isTopocentricStep(d->steps_[0].name) &&
12399
36
         d->getParamValue(d->steps_[0], "type") == "crs");
12400
12401
    // +init=xxxx:yyyy syntax
12402
13.5k
    if (d->steps_.size() == 1 && d->steps_[0].isInit &&
12403
433
        !d->steps_[0].inverted) {
12404
12405
427
        auto ctx = d->ctx_ ? d->ctx_ : proj_context_create();
12406
427
        if (!ctx) {
12407
0
            throw ParsingException("out of memory");
12408
0
        }
12409
427
        PJContextHolder contextHolder(ctx, ctx != d->ctx_);
12410
12411
        // Those used to come from a text init file
12412
        // We only support them in compatibility mode
12413
427
        const std::string &stepName = d->steps_[0].name;
12414
427
        if (ci_starts_with(stepName, "epsg:") ||
12415
424
            ci_starts_with(stepName, "IGNF:")) {
12416
12417
6
            struct BackupContextErrno {
12418
6
                PJ_CONTEXT *m_ctxt = nullptr;
12419
6
                int m_last_errno = 0;
12420
12421
6
                explicit BackupContextErrno(PJ_CONTEXT *ctxtIn)
12422
6
                    : m_ctxt(ctxtIn), m_last_errno(m_ctxt->last_errno) {
12423
6
                    m_ctxt->debug_level = PJ_LOG_ERROR;
12424
6
                }
12425
12426
6
                ~BackupContextErrno() { m_ctxt->last_errno = m_last_errno; }
12427
12428
6
                BackupContextErrno(const BackupContextErrno &) = delete;
12429
6
                BackupContextErrno &
12430
6
                operator=(const BackupContextErrno &) = delete;
12431
6
            };
12432
12433
6
            BackupContextErrno backupContextErrno(ctx);
12434
12435
6
            bool usePROJ4InitRules = d->usePROJ4InitRules_;
12436
6
            if (!usePROJ4InitRules) {
12437
6
                usePROJ4InitRules =
12438
6
                    proj_context_get_use_proj4_init_rules(ctx, FALSE) == TRUE;
12439
6
            }
12440
6
            if (!usePROJ4InitRules) {
12441
6
                throw ParsingException("init=epsg:/init=IGNF: syntax not "
12442
6
                                       "supported in non-PROJ4 emulation mode");
12443
6
            }
12444
12445
0
            char unused[256];
12446
0
            std::string initname(stepName);
12447
0
            initname.resize(initname.find(':'));
12448
0
            int file_found =
12449
0
                pj_find_file(ctx, initname.c_str(), unused, sizeof(unused));
12450
12451
0
            if (!file_found) {
12452
0
                auto obj = createFromUserInput(stepName, d->dbContext_, true);
12453
0
                auto crs = dynamic_cast<CRS *>(obj.get());
12454
12455
0
                bool hasSignificantParamValues = false;
12456
0
                bool hasOver = false;
12457
0
                for (const auto &kv : d->steps_[0].paramValues) {
12458
0
                    if (kv.key == "over") {
12459
0
                        hasOver = true;
12460
0
                    } else if (!((kv.key == "type" && kv.value == "crs") ||
12461
0
                                 kv.key == "wktext" || kv.key == "no_defs")) {
12462
0
                        hasSignificantParamValues = true;
12463
0
                        break;
12464
0
                    }
12465
0
                }
12466
12467
0
                if (crs && !hasSignificantParamValues) {
12468
0
                    PropertyMap properties;
12469
0
                    properties.set(IdentifiedObject::NAME_KEY,
12470
0
                                   d->title_.empty() ? crs->nameStr()
12471
0
                                                     : d->title_);
12472
0
                    if (hasOver) {
12473
0
                        properties.set("OVER", true);
12474
0
                    }
12475
0
                    const auto &extent = getExtent(crs);
12476
0
                    if (extent) {
12477
0
                        properties.set(
12478
0
                            common::ObjectUsage::DOMAIN_OF_VALIDITY_KEY,
12479
0
                            NN_NO_CHECK(extent));
12480
0
                    }
12481
0
                    auto geogCRS = dynamic_cast<GeographicCRS *>(crs);
12482
0
                    if (geogCRS) {
12483
0
                        const auto &cs = geogCRS->coordinateSystem();
12484
                        // Override with longitude latitude in degrees
12485
0
                        return GeographicCRS::create(
12486
0
                            properties, geogCRS->datum(),
12487
0
                            geogCRS->datumEnsemble(),
12488
0
                            cs->axisList().size() == 2
12489
0
                                ? EllipsoidalCS::createLongitudeLatitude(
12490
0
                                      UnitOfMeasure::DEGREE)
12491
0
                                : EllipsoidalCS::
12492
0
                                      createLongitudeLatitudeEllipsoidalHeight(
12493
0
                                          UnitOfMeasure::DEGREE,
12494
0
                                          cs->axisList()[2]->unit()));
12495
0
                    }
12496
0
                    auto projCRS = dynamic_cast<ProjectedCRS *>(crs);
12497
0
                    if (projCRS) {
12498
                        // Override with easting northing order
12499
0
                        const auto conv = projCRS->derivingConversion();
12500
0
                        if (conv->method()->getEPSGCode() !=
12501
0
                            EPSG_CODE_METHOD_TRANSVERSE_MERCATOR_SOUTH_ORIENTATED) {
12502
0
                            return ProjectedCRS::create(
12503
0
                                properties, projCRS->baseCRS(), conv,
12504
0
                                CartesianCS::createEastingNorthing(
12505
0
                                    projCRS->coordinateSystem()
12506
0
                                        ->axisList()[0]
12507
0
                                        ->unit()));
12508
0
                        }
12509
0
                    }
12510
0
                    return obj;
12511
0
                }
12512
0
                auto projStringExportable =
12513
0
                    dynamic_cast<IPROJStringExportable *>(crs);
12514
0
                if (projStringExportable) {
12515
0
                    std::string expanded;
12516
0
                    if (!d->title_.empty()) {
12517
0
                        expanded = "title=";
12518
0
                        expanded +=
12519
0
                            pj_double_quote_string_param_if_needed(d->title_);
12520
0
                    }
12521
0
                    for (const auto &pair : d->steps_[0].paramValues) {
12522
0
                        if (!expanded.empty())
12523
0
                            expanded += ' ';
12524
0
                        expanded += '+';
12525
0
                        expanded += pair.key;
12526
0
                        if (!pair.value.empty()) {
12527
0
                            expanded += '=';
12528
0
                            expanded += pj_double_quote_string_param_if_needed(
12529
0
                                pair.value);
12530
0
                        }
12531
0
                    }
12532
0
                    expanded += ' ';
12533
0
                    expanded += projStringExportable->exportToPROJString(
12534
0
                        PROJStringFormatter::create().get());
12535
0
                    return createFromPROJString(expanded);
12536
0
                }
12537
0
            }
12538
0
        }
12539
12540
421
        paralist *init = pj_mkparam(("init=" + d->steps_[0].name).c_str());
12541
421
        if (!init) {
12542
0
            throw ParsingException("out of memory");
12543
0
        }
12544
421
        ctx->projStringParserCreateFromPROJStringRecursionCounter++;
12545
421
        paralist *list = pj_expand_init(ctx, init);
12546
421
        ctx->projStringParserCreateFromPROJStringRecursionCounter--;
12547
421
        if (!list) {
12548
109
            free(init);
12549
109
            throw ParsingException("cannot expand " + projString);
12550
109
        }
12551
312
        std::string expanded;
12552
312
        if (!d->title_.empty()) {
12553
93
            expanded =
12554
93
                "title=" + pj_double_quote_string_param_if_needed(d->title_);
12555
93
        }
12556
312
        bool first = true;
12557
312
        bool has_init_term = false;
12558
2.26k
        for (auto t = list; t;) {
12559
1.95k
            if (!expanded.empty()) {
12560
1.51k
                expanded += ' ';
12561
1.51k
            }
12562
1.95k
            if (first) {
12563
                // first parameter is the init= itself
12564
312
                first = false;
12565
1.64k
            } else if (starts_with(t->param, "init=")) {
12566
0
                has_init_term = true;
12567
1.64k
            } else {
12568
1.64k
                expanded += t->param;
12569
1.64k
            }
12570
12571
1.95k
            auto n = t->next;
12572
1.95k
            free(t);
12573
1.95k
            t = n;
12574
1.95k
        }
12575
3.66k
        for (const auto &pair : d->steps_[0].paramValues) {
12576
3.66k
            expanded += " +";
12577
3.66k
            expanded += pair.key;
12578
3.66k
            if (!pair.value.empty()) {
12579
2.34k
                expanded += '=';
12580
2.34k
                expanded += pj_double_quote_string_param_if_needed(pair.value);
12581
2.34k
            }
12582
3.66k
        }
12583
12584
312
        if (!has_init_term) {
12585
312
            return createFromPROJString(expanded);
12586
312
        }
12587
312
    }
12588
12589
13.0k
    int iFirstGeogStep = -1;
12590
13.0k
    int iSecondGeogStep = -1;
12591
13.0k
    int iProjStep = -1;
12592
13.0k
    int iFirstUnitConvert = -1;
12593
13.0k
    int iSecondUnitConvert = -1;
12594
13.0k
    int iFirstAxisSwap = -1;
12595
13.0k
    int iSecondAxisSwap = -1;
12596
13.0k
    bool unexpectedStructure = d->steps_.empty();
12597
26.6k
    for (int i = 0; i < static_cast<int>(d->steps_.size()); i++) {
12598
14.6k
        const auto &stepName = d->steps_[i].name;
12599
14.6k
        if (isGeographicStep(stepName)) {
12600
4.42k
            if (iFirstGeogStep < 0) {
12601
4.39k
                iFirstGeogStep = i;
12602
4.39k
            } else if (iSecondGeogStep < 0) {
12603
25
                iSecondGeogStep = i;
12604
25
            } else {
12605
6
                unexpectedStructure = true;
12606
6
                break;
12607
6
            }
12608
10.1k
        } else if (ci_equal(stepName, "unitconvert")) {
12609
254
            if (iFirstUnitConvert < 0) {
12610
210
                iFirstUnitConvert = i;
12611
210
            } else if (iSecondUnitConvert < 0) {
12612
41
                iSecondUnitConvert = i;
12613
41
            } else {
12614
3
                unexpectedStructure = true;
12615
3
                break;
12616
3
            }
12617
9.92k
        } else if (ci_equal(stepName, "axisswap")) {
12618
150
            if (iFirstAxisSwap < 0) {
12619
139
                iFirstAxisSwap = i;
12620
139
            } else if (iSecondAxisSwap < 0) {
12621
7
                iSecondAxisSwap = i;
12622
7
            } else {
12623
4
                unexpectedStructure = true;
12624
4
                break;
12625
4
            }
12626
9.77k
        } else if (isProjectedStep(stepName)) {
12627
9.00k
            if (iProjStep >= 0) {
12628
241
                unexpectedStructure = true;
12629
241
                break;
12630
241
            }
12631
8.76k
            iProjStep = i;
12632
8.76k
        } else {
12633
776
            unexpectedStructure = true;
12634
776
            break;
12635
776
        }
12636
14.6k
    }
12637
12638
13.0k
    if (!d->steps_.empty()) {
12639
        // CRS candidate
12640
12.8k
        if ((d->steps_.size() == 1 &&
12641
11.0k
             d->getParamValue(d->steps_[0], "type") != "crs") ||
12642
10.9k
            (d->steps_.size() > 1 && d->getGlobalParamValue("type") != "crs")) {
12643
2.41k
            unexpectedStructure = true;
12644
2.41k
        }
12645
12.8k
    }
12646
12647
13.0k
    struct Logger {
12648
13.0k
        std::string msg{};
12649
12650
        // cppcheck-suppress functionStatic
12651
13.0k
        void setMessage(const char *msgIn) noexcept {
12652
2.44k
            try {
12653
2.44k
                msg = msgIn;
12654
2.44k
            } catch (const std::exception &) {
12655
0
            }
12656
2.44k
        }
12657
12658
13.0k
        static void log(void *user_data, int level, const char *msg) {
12659
2.44k
            if (level == PJ_LOG_ERROR) {
12660
2.44k
                static_cast<Logger *>(user_data)->setMessage(msg);
12661
2.44k
            }
12662
2.44k
        }
12663
13.0k
    };
12664
12665
    // If the structure is not recognized, then try to instantiate the
12666
    // pipeline, and if successful, wrap it in a PROJBasedOperation
12667
13.0k
    Logger logger;
12668
13.0k
    bool valid;
12669
12670
13.0k
    auto pj_context = d->ctx_ ? d->ctx_ : proj_context_create();
12671
13.0k
    if (!pj_context) {
12672
0
        throw ParsingException("out of memory");
12673
0
    }
12674
12675
    // Backup error logger and level, and install temporary handler
12676
13.0k
    auto old_logger = pj_context->logger;
12677
13.0k
    auto old_logger_app_data = pj_context->logger_app_data;
12678
13.0k
    auto log_level = proj_log_level(pj_context, PJ_LOG_ERROR);
12679
13.0k
    proj_log_func(pj_context, &logger, Logger::log);
12680
12681
13.0k
    if (pj_context != d->ctx_) {
12682
99
        proj_context_use_proj4_init_rules(pj_context, d->usePROJ4InitRules_);
12683
99
    }
12684
13.0k
    pj_context->projStringParserCreateFromPROJStringRecursionCounter++;
12685
13.0k
    auto pj = pj_create_internal(
12686
13.0k
        pj_context, (projString.find("type=crs") != std::string::npos
12687
13.0k
                         ? projString + " +disable_grid_presence_check"
12688
13.0k
                         : projString)
12689
13.0k
                        .c_str());
12690
13.0k
    pj_context->projStringParserCreateFromPROJStringRecursionCounter--;
12691
13.0k
    valid = pj != nullptr;
12692
12693
    // Restore initial error logger and level
12694
13.0k
    proj_log_level(pj_context, log_level);
12695
13.0k
    pj_context->logger = old_logger;
12696
13.0k
    pj_context->logger_app_data = old_logger_app_data;
12697
12698
    // Remove parameters not understood by PROJ.
12699
13.0k
    if (valid && d->steps_.size() == 1) {
12700
9.87k
        std::vector<Step::KeyValue> newParamValues{};
12701
9.87k
        std::set<std::string> foundKeys;
12702
9.87k
        auto &step = d->steps_[0];
12703
12704
55.3k
        for (auto &kv : step.paramValues) {
12705
55.3k
            bool recognizedByPROJ = false;
12706
55.3k
            if (foundKeys.find(kv.key) != foundKeys.end()) {
12707
4.23k
                continue;
12708
4.23k
            }
12709
51.0k
            foundKeys.insert(kv.key);
12710
51.0k
            if ((step.name == "krovak" || step.name == "mod_krovak") &&
12711
3.00k
                kv.key == "alpha") {
12712
                // We recognize it in our CRS parsing code
12713
1
                recognizedByPROJ = true;
12714
51.0k
            } else {
12715
408k
                for (auto cur = pj->params; cur; cur = cur->next) {
12716
408k
                    const char *equal = strchr(cur->param, '=');
12717
408k
                    if (equal && static_cast<size_t>(equal - cur->param) ==
12718
219k
                                     kv.key.size()) {
12719
54.5k
                        if (memcmp(cur->param, kv.key.c_str(), kv.key.size()) ==
12720
54.5k
                            0) {
12721
29.7k
                            recognizedByPROJ = (cur->used == 1);
12722
29.7k
                            break;
12723
29.7k
                        }
12724
354k
                    } else if (strcmp(cur->param, kv.key.c_str()) == 0) {
12725
21.2k
                        recognizedByPROJ = (cur->used == 1);
12726
21.2k
                        break;
12727
21.2k
                    }
12728
408k
                }
12729
51.0k
            }
12730
51.0k
            if (!recognizedByPROJ && kv.key == "geoid_crs") {
12731
3.92k
                for (auto &pair : step.paramValues) {
12732
3.92k
                    if (ci_equal(pair.key, "geoidgrids")) {
12733
651
                        recognizedByPROJ = true;
12734
651
                        break;
12735
651
                    }
12736
3.92k
                }
12737
753
            }
12738
51.0k
            if (recognizedByPROJ) {
12739
15.2k
                newParamValues.emplace_back(kv);
12740
15.2k
            }
12741
51.0k
        }
12742
9.87k
        step.paramValues = std::move(newParamValues);
12743
12744
9.87k
        d->projString_.clear();
12745
9.87k
        if (!step.name.empty()) {
12746
9.85k
            d->projString_ += step.isInit ? "+init=" : "+proj=";
12747
9.85k
            d->projString_ += step.name;
12748
9.85k
        }
12749
15.2k
        for (const auto &paramValue : step.paramValues) {
12750
15.2k
            if (!d->projString_.empty()) {
12751
15.2k
                d->projString_ += ' ';
12752
15.2k
            }
12753
15.2k
            d->projString_ += '+';
12754
15.2k
            d->projString_ += paramValue.key;
12755
15.2k
            if (!paramValue.value.empty()) {
12756
12.7k
                d->projString_ += '=';
12757
12.7k
                d->projString_ +=
12758
12.7k
                    pj_double_quote_string_param_if_needed(paramValue.value);
12759
12.7k
            }
12760
15.2k
        }
12761
9.87k
    }
12762
12763
13.0k
    proj_destroy(pj);
12764
12765
13.0k
    if (!valid) {
12766
1.73k
        const int l_errno = proj_context_errno(pj_context);
12767
1.73k
        std::string msg("Error " + toString(l_errno) + " (" +
12768
1.73k
                        proj_errno_string(l_errno) + ")");
12769
1.73k
        if (!logger.msg.empty()) {
12770
1.65k
            msg += ": ";
12771
1.65k
            msg += logger.msg;
12772
1.65k
        }
12773
1.73k
        logger.msg = std::move(msg);
12774
1.73k
    }
12775
12776
13.0k
    if (pj_context != d->ctx_) {
12777
99
        proj_context_destroy(pj_context);
12778
99
    }
12779
12780
13.0k
    if (!valid) {
12781
1.73k
        throw ParsingException(logger.msg);
12782
1.73k
    }
12783
12784
11.3k
    if (isGeocentricCRS) {
12785
        // First run is dry run to mark all recognized/unrecognized tokens
12786
2.37k
        for (int iter = 0; iter < 2; iter++) {
12787
2.28k
            auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12788
2.28k
                0, d->buildGeocentricCRS(0, (d->steps_.size() == 2 &&
12789
21
                                             d->steps_[1].name == "unitconvert")
12790
2.28k
                                                ? 1
12791
2.28k
                                                : -1));
12792
2.28k
            if (iter == 1) {
12793
1.09k
                return nn_static_pointer_cast<BaseObject>(obj);
12794
1.09k
            }
12795
2.28k
        }
12796
1.18k
    }
12797
12798
10.2k
    if (isTopocentricCRS) {
12799
        // First run is dry run to mark all recognized/unrecognized tokens
12800
10
        for (int iter = 0; iter < 2; iter++) {
12801
8
            auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12802
8
                0,
12803
8
                d->buildProjectedCRS(0, d->buildGeocentricCRS(0, -1), -1, -1));
12804
8
            if (iter == 1) {
12805
3
                return nn_static_pointer_cast<BaseObject>(obj);
12806
3
            }
12807
8
        }
12808
5
    }
12809
12810
10.2k
    if (!unexpectedStructure) {
12811
8.57k
        if (iFirstGeogStep == 0 && !d->steps_[iFirstGeogStep].inverted &&
12812
4.08k
            iSecondGeogStep < 0 && iProjStep < 0 &&
12813
3.10k
            (iFirstUnitConvert < 0 || iSecondUnitConvert < 0) &&
12814
3.10k
            (iFirstAxisSwap < 0 || iSecondAxisSwap < 0)) {
12815
            // First run is dry run to mark all recognized/unrecognized tokens
12816
6.20k
            for (int iter = 0; iter < 2; iter++) {
12817
6.11k
                auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12818
6.11k
                    0, d->buildGeodeticCRS(iFirstGeogStep, iFirstUnitConvert,
12819
6.11k
                                           iFirstAxisSwap, false));
12820
6.11k
                if (iter == 1) {
12821
3.01k
                    return nn_static_pointer_cast<BaseObject>(obj);
12822
3.01k
                }
12823
6.11k
            }
12824
3.10k
        }
12825
5.55k
        if (iProjStep >= 0 && !d->steps_[iProjStep].inverted &&
12826
5.41k
            (iFirstGeogStep < 0 || iFirstGeogStep + 1 == iProjStep) &&
12827
5.41k
            iSecondGeogStep < 0) {
12828
5.40k
            if (iFirstGeogStep < 0)
12829
4.43k
                iFirstGeogStep = iProjStep;
12830
            // First run is dry run to mark all recognized/unrecognized tokens
12831
10.8k
            for (int iter = 0; iter < 2; iter++) {
12832
10.1k
                auto obj = d->buildBoundOrCompoundCRSIfNeeded(
12833
10.1k
                    iProjStep,
12834
10.1k
                    d->buildProjectedCRS(
12835
10.1k
                        iProjStep,
12836
10.1k
                        d->buildGeodeticCRS(iFirstGeogStep,
12837
10.1k
                                            iFirstUnitConvert < iFirstGeogStep
12838
10.1k
                                                ? iFirstUnitConvert
12839
10.1k
                                                : -1,
12840
10.1k
                                            iFirstAxisSwap < iFirstGeogStep
12841
10.1k
                                                ? iFirstAxisSwap
12842
10.1k
                                                : -1,
12843
10.1k
                                            true),
12844
10.1k
                        iFirstUnitConvert < iFirstGeogStep ? iSecondUnitConvert
12845
10.1k
                                                           : iFirstUnitConvert,
12846
10.1k
                        iFirstAxisSwap < iFirstGeogStep ? iSecondAxisSwap
12847
10.1k
                                                        : iFirstAxisSwap));
12848
10.1k
                if (iter == 1) {
12849
4.70k
                    return nn_static_pointer_cast<BaseObject>(obj);
12850
4.70k
                }
12851
10.1k
            }
12852
5.40k
        }
12853
5.55k
    }
12854
12855
2.54k
    auto props = PropertyMap();
12856
2.54k
    if (!d->title_.empty()) {
12857
48
        props.set(IdentifiedObject::NAME_KEY, d->title_);
12858
48
    }
12859
2.54k
    return operation::SingleOperation::createPROJBased(props, projString,
12860
2.54k
                                                       nullptr, nullptr, {});
12861
10.2k
}
12862
12863
// ---------------------------------------------------------------------------
12864
12865
//! @cond Doxygen_Suppress
12866
struct JSONFormatter::Private {
12867
    CPLJSonStreamingWriter writer_{nullptr, nullptr};
12868
    DatabaseContextPtr dbContext_{};
12869
12870
    std::vector<bool> stackHasId_{false};
12871
    std::vector<bool> outputIdStack_{true};
12872
    bool allowIDInImmediateChild_ = false;
12873
    bool omitTypeInImmediateChild_ = false;
12874
    bool abridgedTransformation_ = false;
12875
    bool abridgedTransformationWriteSourceCRS_ = false;
12876
    std::string schema_ = PROJJSON_DEFAULT_VERSION;
12877
12878
    // cppcheck-suppress functionStatic
12879
0
    void pushOutputId(bool outputIdIn) { outputIdStack_.push_back(outputIdIn); }
12880
12881
    // cppcheck-suppress functionStatic
12882
0
    void popOutputId() { outputIdStack_.pop_back(); }
12883
};
12884
//! @endcond
12885
12886
// ---------------------------------------------------------------------------
12887
12888
/** \brief Constructs a new formatter.
12889
 *
12890
 * A formatter can be used only once (its internal state is mutated)
12891
 *
12892
 * @return new formatter.
12893
 */
12894
JSONFormatterNNPtr JSONFormatter::create( // cppcheck-suppress passedByValue
12895
0
    DatabaseContextPtr dbContext) {
12896
0
    auto ret = NN_NO_CHECK(JSONFormatter::make_unique<JSONFormatter>());
12897
0
    ret->d->dbContext_ = std::move(dbContext);
12898
0
    return ret;
12899
0
}
12900
12901
// ---------------------------------------------------------------------------
12902
12903
/** \brief Whether to use multi line output or not. */
12904
0
JSONFormatter &JSONFormatter::setMultiLine(bool multiLine) noexcept {
12905
0
    d->writer_.SetPrettyFormatting(multiLine);
12906
0
    return *this;
12907
0
}
12908
12909
// ---------------------------------------------------------------------------
12910
12911
/** \brief Set number of spaces for each indentation level (defaults to 4).
12912
 */
12913
0
JSONFormatter &JSONFormatter::setIndentationWidth(int width) noexcept {
12914
0
    d->writer_.SetIndentationSize(width);
12915
0
    return *this;
12916
0
}
12917
12918
// ---------------------------------------------------------------------------
12919
12920
/** \brief Set the value of the "$schema" key in the top level object.
12921
 *
12922
 * If set to empty string, it will not be written.
12923
 */
12924
0
JSONFormatter &JSONFormatter::setSchema(const std::string &schema) noexcept {
12925
0
    d->schema_ = schema;
12926
0
    return *this;
12927
0
}
12928
12929
// ---------------------------------------------------------------------------
12930
12931
//! @cond Doxygen_Suppress
12932
12933
0
JSONFormatter::JSONFormatter() : d(std::make_unique<Private>()) {}
12934
12935
// ---------------------------------------------------------------------------
12936
12937
0
JSONFormatter::~JSONFormatter() = default;
12938
12939
// ---------------------------------------------------------------------------
12940
12941
0
CPLJSonStreamingWriter *JSONFormatter::writer() const { return &(d->writer_); }
12942
12943
// ---------------------------------------------------------------------------
12944
12945
0
const DatabaseContextPtr &JSONFormatter::databaseContext() const {
12946
0
    return d->dbContext_;
12947
0
}
12948
12949
// ---------------------------------------------------------------------------
12950
12951
0
bool JSONFormatter::outputId() const { return d->outputIdStack_.back(); }
12952
12953
// ---------------------------------------------------------------------------
12954
12955
0
bool JSONFormatter::outputUsage(bool calledBeforeObjectContext) const {
12956
0
    return outputId() &&
12957
0
           d->outputIdStack_.size() == (calledBeforeObjectContext ? 1U : 2U);
12958
0
}
12959
12960
// ---------------------------------------------------------------------------
12961
12962
0
void JSONFormatter::setAllowIDInImmediateChild() {
12963
0
    d->allowIDInImmediateChild_ = true;
12964
0
}
12965
12966
// ---------------------------------------------------------------------------
12967
12968
0
void JSONFormatter::setOmitTypeInImmediateChild() {
12969
0
    d->omitTypeInImmediateChild_ = true;
12970
0
}
12971
12972
// ---------------------------------------------------------------------------
12973
12974
JSONFormatter::ObjectContext::ObjectContext(JSONFormatter &formatter,
12975
                                            const char *objectType, bool hasId)
12976
0
    : m_formatter(formatter) {
12977
0
    m_formatter.d->writer_.StartObj();
12978
0
    if (m_formatter.d->outputIdStack_.size() == 1 &&
12979
0
        !m_formatter.d->schema_.empty()) {
12980
0
        m_formatter.d->writer_.AddObjKey("$schema");
12981
0
        m_formatter.d->writer_.Add(m_formatter.d->schema_);
12982
0
    }
12983
0
    if (objectType && !m_formatter.d->omitTypeInImmediateChild_) {
12984
0
        m_formatter.d->writer_.AddObjKey("type");
12985
0
        m_formatter.d->writer_.Add(objectType);
12986
0
    }
12987
0
    m_formatter.d->omitTypeInImmediateChild_ = false;
12988
    // All intermediate nodes shouldn't have ID if a parent has an ID
12989
    // unless explicitly enabled.
12990
0
    if (m_formatter.d->allowIDInImmediateChild_) {
12991
0
        m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0]);
12992
0
        m_formatter.d->allowIDInImmediateChild_ = false;
12993
0
    } else {
12994
0
        m_formatter.d->pushOutputId(m_formatter.d->outputIdStack_[0] &&
12995
0
                                    !m_formatter.d->stackHasId_.back());
12996
0
    }
12997
12998
0
    m_formatter.d->stackHasId_.push_back(hasId ||
12999
0
                                         m_formatter.d->stackHasId_.back());
13000
0
}
13001
13002
// ---------------------------------------------------------------------------
13003
13004
0
JSONFormatter::ObjectContext::~ObjectContext() {
13005
0
    m_formatter.d->writer_.EndObj();
13006
0
    m_formatter.d->stackHasId_.pop_back();
13007
0
    m_formatter.d->popOutputId();
13008
0
}
13009
13010
// ---------------------------------------------------------------------------
13011
13012
0
void JSONFormatter::setAbridgedTransformation(bool outputIn) {
13013
0
    d->abridgedTransformation_ = outputIn;
13014
0
}
13015
13016
// ---------------------------------------------------------------------------
13017
13018
0
bool JSONFormatter::abridgedTransformation() const {
13019
0
    return d->abridgedTransformation_;
13020
0
}
13021
13022
// ---------------------------------------------------------------------------
13023
13024
0
void JSONFormatter::setAbridgedTransformationWriteSourceCRS(bool writeCRS) {
13025
0
    d->abridgedTransformationWriteSourceCRS_ = writeCRS;
13026
0
}
13027
13028
// ---------------------------------------------------------------------------
13029
13030
0
bool JSONFormatter::abridgedTransformationWriteSourceCRS() const {
13031
0
    return d->abridgedTransformationWriteSourceCRS_;
13032
0
}
13033
13034
//! @endcond
13035
13036
// ---------------------------------------------------------------------------
13037
13038
/** \brief Return the serialized JSON.
13039
 */
13040
0
const std::string &JSONFormatter::toString() const {
13041
0
    return d->writer_.GetString();
13042
0
}
13043
13044
// ---------------------------------------------------------------------------
13045
13046
//! @cond Doxygen_Suppress
13047
13.0M
IJSONExportable::~IJSONExportable() = default;
13048
13049
// ---------------------------------------------------------------------------
13050
13051
0
std::string IJSONExportable::exportToJSON(JSONFormatter *formatter) const {
13052
0
    _exportToJSON(formatter);
13053
0
    return formatter->toString();
13054
0
}
13055
13056
//! @endcond
13057
13058
} // namespace io
13059
NS_PROJ_END