Coverage Report

Created: 2026-05-16 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/proj/src/iso19111/crs.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
//! @cond Doxygen_Suppress
34
#define DO_NOT_DEFINE_EXTERN_DERIVED_CRS_TEMPLATE
35
//! @endcond
36
37
#include "proj/crs.hpp"
38
#include "proj/common.hpp"
39
#include "proj/coordinateoperation.hpp"
40
#include "proj/coordinatesystem.hpp"
41
#include "proj/io.hpp"
42
#include "proj/metadata.hpp"
43
#include "proj/util.hpp"
44
45
#include "proj/internal/coordinatesystem_internal.hpp"
46
#include "proj/internal/crs_internal.hpp"
47
#include "proj/internal/datum_internal.hpp"
48
#include "proj/internal/internal.hpp"
49
#include "proj/internal/io_internal.hpp"
50
51
#include "operation/oputils.hpp"
52
53
#include "proj_constants.h"
54
#include "proj_json_streaming_writer.hpp"
55
56
#include <algorithm>
57
#include <cassert>
58
#include <cmath>
59
#include <cstring>
60
#include <iostream>
61
#include <memory>
62
#include <string>
63
#include <vector>
64
65
using namespace NS_PROJ::internal;
66
67
#if 0
68
namespace dropbox{ namespace oxygen {
69
template<> nn<NS_PROJ::crs::CRSPtr>::~nn() = default;
70
template<> nn<NS_PROJ::crs::SingleCRSPtr>::~nn() = default;
71
template<> nn<NS_PROJ::crs::GeodeticCRSPtr>::~nn() = default;
72
template<> nn<NS_PROJ::crs::GeographicCRSPtr>::~nn() = default;
73
template<> nn<NS_PROJ::crs::DerivedCRSPtr>::~nn() = default;
74
template<> nn<NS_PROJ::crs::ProjectedCRSPtr>::~nn() = default;
75
template<> nn<NS_PROJ::crs::VerticalCRSPtr>::~nn() = default;
76
template<> nn<NS_PROJ::crs::CompoundCRSPtr>::~nn() = default;
77
template<> nn<NS_PROJ::crs::TemporalCRSPtr>::~nn() = default;
78
template<> nn<NS_PROJ::crs::EngineeringCRSPtr>::~nn() = default;
79
template<> nn<NS_PROJ::crs::ParametricCRSPtr>::~nn() = default;
80
template<> nn<NS_PROJ::crs::BoundCRSPtr>::~nn() = default;
81
template<> nn<NS_PROJ::crs::DerivedGeodeticCRSPtr>::~nn() = default;
82
template<> nn<NS_PROJ::crs::DerivedGeographicCRSPtr>::~nn() = default;
83
template<> nn<NS_PROJ::crs::DerivedProjectedCRSPtr>::~nn() = default;
84
template<> nn<NS_PROJ::crs::DerivedVerticalCRSPtr>::~nn() = default;
85
template<> nn<NS_PROJ::crs::DerivedTemporalCRSPtr>::~nn() = default;
86
template<> nn<NS_PROJ::crs::DerivedEngineeringCRSPtr>::~nn() = default;
87
template<> nn<NS_PROJ::crs::DerivedParametricCRSPtr>::~nn() = default;
88
}}
89
#endif
90
91
NS_PROJ_START
92
93
namespace crs {
94
95
//! @cond Doxygen_Suppress
96
constexpr const char *PROMOTED_TO_3D_PRELUDE = "Promoted to 3D from ";
97
//! @endcond
98
99
// ---------------------------------------------------------------------------
100
101
//! @cond Doxygen_Suppress
102
struct CRS::Private {
103
    BoundCRSPtr canonicalBoundCRS_{};
104
    std::string extensionProj4_{};
105
    bool implicitCS_ = false;
106
    bool over_ = false;
107
108
    bool allowNonConformantWKT1Export_ = false;
109
    // for what was initially a COMPD_CS with a VERT_CS with a datum type ==
110
    // ellipsoidal height / 2002
111
    CompoundCRSPtr originalCompoundCRS_{};
112
113
4.86M
    void setNonStandardProperties(const util::PropertyMap &properties) {
114
4.86M
        {
115
4.86M
            const auto pVal = properties.get("IMPLICIT_CS");
116
4.86M
            if (pVal) {
117
1.09M
                if (const auto genVal =
118
1.09M
                        dynamic_cast<const util::BoxedValue *>(pVal->get())) {
119
1.09M
                    if (genVal->type() == util::BoxedValue::Type::BOOLEAN &&
120
1.09M
                        genVal->booleanValue()) {
121
1.09M
                        implicitCS_ = true;
122
1.09M
                    }
123
1.09M
                }
124
1.09M
            }
125
4.86M
        }
126
127
4.86M
        {
128
4.86M
            const auto pVal = properties.get("OVER");
129
4.86M
            if (pVal) {
130
0
                if (const auto genVal =
131
0
                        dynamic_cast<const util::BoxedValue *>(pVal->get())) {
132
0
                    if (genVal->type() == util::BoxedValue::Type::BOOLEAN &&
133
0
                        genVal->booleanValue()) {
134
0
                        over_ = true;
135
0
                    }
136
0
                }
137
0
            }
138
4.86M
        }
139
4.86M
    }
140
};
141
//! @endcond
142
143
// ---------------------------------------------------------------------------
144
145
6.29M
CRS::CRS() : d(std::make_unique<Private>()) {}
146
147
// ---------------------------------------------------------------------------
148
149
CRS::CRS(const CRS &other)
150
32.9k
    : ObjectUsage(other), d(std::make_unique<Private>(*(other.d))) {}
151
152
// ---------------------------------------------------------------------------
153
154
//! @cond Doxygen_Suppress
155
6.32M
CRS::~CRS() = default;
156
//! @endcond
157
158
// ---------------------------------------------------------------------------
159
160
//! @cond Doxygen_Suppress
161
162
/** \brief Return whether the CRS has an implicit coordinate system
163
 * (e.g from ESRI WKT) */
164
122k
bool CRS::hasImplicitCS() const { return d->implicitCS_; }
165
166
/** \brief Return whether the CRS has a +over flag */
167
1.38M
bool CRS::hasOver() const { return d->over_; }
168
169
//! @endcond
170
171
// ---------------------------------------------------------------------------
172
173
/** \brief Return the BoundCRS potentially attached to this CRS.
174
 *
175
 * In the case this method is called on a object returned by
176
 * BoundCRS::baseCRSWithCanonicalBoundCRS(), this method will return this
177
 * BoundCRS
178
 *
179
 * @return a BoundCRSPtr, that might be null.
180
 */
181
235k
const BoundCRSPtr &CRS::canonicalBoundCRS() PROJ_PURE_DEFN {
182
235k
    return d->canonicalBoundCRS_;
183
235k
}
184
185
// ---------------------------------------------------------------------------
186
187
/** \brief Return whether a CRS is a dynamic CRS.
188
 *
189
 * A dynamic CRS is a CRS that contains a geodetic CRS whose geodetic reference
190
 * frame is dynamic, or a vertical CRS whose vertical reference frame is
191
 * dynamic.
192
 * @param considerWGS84AsDynamic set to true to consider the WGS 84 / EPSG:6326
193
 *                               datum ensemble as dynamic.
194
 * @since 9.2
195
 */
196
5.54k
bool CRS::isDynamic(bool considerWGS84AsDynamic) const {
197
198
5.54k
    if (auto raw = extractGeodeticCRSRaw()) {
199
5.34k
        const auto &l_datum = raw->datum();
200
5.34k
        if (l_datum) {
201
4.30k
            if (dynamic_cast<datum::DynamicGeodeticReferenceFrame *>(
202
4.30k
                    l_datum.get())) {
203
385
                return true;
204
385
            }
205
3.92k
            if (considerWGS84AsDynamic &&
206
3.91k
                l_datum->nameStr() == "World Geodetic System 1984") {
207
1.53k
                return true;
208
1.53k
            }
209
3.92k
        }
210
3.42k
        if (considerWGS84AsDynamic) {
211
3.42k
            const auto &l_datumEnsemble = raw->datumEnsemble();
212
3.42k
            if (l_datumEnsemble && l_datumEnsemble->nameStr() ==
213
1.03k
                                       "World Geodetic System 1984 ensemble") {
214
636
                return true;
215
636
            }
216
3.42k
        }
217
3.42k
    }
218
219
2.98k
    if (auto vertCRS = extractVerticalCRS()) {
220
927
        const auto &l_datum = vertCRS->datum();
221
927
        if (l_datum && dynamic_cast<datum::DynamicVerticalReferenceFrame *>(
222
632
                           l_datum.get())) {
223
139
            return true;
224
139
        }
225
927
    }
226
227
2.85k
    return false;
228
2.98k
}
229
230
// ---------------------------------------------------------------------------
231
232
/** \brief Return the GeodeticCRS of the CRS.
233
 *
234
 * Returns the GeodeticCRS contained in a CRS. This works currently with
235
 * input parameters of type GeodeticCRS or derived, ProjectedCRS,
236
 * CompoundCRS or BoundCRS.
237
 *
238
 * @return a GeodeticCRSPtr, that might be null.
239
 */
240
5.25M
GeodeticCRSPtr CRS::extractGeodeticCRS() const {
241
5.25M
    auto raw = extractGeodeticCRSRaw();
242
5.25M
    if (raw) {
243
4.57M
        return std::dynamic_pointer_cast<GeodeticCRS>(
244
4.57M
            raw->shared_from_this().as_nullable());
245
4.57M
    }
246
678k
    return nullptr;
247
5.25M
}
248
249
// ---------------------------------------------------------------------------
250
251
//! @cond Doxygen_Suppress
252
9.02M
const GeodeticCRS *CRS::extractGeodeticCRSRaw() const {
253
9.02M
    auto geodCRS = dynamic_cast<const GeodeticCRS *>(this);
254
9.02M
    if (geodCRS) {
255
4.98M
        return geodCRS;
256
4.98M
    }
257
4.03M
    auto projCRS = dynamic_cast<const ProjectedCRS *>(this);
258
4.03M
    if (projCRS) {
259
2.66M
        return projCRS->baseCRS()->extractGeodeticCRSRaw();
260
2.66M
    }
261
1.37M
    auto compoundCRS = dynamic_cast<const CompoundCRS *>(this);
262
1.37M
    if (compoundCRS) {
263
521k
        for (const auto &subCrs : compoundCRS->componentReferenceSystems()) {
264
521k
            auto retGeogCRS = subCrs->extractGeodeticCRSRaw();
265
521k
            if (retGeogCRS) {
266
420k
                return retGeogCRS;
267
420k
            }
268
521k
        }
269
450k
    }
270
950k
    auto boundCRS = dynamic_cast<const BoundCRS *>(this);
271
950k
    if (boundCRS) {
272
120k
        return boundCRS->baseCRS()->extractGeodeticCRSRaw();
273
120k
    }
274
829k
    auto derivedProjectedCRS = dynamic_cast<const DerivedProjectedCRS *>(this);
275
829k
    if (derivedProjectedCRS) {
276
0
        return derivedProjectedCRS->baseCRS()->extractGeodeticCRSRaw();
277
0
    }
278
829k
    return nullptr;
279
829k
}
280
//! @endcond
281
282
// ---------------------------------------------------------------------------
283
284
//! @cond Doxygen_Suppress
285
5.34M
const std::string &CRS::getExtensionProj4() const noexcept {
286
5.34M
    return d->extensionProj4_;
287
5.34M
}
288
//! @endcond
289
290
// ---------------------------------------------------------------------------
291
292
/** \brief Return the GeographicCRS of the CRS.
293
 *
294
 * Returns the GeographicCRS contained in a CRS. This works currently with
295
 * input parameters of type GeographicCRS or derived, ProjectedCRS,
296
 * CompoundCRS or BoundCRS.
297
 *
298
 * @return a GeographicCRSPtr, that might be null.
299
 */
300
201k
GeographicCRSPtr CRS::extractGeographicCRS() const {
301
201k
    auto raw = extractGeodeticCRSRaw();
302
201k
    if (raw) {
303
152k
        return std::dynamic_pointer_cast<GeographicCRS>(
304
152k
            raw->shared_from_this().as_nullable());
305
152k
    }
306
49.2k
    return nullptr;
307
201k
}
308
309
// ---------------------------------------------------------------------------
310
311
//! @cond Doxygen_Suppress
312
static util::PropertyMap
313
389k
createPropertyMap(const common::IdentifiedObject *obj) {
314
389k
    auto props = util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
315
389k
                                         obj->nameStr());
316
389k
    if (obj->isDeprecated()) {
317
198
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
318
198
    }
319
389k
    return props;
320
389k
}
321
322
//! @endcond
323
324
// ---------------------------------------------------------------------------
325
326
//! @cond Doxygen_Suppress
327
105k
CRSNNPtr CRS::alterGeodeticCRS(const GeodeticCRSNNPtr &newGeodCRS) const {
328
105k
    if (dynamic_cast<const GeodeticCRS *>(this)) {
329
336
        return newGeodCRS;
330
336
    }
331
332
105k
    if (auto projCRS = dynamic_cast<const ProjectedCRS *>(this)) {
333
105k
        return ProjectedCRS::create(createPropertyMap(this), newGeodCRS,
334
105k
                                    projCRS->derivingConversion(),
335
105k
                                    projCRS->coordinateSystem());
336
105k
    }
337
338
0
    if (auto derivedProjCRS = dynamic_cast<const DerivedProjectedCRS *>(this)) {
339
0
        auto newProjCRS =
340
0
            NN_CHECK_ASSERT(util::nn_dynamic_pointer_cast<ProjectedCRS>(
341
0
                derivedProjCRS->baseCRS()->alterGeodeticCRS(newGeodCRS)));
342
343
0
        return DerivedProjectedCRS::create(createPropertyMap(this), newProjCRS,
344
0
                                           derivedProjCRS->derivingConversion(),
345
0
                                           derivedProjCRS->coordinateSystem());
346
0
    }
347
348
0
    if (auto compoundCRS = dynamic_cast<const CompoundCRS *>(this)) {
349
0
        std::vector<CRSNNPtr> components;
350
0
        for (const auto &subCrs : compoundCRS->componentReferenceSystems()) {
351
0
            components.emplace_back(subCrs->alterGeodeticCRS(newGeodCRS));
352
0
        }
353
0
        return CompoundCRS::create(createPropertyMap(this), components);
354
0
    }
355
356
0
    return NN_NO_CHECK(
357
0
        std::dynamic_pointer_cast<CRS>(shared_from_this().as_nullable()));
358
0
}
359
//! @endcond
360
361
// ---------------------------------------------------------------------------
362
363
//! @cond Doxygen_Suppress
364
156k
CRSNNPtr CRS::alterCSLinearUnit(const common::UnitOfMeasure &unit) const {
365
156k
    {
366
156k
        auto projCRS = dynamic_cast<const ProjectedCRS *>(this);
367
156k
        if (projCRS) {
368
129k
            return ProjectedCRS::create(
369
129k
                createPropertyMap(this), projCRS->baseCRS(),
370
129k
                projCRS->derivingConversion(),
371
129k
                projCRS->coordinateSystem()->alterUnit(unit));
372
129k
        }
373
156k
    }
374
375
26.9k
    {
376
26.9k
        auto geodCRS = dynamic_cast<const GeodeticCRS *>(this);
377
26.9k
        if (geodCRS && geodCRS->isGeocentric()) {
378
2.78k
            auto cs = dynamic_cast<const cs::CartesianCS *>(
379
2.78k
                geodCRS->coordinateSystem().get());
380
2.78k
            assert(cs);
381
2.78k
            return GeodeticCRS::create(
382
2.78k
                createPropertyMap(this), geodCRS->datum(),
383
2.78k
                geodCRS->datumEnsemble(), cs->alterUnit(unit));
384
2.78k
        }
385
26.9k
    }
386
387
24.2k
    {
388
24.2k
        auto geogCRS = dynamic_cast<const GeographicCRS *>(this);
389
24.2k
        if (geogCRS && geogCRS->coordinateSystem()->axisList().size() == 3) {
390
21
            return GeographicCRS::create(
391
21
                createPropertyMap(this), geogCRS->datum(),
392
21
                geogCRS->datumEnsemble(),
393
21
                geogCRS->coordinateSystem()->alterLinearUnit(unit));
394
21
        }
395
24.2k
    }
396
397
24.1k
    {
398
24.1k
        auto vertCRS = dynamic_cast<const VerticalCRS *>(this);
399
24.1k
        if (vertCRS) {
400
74
            return VerticalCRS::create(
401
74
                createPropertyMap(this), vertCRS->datum(),
402
74
                vertCRS->datumEnsemble(),
403
74
                vertCRS->coordinateSystem()->alterUnit(unit));
404
74
        }
405
24.1k
    }
406
407
24.1k
    {
408
24.1k
        auto engCRS = dynamic_cast<const EngineeringCRS *>(this);
409
24.1k
        if (engCRS) {
410
21.9k
            auto cartCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(
411
21.9k
                engCRS->coordinateSystem());
412
21.9k
            if (cartCS) {
413
21.9k
                return EngineeringCRS::create(createPropertyMap(this),
414
21.9k
                                              engCRS->datum(),
415
21.9k
                                              cartCS->alterUnit(unit));
416
21.9k
            } else {
417
20
                auto vertCS = util::nn_dynamic_pointer_cast<cs::VerticalCS>(
418
20
                    engCRS->coordinateSystem());
419
20
                if (vertCS) {
420
20
                    return EngineeringCRS::create(createPropertyMap(this),
421
20
                                                  engCRS->datum(),
422
20
                                                  vertCS->alterUnit(unit));
423
20
                }
424
20
            }
425
21.9k
        }
426
24.1k
    }
427
428
2.16k
    {
429
2.16k
        auto derivedProjCRS = dynamic_cast<const DerivedProjectedCRS *>(this);
430
2.16k
        if (derivedProjCRS) {
431
0
            auto cs = derivedProjCRS->coordinateSystem();
432
0
            auto cartCS = util::nn_dynamic_pointer_cast<cs::CartesianCS>(cs);
433
0
            if (cartCS) {
434
0
                cs = cartCS->alterUnit(unit);
435
0
            }
436
0
            return DerivedProjectedCRS::create(
437
0
                createPropertyMap(this), derivedProjCRS->baseCRS(),
438
0
                derivedProjCRS->derivingConversion(), cs);
439
0
        }
440
2.16k
    }
441
442
2.16k
    {
443
2.16k
        auto compoundCRS = dynamic_cast<const CompoundCRS *>(this);
444
2.16k
        if (compoundCRS) {
445
73
            std::vector<CRSNNPtr> components;
446
73
            for (const auto &subCrs :
447
146
                 compoundCRS->componentReferenceSystems()) {
448
146
                components.push_back(subCrs->alterCSLinearUnit(unit));
449
146
            }
450
73
            return CompoundCRS::create(createPropertyMap(this), components);
451
73
        }
452
2.16k
    }
453
454
2.08k
    {
455
2.08k
        auto boundCRS = dynamic_cast<const BoundCRS *>(this);
456
2.08k
        if (boundCRS) {
457
62
            return BoundCRS::create(
458
62
                createPropertyMap(this),
459
62
                boundCRS->baseCRS()->alterCSLinearUnit(unit),
460
62
                boundCRS->hubCRS(), boundCRS->transformation());
461
62
        }
462
2.08k
    }
463
464
2.02k
    return NN_NO_CHECK(
465
2.08k
        std::dynamic_pointer_cast<CRS>(shared_from_this().as_nullable()));
466
2.08k
}
467
//! @endcond
468
469
// ---------------------------------------------------------------------------
470
471
/** \brief Return the VerticalCRS of the CRS.
472
 *
473
 * Returns the VerticalCRS contained in a CRS. This works currently with
474
 * input parameters of type VerticalCRS or derived, CompoundCRS or BoundCRS.
475
 *
476
 * @return a VerticalCRSPtr, that might be null.
477
 */
478
8.86k
VerticalCRSPtr CRS::extractVerticalCRS() const {
479
8.86k
    auto vertCRS = dynamic_cast<const VerticalCRS *>(this);
480
8.86k
    if (vertCRS) {
481
4.19k
        return std::dynamic_pointer_cast<VerticalCRS>(
482
4.19k
            shared_from_this().as_nullable());
483
4.19k
    }
484
4.67k
    auto compoundCRS = dynamic_cast<const CompoundCRS *>(this);
485
4.67k
    if (compoundCRS) {
486
2.59k
        for (const auto &subCrs : compoundCRS->componentReferenceSystems()) {
487
2.59k
            auto retVertCRS = subCrs->extractVerticalCRS();
488
2.59k
            if (retVertCRS) {
489
1.29k
                return retVertCRS;
490
1.29k
            }
491
2.59k
        }
492
1.29k
    }
493
3.37k
    auto boundCRS = dynamic_cast<const BoundCRS *>(this);
494
3.37k
    if (boundCRS) {
495
22
        return boundCRS->baseCRS()->extractVerticalCRS();
496
22
    }
497
3.35k
    return nullptr;
498
3.37k
}
499
500
// ---------------------------------------------------------------------------
501
502
/** \brief Returns potentially
503
 * a BoundCRS, with a transformation to EPSG:4326, wrapping this CRS
504
 *
505
 * If no such BoundCRS is possible, the object will be returned.
506
 *
507
 * The purpose of this method is to be able to format a PROJ.4 string with
508
 * a +towgs84 parameter or a WKT1:GDAL string with a TOWGS node.
509
 *
510
 * This method will fetch the GeographicCRS of this CRS and find a
511
 * transformation to EPSG:4326 using the domain of the validity of the main CRS,
512
 * and there's only one Helmert transformation.
513
 *
514
 * @return a CRS.
515
 */
516
CRSNNPtr CRS::createBoundCRSToWGS84IfPossible(
517
    const io::DatabaseContextPtr &dbContext,
518
    operation::CoordinateOperationContext::IntermediateCRSUse
519
705
        allowIntermediateCRSUse) const {
520
705
    auto thisAsCRS = NN_NO_CHECK(
521
705
        std::static_pointer_cast<CRS>(shared_from_this().as_nullable()));
522
705
    auto boundCRS = util::nn_dynamic_pointer_cast<BoundCRS>(thisAsCRS);
523
705
    if (!boundCRS) {
524
705
        boundCRS = canonicalBoundCRS();
525
705
    }
526
705
    if (boundCRS) {
527
0
        if (boundCRS->hubCRS()->_isEquivalentTo(
528
0
                GeographicCRS::EPSG_4326.get(),
529
0
                util::IComparable::Criterion::EQUIVALENT, dbContext)) {
530
0
            return NN_NO_CHECK(boundCRS);
531
0
        }
532
0
    }
533
534
705
    auto compoundCRS = dynamic_cast<const CompoundCRS *>(this);
535
705
    if (compoundCRS) {
536
0
        const auto &comps = compoundCRS->componentReferenceSystems();
537
0
        if (comps.size() == 2) {
538
0
            auto horiz = comps[0]->createBoundCRSToWGS84IfPossible(
539
0
                dbContext, allowIntermediateCRSUse);
540
0
            auto vert = comps[1]->createBoundCRSToWGS84IfPossible(
541
0
                dbContext, allowIntermediateCRSUse);
542
0
            if (horiz.get() != comps[0].get() || vert.get() != comps[1].get()) {
543
0
                return CompoundCRS::create(createPropertyMap(this),
544
0
                                           {std::move(horiz), std::move(vert)});
545
0
            }
546
0
        }
547
0
        return thisAsCRS;
548
0
    }
549
550
705
    if (!dbContext) {
551
0
        return thisAsCRS;
552
0
    }
553
554
705
    const auto &l_domains = domains();
555
705
    metadata::ExtentPtr extent;
556
705
    if (!l_domains.empty()) {
557
700
        if (l_domains.size() == 2) {
558
            // Special case for the UTM ETRS89 CRS that have 2 domains since
559
            // EPSG v11.009. The "Pan-European conformal mapping at scales
560
            // larger than 1:500,000" one includes a slightly smaller one
561
            // "Engineering survey, topographic mapping" valid for some
562
            // countries. Let's take the larger one to get a transformation
563
            // valid for all domains.
564
22
            auto extent0 = l_domains[0]->domainOfValidity();
565
22
            auto extent1 = l_domains[1]->domainOfValidity();
566
22
            if (extent0 && extent1) {
567
22
                if (extent0->contains(NN_NO_CHECK(extent1))) {
568
0
                    extent = std::move(extent0);
569
22
                } else if (extent1->contains(NN_NO_CHECK(extent0))) {
570
22
                    extent = std::move(extent1);
571
22
                }
572
22
            }
573
22
        }
574
700
        if (!extent) {
575
678
            if (l_domains.size() > 1) {
576
                // If there are several domains of validity, then it is
577
                // extremely unlikely, we could get a single transformation
578
                // valid for all. At least, in the current state of the code of
579
                // createOperations() which returns a single extent, this can't
580
                // happen.
581
0
                return thisAsCRS;
582
0
            }
583
678
            extent = l_domains[0]->domainOfValidity();
584
678
        }
585
700
    }
586
587
705
    std::string crs_authority;
588
705
    const auto &l_identifiers = identifiers();
589
    // If the object has an authority, restrict the transformations to
590
    // come from that codespace too. This avoids for example EPSG:4269
591
    // (NAD83) to use a (dubious) ESRI transformation.
592
705
    if (!l_identifiers.empty()) {
593
705
        crs_authority = *(l_identifiers[0]->codeSpace());
594
705
    }
595
596
705
    auto authorities = dbContext->getAllowedAuthorities(
597
705
        crs_authority, metadata::Identifier::EPSG);
598
705
    if (authorities.empty()) {
599
0
        authorities.emplace_back();
600
0
    }
601
602
    // Vertical CRS ?
603
705
    auto vertCRS = dynamic_cast<const VerticalCRS *>(this);
604
705
    if (vertCRS) {
605
162
        auto hubCRS =
606
162
            util::nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4979);
607
442
        for (const auto &authority : authorities) {
608
442
            try {
609
610
442
                auto authFactory = io::AuthorityFactory::create(
611
442
                    NN_NO_CHECK(dbContext),
612
442
                    authority == "any" ? std::string() : authority);
613
442
                auto ctxt = operation::CoordinateOperationContext::create(
614
442
                    authFactory, extent, 0.0);
615
442
                ctxt->setAllowUseIntermediateCRS(allowIntermediateCRSUse);
616
                // ctxt->setSpatialCriterion(
617
                //    operation::CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
618
442
                auto list = operation::CoordinateOperationFactory::create()
619
442
                                ->createOperations(hubCRS, thisAsCRS, ctxt);
620
442
                CRSPtr candidateBoundCRS;
621
445
                for (const auto &op : list) {
622
445
                    auto transf = util::nn_dynamic_pointer_cast<
623
445
                        operation::Transformation>(op);
624
                    // Only keep transformations that use a known grid
625
445
                    if (transf && !transf->hasBallparkTransformation()) {
626
50
                        auto gridsNeeded = transf->gridsNeeded(dbContext, true);
627
50
                        bool gridsKnown = !gridsNeeded.empty();
628
50
                        for (const auto &gridDesc : gridsNeeded) {
629
50
                            if (gridDesc.packageName.empty() &&
630
50
                                !(!gridDesc.url.empty() &&
631
46
                                  gridDesc.openLicense) &&
632
4
                                !gridDesc.available) {
633
4
                                gridsKnown = false;
634
4
                                break;
635
4
                            }
636
50
                        }
637
50
                        if (gridsKnown) {
638
46
                            if (candidateBoundCRS) {
639
1
                                candidateBoundCRS = nullptr;
640
1
                                break;
641
1
                            }
642
45
                            candidateBoundCRS =
643
45
                                BoundCRS::create(thisAsCRS, hubCRS,
644
45
                                                 NN_NO_CHECK(transf))
645
45
                                    .as_nullable();
646
45
                        }
647
50
                    }
648
445
                }
649
442
                if (candidateBoundCRS) {
650
44
                    return NN_NO_CHECK(candidateBoundCRS);
651
44
                }
652
442
            } catch (const std::exception &) {
653
0
            }
654
442
        }
655
118
        return thisAsCRS;
656
162
    }
657
658
    // Geodetic/geographic CRS ?
659
543
    auto geodCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(thisAsCRS);
660
543
    auto geogCRS = extractGeographicCRS();
661
543
    auto hubCRS = util::nn_static_pointer_cast<CRS>(GeographicCRS::EPSG_4326);
662
543
    if (geodCRS && !geogCRS) {
663
3
        if (geodCRS->datumNonNull(dbContext)->nameStr() != "unknown" &&
664
3
            geodCRS->_isEquivalentTo(GeographicCRS::EPSG_4978.get(),
665
3
                                     util::IComparable::Criterion::EQUIVALENT,
666
3
                                     dbContext)) {
667
0
            return thisAsCRS;
668
0
        }
669
3
        hubCRS = util::nn_static_pointer_cast<CRS>(GeodeticCRS::EPSG_4978);
670
540
    } else if (!geogCRS ||
671
540
               (geogCRS->datumNonNull(dbContext)->nameStr() != "unknown" &&
672
540
                geogCRS->_isEquivalentTo(
673
540
                    GeographicCRS::EPSG_4326.get(),
674
540
                    util::IComparable::Criterion::EQUIVALENT, dbContext))) {
675
15
        return thisAsCRS;
676
525
    } else {
677
525
        geodCRS = geogCRS;
678
525
    }
679
680
1.32k
    for (const auto &authority : authorities) {
681
1.32k
        try {
682
683
1.32k
            auto authFactory = io::AuthorityFactory::create(
684
1.32k
                NN_NO_CHECK(dbContext),
685
1.32k
                authority == "any" ? std::string() : authority);
686
1.32k
            metadata::ExtentPtr extentResolved(extent);
687
1.32k
            if (!extent) {
688
12
                getResolvedCRS(thisAsCRS, authFactory, extentResolved);
689
12
            }
690
1.32k
            auto ctxt = operation::CoordinateOperationContext::create(
691
1.32k
                authFactory, extentResolved, 0.0);
692
1.32k
            ctxt->setAllowUseIntermediateCRS(allowIntermediateCRSUse);
693
            // ctxt->setSpatialCriterion(
694
            //    operation::CoordinateOperationContext::SpatialCriterion::PARTIAL_INTERSECTION);
695
1.32k
            auto list =
696
1.32k
                operation::CoordinateOperationFactory::create()
697
1.32k
                    ->createOperations(NN_NO_CHECK(geodCRS), hubCRS, ctxt);
698
1.32k
            CRSPtr candidateBoundCRS;
699
1.32k
            int candidateCount = 0;
700
701
1.32k
            const auto takeIntoAccountCandidate =
702
1.32k
                [&](const operation::TransformationNNPtr &transf) {
703
782
                    if (transf->getTOWGS84Parameters(false).empty()) {
704
54
                        return;
705
54
                    }
706
707
728
                    candidateCount++;
708
728
                    if (candidateBoundCRS == nullptr) {
709
484
                        candidateCount = 1;
710
484
                        candidateBoundCRS =
711
484
                            BoundCRS::create(thisAsCRS, hubCRS, transf)
712
484
                                .as_nullable();
713
484
                    }
714
728
                };
715
716
1.59k
            for (const auto &op : list) {
717
1.59k
                auto transf =
718
1.59k
                    util::nn_dynamic_pointer_cast<operation::Transformation>(
719
1.59k
                        op);
720
1.59k
                if (transf && !starts_with(transf->nameStr(), "Ballpark geo")) {
721
760
                    takeIntoAccountCandidate(NN_NO_CHECK(transf));
722
836
                } else {
723
836
                    auto concatenated =
724
836
                        dynamic_cast<const operation::ConcatenatedOperation *>(
725
836
                            op.get());
726
836
                    if (concatenated) {
727
                        // Case for EPSG:4807 / "NTF (Paris)" that is made of a
728
                        // longitude rotation followed by a Helmert
729
                        // The prime meridian shift will be accounted elsewhere
730
50
                        const auto &subops = concatenated->operations();
731
50
                        if (subops.size() == 2) {
732
49
                            auto firstOpIsTransformation =
733
49
                                dynamic_cast<const operation::Transformation *>(
734
49
                                    subops[0].get());
735
49
                            auto firstOpIsConversion =
736
49
                                dynamic_cast<const operation::Conversion *>(
737
49
                                    subops[0].get());
738
49
                            if ((firstOpIsTransformation &&
739
49
                                 firstOpIsTransformation
740
49
                                     ->isLongitudeRotation()) ||
741
2
                                (dynamic_cast<DerivedCRS *>(thisAsCRS.get()) &&
742
47
                                 firstOpIsConversion)) {
743
47
                                transf = util::nn_dynamic_pointer_cast<
744
47
                                    operation::Transformation>(subops[1]);
745
47
                                if (transf && !starts_with(transf->nameStr(),
746
47
                                                           "Ballpark geo")) {
747
22
                                    takeIntoAccountCandidate(
748
22
                                        NN_NO_CHECK(transf));
749
22
                                }
750
47
                            }
751
49
                        }
752
50
                    }
753
836
                }
754
1.59k
            }
755
1.32k
            if (candidateCount == 1 && candidateBoundCRS) {
756
264
                return NN_NO_CHECK(candidateBoundCRS);
757
264
            }
758
1.32k
        } catch (const std::exception &) {
759
0
        }
760
1.32k
    }
761
264
    return thisAsCRS;
762
528
}
763
764
// ---------------------------------------------------------------------------
765
766
/** \brief Returns a CRS whose coordinate system does not contain a vertical
767
 * component.
768
 *
769
 * As of PROJ 9.5, this method is an alias of demoteTo2D(std::string(),
770
 * nullptr), which deals with all potential CRS types.
771
 *
772
 * demoteTo2D() is a preferred alternative, especially when invoked with a
773
 * non-null database context, to perform a look-up in the database for
774
 * already registered 2D CRS.
775
 *
776
 * @return a CRS.
777
 */
778
0
CRSNNPtr CRS::stripVerticalComponent() const {
779
0
    return demoteTo2D(std::string(), nullptr);
780
0
}
781
782
// ---------------------------------------------------------------------------
783
784
//! @cond Doxygen_Suppress
785
786
/** \brief Return a shallow clone of this object. */
787
32.9k
CRSNNPtr CRS::shallowClone() const { return _shallowClone(); }
788
789
//! @endcond
790
791
// ---------------------------------------------------------------------------
792
793
//! @cond Doxygen_Suppress
794
795
1.48k
CRSNNPtr CRS::allowNonConformantWKT1Export() const {
796
1.48k
    const auto boundCRS = dynamic_cast<const BoundCRS *>(this);
797
1.48k
    if (boundCRS) {
798
30
        return BoundCRS::create(
799
30
            boundCRS->baseCRS()->allowNonConformantWKT1Export(),
800
30
            boundCRS->hubCRS(), boundCRS->transformation());
801
30
    }
802
1.45k
    auto crs(shallowClone());
803
1.45k
    crs->d->allowNonConformantWKT1Export_ = true;
804
1.45k
    return crs;
805
1.48k
}
806
807
//! @endcond
808
809
// ---------------------------------------------------------------------------
810
811
//! @cond Doxygen_Suppress
812
813
CRSNNPtr
814
2.98k
CRS::attachOriginalCompoundCRS(const CompoundCRSNNPtr &compoundCRS) const {
815
816
2.98k
    const auto boundCRS = dynamic_cast<const BoundCRS *>(this);
817
2.98k
    if (boundCRS) {
818
272
        return BoundCRS::create(
819
272
            boundCRS->baseCRS()->attachOriginalCompoundCRS(compoundCRS),
820
272
            boundCRS->hubCRS(), boundCRS->transformation());
821
272
    }
822
823
2.71k
    auto crs(shallowClone());
824
2.71k
    crs->d->originalCompoundCRS_ = compoundCRS.as_nullable();
825
2.71k
    return crs;
826
2.98k
}
827
828
//! @endcond
829
830
// ---------------------------------------------------------------------------
831
832
//! @cond Doxygen_Suppress
833
834
2.12k
CRSNNPtr CRS::alterName(const std::string &newName) const {
835
2.12k
    auto crs = shallowClone();
836
2.12k
    auto newNameMod(newName);
837
2.12k
    auto props = util::PropertyMap();
838
2.12k
    if (ends_with(newNameMod, " (deprecated)")) {
839
3
        newNameMod.resize(newNameMod.size() - strlen(" (deprecated)"));
840
3
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
841
3
    }
842
2.12k
    props.set(common::IdentifiedObject::NAME_KEY, newNameMod);
843
2.12k
    crs->setProperties(props);
844
2.12k
    return crs;
845
2.12k
}
846
847
//! @endcond
848
849
// ---------------------------------------------------------------------------
850
851
//! @cond Doxygen_Suppress
852
853
CRSNNPtr CRS::alterId(const std::string &authName,
854
26.6k
                      const std::string &code) const {
855
26.6k
    auto crs = shallowClone();
856
26.6k
    auto props = util::PropertyMap();
857
26.6k
    props.set(metadata::Identifier::CODESPACE_KEY, authName)
858
26.6k
        .set(metadata::Identifier::CODE_KEY, code);
859
26.6k
    crs->setProperties(props);
860
26.6k
    return crs;
861
26.6k
}
862
863
//! @endcond
864
865
// ---------------------------------------------------------------------------
866
867
//! @cond Doxygen_Suppress
868
869
static bool mustAxisOrderBeSwitchedForVisualizationInternal(
870
549k
    const std::vector<cs::CoordinateSystemAxisNNPtr> &axisList) {
871
549k
    const auto &dir0 = axisList[0]->direction();
872
549k
    const auto &dir1 = axisList[1]->direction();
873
549k
    if (&dir0 == &cs::AxisDirection::NORTH &&
874
542k
        &dir1 == &cs::AxisDirection::EAST) {
875
542k
        return true;
876
542k
    }
877
878
    // Address EPSG:32661 "WGS 84 / UPS North (N,E)"
879
6.34k
    if (&dir0 == &cs::AxisDirection::SOUTH &&
880
0
        &dir1 == &cs::AxisDirection::SOUTH) {
881
0
        const auto &meridian0 = axisList[0]->meridian();
882
0
        const auto &meridian1 = axisList[1]->meridian();
883
0
        return meridian0 != nullptr && meridian1 != nullptr &&
884
0
               std::abs(meridian0->longitude().convertToUnit(
885
0
                            common::UnitOfMeasure::DEGREE) -
886
0
                        180.0) < 1e-10 &&
887
0
               std::abs(meridian1->longitude().convertToUnit(
888
0
                            common::UnitOfMeasure::DEGREE) -
889
0
                        90.0) < 1e-10;
890
0
    }
891
892
6.34k
    if (&dir0 == &cs::AxisDirection::NORTH &&
893
0
        &dir1 == &cs::AxisDirection::NORTH) {
894
0
        const auto &meridian0 = axisList[0]->meridian();
895
0
        const auto &meridian1 = axisList[1]->meridian();
896
0
        return meridian0 != nullptr && meridian1 != nullptr &&
897
0
               ((
898
                    // Address EPSG:32761 "WGS 84 / UPS South (N,E)"
899
0
                    std::abs(meridian0->longitude().convertToUnit(
900
0
                                 common::UnitOfMeasure::DEGREE) -
901
0
                             0.0) < 1e-10 &&
902
0
                    std::abs(meridian1->longitude().convertToUnit(
903
0
                                 common::UnitOfMeasure::DEGREE) -
904
0
                             90.0) < 1e-10) ||
905
                // Address EPSG:5482 "RSRGD2000 / RSPS2000"
906
0
                (std::abs(meridian0->longitude().convertToUnit(
907
0
                              common::UnitOfMeasure::DEGREE) -
908
0
                          180) < 1e-10 &&
909
0
                 std::abs(meridian1->longitude().convertToUnit(
910
0
                              common::UnitOfMeasure::DEGREE) -
911
0
                          -90.0) < 1e-10));
912
0
    }
913
914
6.34k
    return false;
915
6.34k
}
916
// ---------------------------------------------------------------------------
917
918
281k
bool CRS::mustAxisOrderBeSwitchedForVisualization() const {
919
920
281k
    if (const CompoundCRS *compoundCRS =
921
281k
            dynamic_cast<const CompoundCRS *>(this)) {
922
0
        const auto &comps = compoundCRS->componentReferenceSystems();
923
0
        if (!comps.empty()) {
924
0
            return comps[0]->mustAxisOrderBeSwitchedForVisualization();
925
0
        }
926
0
    }
927
928
281k
    if (const GeographicCRS *geogCRS =
929
281k
            dynamic_cast<const GeographicCRS *>(this)) {
930
277k
        return mustAxisOrderBeSwitchedForVisualizationInternal(
931
277k
            geogCRS->coordinateSystem()->axisList());
932
277k
    }
933
934
3.74k
    if (const ProjectedCRS *projCRS =
935
3.74k
            dynamic_cast<const ProjectedCRS *>(this)) {
936
0
        return mustAxisOrderBeSwitchedForVisualizationInternal(
937
0
            projCRS->coordinateSystem()->axisList());
938
0
    }
939
940
3.74k
    if (const DerivedProjectedCRS *derivedProjCRS =
941
3.74k
            dynamic_cast<const DerivedProjectedCRS *>(this)) {
942
0
        return mustAxisOrderBeSwitchedForVisualizationInternal(
943
0
            derivedProjCRS->coordinateSystem()->axisList());
944
0
    }
945
946
3.74k
    return false;
947
3.74k
}
948
949
//! @endcond
950
951
// ---------------------------------------------------------------------------
952
953
//! @cond Doxygen_Suppress
954
955
void CRS::setProperties(
956
    const util::PropertyMap &properties) // throw(InvalidValueTypeException)
957
6.32M
{
958
6.32M
    std::string l_remarks;
959
6.32M
    std::string extensionProj4;
960
6.32M
    properties.getStringValue(IdentifiedObject::REMARKS_KEY, l_remarks);
961
6.32M
    properties.getStringValue("EXTENSION_PROJ4", extensionProj4);
962
963
6.32M
    const char *PROJ_CRS_STRING_PREFIX = "PROJ CRS string: ";
964
6.32M
    const char *PROJ_CRS_STRING_SUFFIX = ". ";
965
6.32M
    const auto beginOfProjStringPos = l_remarks.find(PROJ_CRS_STRING_PREFIX);
966
6.32M
    if (beginOfProjStringPos == std::string::npos && extensionProj4.empty()) {
967
6.08M
        ObjectUsage::setProperties(properties);
968
6.08M
        return;
969
6.08M
    }
970
971
243k
    util::PropertyMap newProperties(properties);
972
973
    // Parse remarks and extract EXTENSION_PROJ4 from it
974
243k
    if (extensionProj4.empty()) {
975
1.88k
        if (beginOfProjStringPos != std::string::npos) {
976
1.88k
            const auto endOfProjStringPos =
977
1.88k
                l_remarks.find(PROJ_CRS_STRING_SUFFIX, beginOfProjStringPos);
978
1.88k
            if (endOfProjStringPos == std::string::npos) {
979
1.65k
                extensionProj4 = l_remarks.substr(
980
1.65k
                    beginOfProjStringPos + strlen(PROJ_CRS_STRING_PREFIX));
981
1.65k
            } else {
982
235
                extensionProj4 = l_remarks.substr(
983
235
                    beginOfProjStringPos + strlen(PROJ_CRS_STRING_PREFIX),
984
235
                    endOfProjStringPos - beginOfProjStringPos -
985
235
                        strlen(PROJ_CRS_STRING_PREFIX));
986
235
            }
987
1.88k
        }
988
1.88k
    }
989
990
243k
    if (!extensionProj4.empty()) {
991
243k
        if (beginOfProjStringPos == std::string::npos) {
992
            // Add EXTENSION_PROJ4 to remarks
993
241k
            l_remarks =
994
241k
                PROJ_CRS_STRING_PREFIX + extensionProj4 +
995
241k
                (l_remarks.empty() ? std::string()
996
241k
                                   : PROJ_CRS_STRING_SUFFIX + l_remarks);
997
241k
        }
998
243k
    }
999
1000
243k
    newProperties.set(IdentifiedObject::REMARKS_KEY, l_remarks);
1001
1002
243k
    ObjectUsage::setProperties(newProperties);
1003
1004
243k
    d->extensionProj4_ = std::move(extensionProj4);
1005
243k
}
1006
1007
//! @endcond
1008
1009
// ---------------------------------------------------------------------------
1010
1011
//! @cond Doxygen_Suppress
1012
1013
// ---------------------------------------------------------------------------
1014
1015
271k
CRSNNPtr CRS::applyAxisOrderReversal(const char *nameSuffix) const {
1016
1017
271k
    const auto createProperties =
1018
271k
        [this, nameSuffix](const std::string &newNameIn = std::string()) {
1019
271k
            std::string newName(newNameIn);
1020
271k
            if (newName.empty()) {
1021
271k
                newName = nameStr();
1022
271k
                if (ends_with(newName, NORMALIZED_AXIS_ORDER_SUFFIX_STR)) {
1023
0
                    newName.resize(newName.size() -
1024
0
                                   strlen(NORMALIZED_AXIS_ORDER_SUFFIX_STR));
1025
271k
                } else if (ends_with(newName, AXIS_ORDER_REVERSED_SUFFIX_STR)) {
1026
0
                    newName.resize(newName.size() -
1027
0
                                   strlen(AXIS_ORDER_REVERSED_SUFFIX_STR));
1028
271k
                } else {
1029
271k
                    newName += nameSuffix;
1030
271k
                }
1031
271k
            }
1032
271k
            auto props = util::PropertyMap().set(
1033
271k
                common::IdentifiedObject::NAME_KEY, newName);
1034
271k
            const auto &l_domains = domains();
1035
271k
            if (!l_domains.empty()) {
1036
267k
                auto array = util::ArrayOfBaseObject::create();
1037
267k
                for (const auto &domain : l_domains) {
1038
267k
                    array->add(domain);
1039
267k
                }
1040
267k
                if (!array->empty()) {
1041
267k
                    props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, array);
1042
267k
                }
1043
267k
            }
1044
271k
            const auto &l_identifiers = identifiers();
1045
271k
            const auto &l_remarks = remarks();
1046
271k
            if (l_identifiers.size() == 1) {
1047
270k
                std::string remarks("Axis order reversed compared to ");
1048
270k
                if (!starts_with(l_remarks, remarks)) {
1049
270k
                    remarks += *(l_identifiers[0]->codeSpace());
1050
270k
                    remarks += ':';
1051
270k
                    remarks += l_identifiers[0]->code();
1052
270k
                    if (!l_remarks.empty()) {
1053
79.0k
                        remarks += ". ";
1054
79.0k
                        remarks += l_remarks;
1055
79.0k
                    }
1056
270k
                    props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
1057
270k
                }
1058
270k
            } else if (!l_remarks.empty()) {
1059
0
                props.set(common::IdentifiedObject::REMARKS_KEY, l_remarks);
1060
0
            }
1061
271k
            return props;
1062
271k
        };
1063
1064
271k
    if (const CompoundCRS *compoundCRS =
1065
271k
            dynamic_cast<const CompoundCRS *>(this)) {
1066
0
        const auto &comps = compoundCRS->componentReferenceSystems();
1067
0
        if (!comps.empty()) {
1068
0
            std::vector<CRSNNPtr> newComps;
1069
0
            newComps.emplace_back(comps[0]->applyAxisOrderReversal(nameSuffix));
1070
0
            std::string l_name = newComps.back()->nameStr();
1071
0
            for (size_t i = 1; i < comps.size(); i++) {
1072
0
                newComps.emplace_back(comps[i]);
1073
0
                l_name += " + ";
1074
0
                l_name += newComps.back()->nameStr();
1075
0
            }
1076
0
            return util::nn_static_pointer_cast<CRS>(
1077
0
                CompoundCRS::create(createProperties(l_name), newComps));
1078
0
        }
1079
0
    }
1080
1081
271k
    if (const GeographicCRS *geogCRS =
1082
271k
            dynamic_cast<const GeographicCRS *>(this)) {
1083
271k
        const auto &axisList = geogCRS->coordinateSystem()->axisList();
1084
271k
        auto cs =
1085
271k
            axisList.size() == 2
1086
271k
                ? cs::EllipsoidalCS::create(util::PropertyMap(), axisList[1],
1087
267k
                                            axisList[0])
1088
271k
                : cs::EllipsoidalCS::create(util::PropertyMap(), axisList[1],
1089
3.85k
                                            axisList[0], axisList[2]);
1090
271k
        return util::nn_static_pointer_cast<CRS>(
1091
271k
            GeographicCRS::create(createProperties(), geogCRS->datum(),
1092
271k
                                  geogCRS->datumEnsemble(), cs));
1093
271k
    }
1094
1095
64
    if (const ProjectedCRS *projCRS =
1096
64
            dynamic_cast<const ProjectedCRS *>(this)) {
1097
64
        const auto &axisList = projCRS->coordinateSystem()->axisList();
1098
64
        auto cs =
1099
64
            axisList.size() == 2
1100
64
                ? cs::CartesianCS::create(util::PropertyMap(), axisList[1],
1101
64
                                          axisList[0])
1102
64
                : cs::CartesianCS::create(util::PropertyMap(), axisList[1],
1103
0
                                          axisList[0], axisList[2]);
1104
64
        return util::nn_static_pointer_cast<CRS>(
1105
64
            ProjectedCRS::create(createProperties(), projCRS->baseCRS(),
1106
64
                                 projCRS->derivingConversion(), cs));
1107
64
    }
1108
1109
0
    if (const DerivedProjectedCRS *derivedProjCRS =
1110
0
            dynamic_cast<const DerivedProjectedCRS *>(this)) {
1111
0
        const auto &axisList = derivedProjCRS->coordinateSystem()->axisList();
1112
0
        auto cs =
1113
0
            axisList.size() == 2
1114
0
                ? cs::CartesianCS::create(util::PropertyMap(), axisList[1],
1115
0
                                          axisList[0])
1116
0
                : cs::CartesianCS::create(util::PropertyMap(), axisList[1],
1117
0
                                          axisList[0], axisList[2]);
1118
0
        return util::nn_static_pointer_cast<CRS>(DerivedProjectedCRS::create(
1119
0
            createProperties(), derivedProjCRS->baseCRS(),
1120
0
            derivedProjCRS->derivingConversion(), cs));
1121
0
    }
1122
1123
0
    throw util::UnsupportedOperationException(
1124
0
        "axis order reversal not supported on this type of CRS");
1125
0
}
1126
1127
// ---------------------------------------------------------------------------
1128
1129
271k
CRSNNPtr CRS::normalizeForVisualization() const {
1130
1131
271k
    if (const CompoundCRS *compoundCRS =
1132
271k
            dynamic_cast<const CompoundCRS *>(this)) {
1133
0
        const auto &comps = compoundCRS->componentReferenceSystems();
1134
0
        if (!comps.empty() &&
1135
0
            comps[0]->mustAxisOrderBeSwitchedForVisualization()) {
1136
0
            return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR);
1137
0
        }
1138
0
    }
1139
1140
271k
    if (const GeographicCRS *geogCRS =
1141
271k
            dynamic_cast<const GeographicCRS *>(this)) {
1142
271k
        const auto &axisList = geogCRS->coordinateSystem()->axisList();
1143
271k
        if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) {
1144
271k
            return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR);
1145
271k
        }
1146
271k
    }
1147
1148
80
    if (const ProjectedCRS *projCRS =
1149
80
            dynamic_cast<const ProjectedCRS *>(this)) {
1150
64
        const auto &axisList = projCRS->coordinateSystem()->axisList();
1151
64
        if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) {
1152
64
            return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR);
1153
64
        }
1154
64
    }
1155
1156
16
    if (const DerivedProjectedCRS *derivedProjCRS =
1157
16
            dynamic_cast<const DerivedProjectedCRS *>(this)) {
1158
0
        const auto &axisList = derivedProjCRS->coordinateSystem()->axisList();
1159
0
        if (mustAxisOrderBeSwitchedForVisualizationInternal(axisList)) {
1160
0
            return applyAxisOrderReversal(NORMALIZED_AXIS_ORDER_SUFFIX_STR);
1161
0
        }
1162
0
    }
1163
1164
16
    if (const BoundCRS *boundCRS = dynamic_cast<const BoundCRS *>(this)) {
1165
16
        auto baseNormCRS = boundCRS->baseCRS()->normalizeForVisualization();
1166
16
        return BoundCRS::create(baseNormCRS, boundCRS->hubCRS(),
1167
16
                                boundCRS->transformation());
1168
16
    }
1169
1170
0
    return NN_NO_CHECK(
1171
16
        std::static_pointer_cast<CRS>(shared_from_this().as_nullable()));
1172
16
}
1173
1174
//! @endcond
1175
1176
// ---------------------------------------------------------------------------
1177
1178
/** \brief Identify the CRS with reference CRSs.
1179
 *
1180
 * The candidate CRSs are either hard-coded, or looked in the database when
1181
 * authorityFactory is not null.
1182
 *
1183
 * Note that the implementation uses a set of heuristics to have a good
1184
 * compromise of successful identifications over execution time. It might miss
1185
 * legitimate matches in some circumstances.
1186
 *
1187
 * The method returns a list of matching reference CRS, and the percentage
1188
 * (0-100) of confidence in the match. The list is sorted by decreasing
1189
 * confidence.
1190
 * <ul>
1191
 * <li>100% means that the name of the reference entry
1192
 * perfectly matches the CRS name, and both are equivalent. In which case a
1193
 * single result is returned.
1194
 * Note: in the case of a GeographicCRS whose axis
1195
 * order is implicit in the input definition (for example ESRI WKT), then axis
1196
 * order is ignored for the purpose of identification. That is the CRS built
1197
 * from
1198
 * GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],
1199
 * PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]
1200
 * will be identified to EPSG:4326, but will not pass a
1201
 * isEquivalentTo(EPSG_4326, util::IComparable::Criterion::EQUIVALENT) test,
1202
 * but rather isEquivalentTo(EPSG_4326,
1203
 * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)
1204
 * </li>
1205
 * <li>90% means that CRS are equivalent, but the names are not exactly the
1206
 * same.</li>
1207
 * <li>70% means that CRS are equivalent), but the names do not match at
1208
 * all.</li>
1209
 * <li>25% means that the CRS are not equivalent, but there is some similarity
1210
 * in
1211
 * the names.</li>
1212
 * </ul>
1213
 * Other confidence values may be returned by some specialized implementations.
1214
 *
1215
 * This is implemented for GeodeticCRS, ProjectedCRS, VerticalCRS and
1216
 * CompoundCRS.
1217
 *
1218
 * @param authorityFactory Authority factory (or null, but degraded
1219
 * functionality)
1220
 * @return a list of matching reference CRS, and the percentage (0-100) of
1221
 * confidence in the match.
1222
 */
1223
std::list<std::pair<CRSNNPtr, int>>
1224
87.9k
CRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
1225
87.9k
    return _identify(authorityFactory);
1226
87.9k
}
1227
1228
// ---------------------------------------------------------------------------
1229
1230
/** \brief Return CRSs that are non-deprecated substitutes for the current CRS.
1231
 */
1232
std::list<CRSNNPtr>
1233
1.59k
CRS::getNonDeprecated(const io::DatabaseContextNNPtr &dbContext) const {
1234
1.59k
    std::list<CRSNNPtr> res;
1235
1.59k
    const auto &l_identifiers = identifiers();
1236
1.59k
    if (l_identifiers.empty()) {
1237
0
        return res;
1238
0
    }
1239
1.59k
    const char *tableName = nullptr;
1240
1.59k
    if (dynamic_cast<const GeodeticCRS *>(this)) {
1241
780
        tableName = "geodetic_crs";
1242
815
    } else if (dynamic_cast<const ProjectedCRS *>(this)) {
1243
699
        tableName = "projected_crs";
1244
699
    } else if (dynamic_cast<const VerticalCRS *>(this)) {
1245
10
        tableName = "vertical_crs";
1246
106
    } else if (dynamic_cast<const CompoundCRS *>(this)) {
1247
106
        tableName = "compound_crs";
1248
106
    }
1249
1.59k
    if (!tableName) {
1250
0
        return res;
1251
0
    }
1252
1.59k
    const auto &id = l_identifiers[0];
1253
1.59k
    auto tmpRes =
1254
1.59k
        dbContext->getNonDeprecated(tableName, *(id->codeSpace()), id->code());
1255
1.59k
    for (const auto &pair : tmpRes) {
1256
1.55k
        res.emplace_back(io::AuthorityFactory::create(dbContext, pair.first)
1257
1.55k
                             ->createCoordinateReferenceSystem(pair.second));
1258
1.55k
    }
1259
1.59k
    return res;
1260
1.59k
}
1261
1262
// ---------------------------------------------------------------------------
1263
1264
//! @cond Doxygen_Suppress
1265
1266
/** \brief Return the authority name to which this object is registered, or
1267
 * has an indirect provenance.
1268
 *
1269
 * Typically this method called on EPSG:4269 (NAD83) promoted to 3D will return
1270
 * "EPSG".
1271
 *
1272
 * Returns empty string if more than an authority or no originating authority is
1273
 * found.
1274
 */
1275
22.2k
std::string CRS::getOriginatingAuthName() const {
1276
22.2k
    const auto &ids = identifiers();
1277
22.2k
    if (ids.size() == 1) {
1278
28
        return *(ids[0]->codeSpace());
1279
28
    }
1280
22.1k
    if (ids.size() > 1) {
1281
0
        return std::string();
1282
0
    }
1283
22.1k
    const auto &l_remarks = remarks();
1284
22.1k
    if (starts_with(l_remarks, PROMOTED_TO_3D_PRELUDE)) {
1285
273
        const auto pos = l_remarks.find(':');
1286
273
        if (pos != std::string::npos) {
1287
273
            return l_remarks.substr(strlen(PROMOTED_TO_3D_PRELUDE),
1288
273
                                    pos - strlen(PROMOTED_TO_3D_PRELUDE));
1289
273
        }
1290
273
    }
1291
21.9k
    return std::string();
1292
22.1k
}
1293
1294
//! @endcond
1295
1296
// ---------------------------------------------------------------------------
1297
1298
/** \brief Return a variant of this CRS "promoted" to a 3D one, if not already
1299
 * the case.
1300
 *
1301
 * The new axis will be ellipsoidal height, oriented upwards, and with the same
1302
 * unit as the existing coordinate system for a CartesianCS, or otherwise
1303
 * with metre units
1304
 *
1305
 * @param newName Name of the new CRS. If empty, nameStr() will be used.
1306
 * @param dbContext Database context to look for potentially already registered
1307
 *                  3D CRS. May be nullptr.
1308
 * @return a new CRS promoted to 3D, or the current one if already 3D or not
1309
 * applicable.
1310
 * @since 6.3
1311
 */
1312
CRSNNPtr CRS::promoteTo3D(const std::string &newName,
1313
10.8k
                          const io::DatabaseContextPtr &dbContext) const {
1314
1315
10.8k
    common::UnitOfMeasure uom = common::UnitOfMeasure::METRE;
1316
10.8k
    if (auto projCRS = dynamic_cast<const ProjectedCRS *>(this)) {
1317
287
        const auto &axisList = projCRS->coordinateSystem()->axisList();
1318
287
        assert(!axisList.empty());
1319
287
        uom = axisList[0]->unit();
1320
10.6k
    } else if (auto derivedProjCRS =
1321
10.6k
                   dynamic_cast<const DerivedProjectedCRS *>(this)) {
1322
0
        const auto &cs = derivedProjCRS->coordinateSystem();
1323
0
        if (dynamic_cast<const cs::CartesianCS *>(cs.get())) {
1324
0
            const auto &axisList = cs->axisList();
1325
0
            assert(!axisList.empty());
1326
0
            uom = axisList[0]->unit();
1327
0
        }
1328
0
    }
1329
1330
10.8k
    auto upAxis = cs::CoordinateSystemAxis::create(
1331
10.8k
        util::PropertyMap().set(IdentifiedObject::NAME_KEY,
1332
10.8k
                                cs::AxisName::Ellipsoidal_height),
1333
10.8k
        cs::AxisAbbreviation::h, cs::AxisDirection::UP, uom);
1334
10.8k
    return promoteTo3D(newName, dbContext, upAxis);
1335
10.8k
}
1336
1337
// ---------------------------------------------------------------------------
1338
1339
//! @cond Doxygen_Suppress
1340
1341
CRSNNPtr CRS::promoteTo3D(const std::string &newName,
1342
                          const io::DatabaseContextPtr &dbContext,
1343
                          const cs::CoordinateSystemAxisNNPtr
1344
13.9k
                              &verticalAxisIfNotAlreadyPresent) const {
1345
1346
13.9k
    const auto createProperties = [this, &newName]() {
1347
9.93k
        auto props =
1348
9.93k
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
1349
9.93k
                                    !newName.empty() ? newName : nameStr());
1350
9.93k
        const auto &l_domains = domains();
1351
9.93k
        if (!l_domains.empty()) {
1352
1.31k
            auto array = util::ArrayOfBaseObject::create();
1353
2.42k
            for (const auto &domain : l_domains) {
1354
2.42k
                auto extent = domain->domainOfValidity();
1355
2.42k
                if (extent) {
1356
                    // Propagate only the extent, not the scope, as it might
1357
                    // imply more that we can guarantee with the promotion to
1358
                    // 3D.
1359
1.77k
                    auto newDomain = common::ObjectDomain::create(
1360
1.77k
                        util::optional<std::string>(), extent);
1361
1.77k
                    array->add(newDomain);
1362
1.77k
                }
1363
2.42k
            }
1364
1.31k
            if (!array->empty()) {
1365
786
                props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, array);
1366
786
            }
1367
1.31k
        }
1368
9.93k
        const auto &l_identifiers = identifiers();
1369
9.93k
        const auto &l_remarks = remarks();
1370
9.93k
        if (l_identifiers.size() == 1) {
1371
2.25k
            std::string remarks(PROMOTED_TO_3D_PRELUDE);
1372
2.25k
            remarks += *(l_identifiers[0]->codeSpace());
1373
2.25k
            remarks += ':';
1374
2.25k
            remarks += l_identifiers[0]->code();
1375
2.25k
            if (!l_remarks.empty()) {
1376
358
                remarks += ". ";
1377
358
                remarks += l_remarks;
1378
358
            }
1379
2.25k
            props.set(common::IdentifiedObject::REMARKS_KEY, remarks);
1380
7.68k
        } else if (!l_remarks.empty()) {
1381
1.24k
            props.set(common::IdentifiedObject::REMARKS_KEY, l_remarks);
1382
1.24k
        }
1383
9.93k
        return props;
1384
9.93k
    };
1385
1386
13.9k
    if (auto derivedGeogCRS =
1387
13.9k
            dynamic_cast<const DerivedGeographicCRS *>(this)) {
1388
96
        const auto &axisList = derivedGeogCRS->coordinateSystem()->axisList();
1389
96
        if (axisList.size() == 2) {
1390
96
            auto cs = cs::EllipsoidalCS::create(
1391
96
                util::PropertyMap(), axisList[0], axisList[1],
1392
96
                verticalAxisIfNotAlreadyPresent);
1393
96
            auto baseGeog3DCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(
1394
96
                derivedGeogCRS->baseCRS()->promoteTo3D(std::string(),
1395
96
                                                       dbContext));
1396
96
            return util::nn_static_pointer_cast<CRS>(
1397
96
                DerivedGeographicCRS::create(
1398
96
                    createProperties(),
1399
96
                    NN_CHECK_THROW(std::move(baseGeog3DCRS)),
1400
96
                    derivedGeogCRS->derivingConversion(), std::move(cs)));
1401
96
        }
1402
96
    }
1403
1404
13.8k
    else if (auto derivedProjCRS =
1405
13.8k
                 dynamic_cast<const DerivedProjectedCRS *>(this)) {
1406
0
        const auto &axisList = derivedProjCRS->coordinateSystem()->axisList();
1407
0
        if (axisList.size() == 2) {
1408
0
            auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0],
1409
0
                                              axisList[1],
1410
0
                                              verticalAxisIfNotAlreadyPresent);
1411
0
            auto baseProj3DCRS = util::nn_dynamic_pointer_cast<ProjectedCRS>(
1412
0
                derivedProjCRS->baseCRS()->promoteTo3D(std::string(),
1413
0
                                                       dbContext));
1414
0
            return util::nn_static_pointer_cast<CRS>(
1415
0
                DerivedProjectedCRS::create(
1416
0
                    createProperties(),
1417
0
                    NN_CHECK_THROW(std::move(baseProj3DCRS)),
1418
0
                    derivedProjCRS->derivingConversion(), std::move(cs)));
1419
0
        }
1420
0
    }
1421
1422
13.8k
    else if (auto geogCRS = dynamic_cast<const GeographicCRS *>(this)) {
1423
11.4k
        const auto &axisList = geogCRS->coordinateSystem()->axisList();
1424
11.4k
        if (axisList.size() == 2) {
1425
11.0k
            const auto &l_identifiers = identifiers();
1426
            // First check if there is a Geographic 3D CRS in the database
1427
            // of the same name.
1428
            // This is the common practice in the EPSG dataset.
1429
11.0k
            if (dbContext && l_identifiers.size() == 1) {
1430
4.05k
                auto authFactory = io::AuthorityFactory::create(
1431
4.05k
                    NN_NO_CHECK(dbContext), *(l_identifiers[0]->codeSpace()));
1432
4.05k
                auto res = authFactory->createObjectsFromName(
1433
4.05k
                    nameStr(),
1434
4.05k
                    {io::AuthorityFactory::ObjectType::GEOGRAPHIC_3D_CRS},
1435
4.05k
                    false);
1436
4.05k
                if (!res.empty()) {
1437
2.07k
                    const auto &firstRes = res.front();
1438
2.07k
                    const auto firstResGeog =
1439
2.07k
                        dynamic_cast<GeographicCRS *>(firstRes.get());
1440
2.07k
                    const auto &firstResAxisList =
1441
2.07k
                        firstResGeog->coordinateSystem()->axisList();
1442
2.07k
                    if (firstResAxisList[2]->_isEquivalentTo(
1443
2.07k
                            verticalAxisIfNotAlreadyPresent.get(),
1444
2.07k
                            util::IComparable::Criterion::EQUIVALENT) &&
1445
2.07k
                        geogCRS->is2DPartOf3D(NN_NO_CHECK(firstResGeog),
1446
2.07k
                                              dbContext)) {
1447
2.06k
                        return NN_NO_CHECK(
1448
2.06k
                            util::nn_dynamic_pointer_cast<CRS>(firstRes));
1449
2.06k
                    }
1450
2.07k
                }
1451
4.05k
            }
1452
1453
8.95k
            auto cs = cs::EllipsoidalCS::create(
1454
8.95k
                util::PropertyMap(), axisList[0], axisList[1],
1455
8.95k
                verticalAxisIfNotAlreadyPresent);
1456
8.95k
            return util::nn_static_pointer_cast<CRS>(
1457
8.95k
                GeographicCRS::create(createProperties(), geogCRS->datum(),
1458
8.95k
                                      geogCRS->datumEnsemble(), std::move(cs)));
1459
11.0k
        }
1460
11.4k
    }
1461
1462
2.38k
    else if (auto projCRS = dynamic_cast<const ProjectedCRS *>(this)) {
1463
610
        const auto &axisList = projCRS->coordinateSystem()->axisList();
1464
610
        if (axisList.size() == 2) {
1465
609
            auto base3DCRS =
1466
609
                projCRS->baseCRS()->promoteTo3D(std::string(), dbContext);
1467
609
            auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0],
1468
609
                                              axisList[1],
1469
609
                                              verticalAxisIfNotAlreadyPresent);
1470
609
            return util::nn_static_pointer_cast<CRS>(ProjectedCRS::create(
1471
609
                createProperties(),
1472
609
                NN_NO_CHECK(
1473
609
                    util::nn_dynamic_pointer_cast<GeodeticCRS>(base3DCRS)),
1474
609
                projCRS->derivingConversion(), std::move(cs)));
1475
609
        }
1476
610
    }
1477
1478
1.77k
    else if (auto boundCRS = dynamic_cast<const BoundCRS *>(this)) {
1479
311
        auto base3DCRS = boundCRS->baseCRS()->promoteTo3D(
1480
311
            newName, dbContext, verticalAxisIfNotAlreadyPresent);
1481
311
        auto transf = boundCRS->transformation();
1482
311
        if (!transf->getTOWGS84Parameters(false).empty()) {
1483
302
            return BoundCRS::create(
1484
302
                createProperties(), base3DCRS,
1485
302
                boundCRS->hubCRS()->promoteTo3D(std::string(), dbContext),
1486
302
                transf->promoteTo3D(std::string(), dbContext));
1487
302
        } else {
1488
9
            return BoundCRS::create(base3DCRS, boundCRS->hubCRS(),
1489
9
                                    std::move(transf));
1490
9
        }
1491
311
    }
1492
1493
1.94k
    return NN_NO_CHECK(
1494
13.9k
        std::static_pointer_cast<CRS>(shared_from_this().as_nullable()));
1495
13.9k
}
1496
1497
//! @endcond
1498
1499
// ---------------------------------------------------------------------------
1500
1501
/** \brief Return a variant of this CRS "demoted" to a 2D one, if not already
1502
 * the case.
1503
 *
1504
 *
1505
 * @param newName Name of the new CRS. If empty, nameStr() will be used.
1506
 * @param dbContext Database context to look for potentially already registered
1507
 *                  2D CRS. May be nullptr.
1508
 * @return a new CRS demoted to 2D, or the current one if already 2D or not
1509
 * applicable.
1510
 * @since 6.3
1511
 */
1512
CRSNNPtr CRS::demoteTo2D(const std::string &newName,
1513
3.73k
                         const io::DatabaseContextPtr &dbContext) const {
1514
1515
3.73k
    if (auto derivedGeogCRS =
1516
3.73k
            dynamic_cast<const DerivedGeographicCRS *>(this)) {
1517
0
        return derivedGeogCRS->demoteTo2D(newName, dbContext);
1518
0
    }
1519
1520
3.73k
    else if (auto derivedProjCRS =
1521
3.73k
                 dynamic_cast<const DerivedProjectedCRS *>(this)) {
1522
0
        return derivedProjCRS->demoteTo2D(newName, dbContext);
1523
0
    }
1524
1525
3.73k
    else if (auto geogCRS = dynamic_cast<const GeographicCRS *>(this)) {
1526
2.46k
        return geogCRS->demoteTo2D(newName, dbContext);
1527
2.46k
    }
1528
1529
1.26k
    else if (auto projCRS = dynamic_cast<const ProjectedCRS *>(this)) {
1530
1.25k
        return projCRS->demoteTo2D(newName, dbContext);
1531
1.25k
    }
1532
1533
17
    else if (auto boundCRS = dynamic_cast<const BoundCRS *>(this)) {
1534
0
        auto base2DCRS = boundCRS->baseCRS()->demoteTo2D(newName, dbContext);
1535
0
        auto transf = boundCRS->transformation();
1536
0
        if (!transf->getTOWGS84Parameters(false).empty()) {
1537
0
            return BoundCRS::create(
1538
0
                base2DCRS,
1539
0
                boundCRS->hubCRS()->demoteTo2D(std::string(), dbContext),
1540
0
                transf->demoteTo2D(std::string(), dbContext));
1541
0
        } else {
1542
0
            return BoundCRS::create(base2DCRS, boundCRS->hubCRS(), transf);
1543
0
        }
1544
0
    }
1545
1546
17
    else if (auto compoundCRS = dynamic_cast<const CompoundCRS *>(this)) {
1547
17
        const auto &components = compoundCRS->componentReferenceSystems();
1548
17
        if (components.size() >= 2) {
1549
17
            return components[0];
1550
17
        }
1551
17
    }
1552
1553
0
    return NN_NO_CHECK(
1554
3.73k
        std::static_pointer_cast<CRS>(shared_from_this().as_nullable()));
1555
3.73k
}
1556
1557
// ---------------------------------------------------------------------------
1558
1559
//! @cond Doxygen_Suppress
1560
1561
std::list<std::pair<CRSNNPtr, int>>
1562
9.75k
CRS::_identify(const io::AuthorityFactoryPtr &) const {
1563
9.75k
    return {};
1564
9.75k
}
1565
1566
//! @endcond
1567
1568
// ---------------------------------------------------------------------------
1569
1570
//! @cond Doxygen_Suppress
1571
struct SingleCRS::Private {
1572
    datum::DatumPtr datum{};
1573
    datum::DatumEnsemblePtr datumEnsemble{};
1574
    cs::CoordinateSystemNNPtr coordinateSystem;
1575
1576
    Private(const datum::DatumPtr &datumIn,
1577
            const datum::DatumEnsemblePtr &datumEnsembleIn,
1578
            const cs::CoordinateSystemNNPtr &csIn)
1579
5.67M
        : datum(datumIn), datumEnsemble(datumEnsembleIn),
1580
5.67M
          coordinateSystem(csIn) {
1581
5.67M
        if ((datum ? 1 : 0) + (datumEnsemble ? 1 : 0) != 1) {
1582
6
            throw util::Exception("datum or datumEnsemble should be set");
1583
6
        }
1584
5.67M
    }
1585
};
1586
//! @endcond
1587
1588
// ---------------------------------------------------------------------------
1589
1590
SingleCRS::SingleCRS(const datum::DatumPtr &datumIn,
1591
                     const datum::DatumEnsemblePtr &datumEnsembleIn,
1592
                     const cs::CoordinateSystemNNPtr &csIn)
1593
5.67M
    : d(std::make_unique<Private>(datumIn, datumEnsembleIn, csIn)) {}
1594
1595
// ---------------------------------------------------------------------------
1596
1597
SingleCRS::SingleCRS(const SingleCRS &other)
1598
32.9k
    : CRS(other), d(std::make_unique<Private>(*other.d)) {}
1599
1600
// ---------------------------------------------------------------------------
1601
1602
//! @cond Doxygen_Suppress
1603
5.70M
SingleCRS::~SingleCRS() = default;
1604
//! @endcond
1605
1606
// ---------------------------------------------------------------------------
1607
1608
/** \brief Return the datum::Datum associated with the CRS.
1609
 *
1610
 * This might be null, in which case datumEnsemble() return will not be null.
1611
 *
1612
 * @return a Datum that might be null.
1613
 */
1614
116k
const datum::DatumPtr &SingleCRS::datum() PROJ_PURE_DEFN { return d->datum; }
1615
1616
// ---------------------------------------------------------------------------
1617
1618
/** \brief Return the datum::DatumEnsemble associated with the CRS.
1619
 *
1620
 * This might be null, in which case datum() return will not be null.
1621
 *
1622
 * @return a DatumEnsemble that might be null.
1623
 */
1624
5.14M
const datum::DatumEnsemblePtr &SingleCRS::datumEnsemble() PROJ_PURE_DEFN {
1625
5.14M
    return d->datumEnsemble;
1626
5.14M
}
1627
1628
// ---------------------------------------------------------------------------
1629
1630
//! @cond Doxygen_Suppress
1631
/** \brief Return the real datum or a synthesized one if a datumEnsemble.
1632
 */
1633
const datum::DatumNNPtr
1634
4.45M
SingleCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const {
1635
4.45M
    return d->datum ? NN_NO_CHECK(d->datum)
1636
4.45M
                    : d->datumEnsemble->asDatum(dbContext);
1637
4.45M
}
1638
//! @endcond
1639
1640
// ---------------------------------------------------------------------------
1641
1642
/** \brief Return the cs::CoordinateSystem associated with the CRS.
1643
 *
1644
 * @return a CoordinateSystem.
1645
 */
1646
33.1M
const cs::CoordinateSystemNNPtr &SingleCRS::coordinateSystem() PROJ_PURE_DEFN {
1647
33.1M
    return d->coordinateSystem;
1648
33.1M
}
1649
1650
// ---------------------------------------------------------------------------
1651
1652
bool SingleCRS::baseIsEquivalentTo(
1653
    const util::IComparable *other, util::IComparable::Criterion criterion,
1654
3.65M
    const io::DatabaseContextPtr &dbContext) const {
1655
3.65M
    auto otherSingleCRS = dynamic_cast<const SingleCRS *>(other);
1656
3.65M
    if (otherSingleCRS == nullptr ||
1657
3.65M
        (criterion == util::IComparable::Criterion::STRICT &&
1658
8.49k
         !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
1659
49
        return false;
1660
49
    }
1661
1662
    // Check datum
1663
3.65M
    const auto &thisDatum = d->datum;
1664
3.65M
    const auto &otherDatum = otherSingleCRS->d->datum;
1665
3.65M
    const auto &thisDatumEnsemble = d->datumEnsemble;
1666
3.65M
    const auto &otherDatumEnsemble = otherSingleCRS->d->datumEnsemble;
1667
3.65M
    if (thisDatum && otherDatum) {
1668
2.79M
        if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion,
1669
2.79M
                                        dbContext)) {
1670
1.44M
            return false;
1671
1.44M
        }
1672
2.79M
    } else if (thisDatumEnsemble && otherDatumEnsemble) {
1673
559k
        if (!thisDatumEnsemble->_isEquivalentTo(otherDatumEnsemble.get(),
1674
559k
                                                criterion, dbContext)) {
1675
174
            return false;
1676
174
        }
1677
559k
    }
1678
1679
2.21M
    if (criterion == util::IComparable::Criterion::STRICT) {
1680
8.29k
        if ((thisDatum != nullptr) ^ (otherDatum != nullptr)) {
1681
4
            return false;
1682
4
        }
1683
8.29k
        if ((thisDatumEnsemble != nullptr) ^ (otherDatumEnsemble != nullptr)) {
1684
0
            return false;
1685
0
        }
1686
2.20M
    } else {
1687
2.20M
        if (!datumNonNull(dbContext)->_isEquivalentTo(
1688
2.20M
                otherSingleCRS->datumNonNull(dbContext).get(), criterion,
1689
2.20M
                dbContext)) {
1690
127k
            return false;
1691
127k
        }
1692
2.20M
    }
1693
1694
    // Check coordinate system
1695
2.08M
    if (!(d->coordinateSystem->_isEquivalentTo(
1696
2.08M
            otherSingleCRS->d->coordinateSystem.get(), criterion, dbContext))) {
1697
115k
        return false;
1698
115k
    }
1699
1700
    // Now compare PROJ4 extensions
1701
1702
1.96M
    const auto &thisProj4 = getExtensionProj4();
1703
1.96M
    const auto &otherProj4 = otherSingleCRS->getExtensionProj4();
1704
1705
1.96M
    if (thisProj4.empty() && otherProj4.empty()) {
1706
1.95M
        return true;
1707
1.95M
    }
1708
1709
11.8k
    if (!(thisProj4.empty() ^ otherProj4.empty())) {
1710
17
        return true;
1711
17
    }
1712
1713
    // Asks for a "normalized" output during toString(), aimed at comparing two
1714
    // strings for equivalence.
1715
11.8k
    auto formatter1 = io::PROJStringFormatter::create();
1716
11.8k
    formatter1->setNormalizeOutput();
1717
11.8k
    formatter1->ingestPROJString(thisProj4);
1718
1719
11.8k
    auto formatter2 = io::PROJStringFormatter::create();
1720
11.8k
    formatter2->setNormalizeOutput();
1721
11.8k
    formatter2->ingestPROJString(otherProj4);
1722
1723
11.8k
    return formatter1->toString() == formatter2->toString();
1724
11.8k
}
1725
1726
// ---------------------------------------------------------------------------
1727
1728
//! @cond Doxygen_Suppress
1729
void SingleCRS::exportDatumOrDatumEnsembleToWkt(
1730
    io::WKTFormatter *formatter) const // throw(io::FormattingException)
1731
549k
{
1732
549k
    const auto &l_datum = d->datum;
1733
549k
    if (l_datum) {
1734
536k
        l_datum->_exportToWKT(formatter);
1735
536k
    } else {
1736
13.5k
        const auto &l_datumEnsemble = d->datumEnsemble;
1737
13.5k
        assert(l_datumEnsemble);
1738
13.5k
        l_datumEnsemble->_exportToWKT(formatter);
1739
13.5k
    }
1740
549k
}
1741
//! @endcond
1742
1743
// ---------------------------------------------------------------------------
1744
1745
//! @cond Doxygen_Suppress
1746
struct GeodeticCRS::Private {
1747
    std::vector<operation::PointMotionOperationNNPtr> velocityModel{};
1748
    datum::GeodeticReferenceFramePtr datum_;
1749
1750
    explicit Private(const datum::GeodeticReferenceFramePtr &datumIn)
1751
3.35M
        : datum_(datumIn) {}
1752
};
1753
1754
// ---------------------------------------------------------------------------
1755
1756
static const datum::DatumEnsemblePtr &
1757
checkEnsembleForGeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn,
1758
3.35M
                            const datum::DatumEnsemblePtr &ensemble) {
1759
3.35M
    const char *msg = "One of Datum or DatumEnsemble should be defined";
1760
3.35M
    if (datumIn) {
1761
3.13M
        if (!ensemble) {
1762
3.13M
            return ensemble;
1763
3.13M
        }
1764
7
        msg = "Datum and DatumEnsemble should not be defined";
1765
217k
    } else if (ensemble) {
1766
217k
        const auto &datums = ensemble->datums();
1767
217k
        assert(!datums.empty());
1768
217k
        auto grfFirst =
1769
217k
            dynamic_cast<datum::GeodeticReferenceFrame *>(datums[0].get());
1770
217k
        if (grfFirst) {
1771
217k
            return ensemble;
1772
217k
        }
1773
0
        msg = "Ensemble should contain GeodeticReferenceFrame";
1774
0
    }
1775
7
    throw util::Exception(msg);
1776
3.35M
}
1777
1778
//! @endcond
1779
1780
// ---------------------------------------------------------------------------
1781
1782
GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn,
1783
                         const datum::DatumEnsemblePtr &datumEnsembleIn,
1784
                         const cs::EllipsoidalCSNNPtr &csIn)
1785
3.13M
    : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn),
1786
3.13M
                csIn),
1787
3.13M
      d(std::make_unique<Private>(datumIn)) {}
osgeo::proj::crs::GeodeticCRS::GeodeticCRS(std::__1::shared_ptr<osgeo::proj::datum::GeodeticReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::EllipsoidalCS> > const&)
Line
Count
Source
1785
3.13M
    : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn),
1786
3.13M
                csIn),
1787
3.13M
      d(std::make_unique<Private>(datumIn)) {}
Unexecuted instantiation: osgeo::proj::crs::GeodeticCRS::GeodeticCRS(std::__1::shared_ptr<osgeo::proj::datum::GeodeticReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::EllipsoidalCS> > const&)
1788
1789
// ---------------------------------------------------------------------------
1790
1791
GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn,
1792
                         const datum::DatumEnsemblePtr &datumEnsembleIn,
1793
                         const cs::SphericalCSNNPtr &csIn)
1794
33.2k
    : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn),
1795
33.2k
                csIn),
1796
33.2k
      d(std::make_unique<Private>(datumIn)) {}
Unexecuted instantiation: osgeo::proj::crs::GeodeticCRS::GeodeticCRS(std::__1::shared_ptr<osgeo::proj::datum::GeodeticReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::SphericalCS> > const&)
osgeo::proj::crs::GeodeticCRS::GeodeticCRS(std::__1::shared_ptr<osgeo::proj::datum::GeodeticReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::SphericalCS> > const&)
Line
Count
Source
1794
33.2k
    : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn),
1795
33.2k
                csIn),
1796
33.2k
      d(std::make_unique<Private>(datumIn)) {}
1797
1798
// ---------------------------------------------------------------------------
1799
1800
GeodeticCRS::GeodeticCRS(const datum::GeodeticReferenceFramePtr &datumIn,
1801
                         const datum::DatumEnsemblePtr &datumEnsembleIn,
1802
                         const cs::CartesianCSNNPtr &csIn)
1803
188k
    : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn),
1804
188k
                csIn),
1805
188k
      d(std::make_unique<Private>(datumIn)) {}
Unexecuted instantiation: osgeo::proj::crs::GeodeticCRS::GeodeticCRS(std::__1::shared_ptr<osgeo::proj::datum::GeodeticReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CartesianCS> > const&)
osgeo::proj::crs::GeodeticCRS::GeodeticCRS(std::__1::shared_ptr<osgeo::proj::datum::GeodeticReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CartesianCS> > const&)
Line
Count
Source
1803
188k
    : SingleCRS(datumIn, checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn),
1804
188k
                csIn),
1805
188k
      d(std::make_unique<Private>(datumIn)) {}
1806
1807
// ---------------------------------------------------------------------------
1808
1809
GeodeticCRS::GeodeticCRS(const GeodeticCRS &other)
1810
18.3k
    : SingleCRS(other), d(std::make_unique<Private>(*other.d)) {}
osgeo::proj::crs::GeodeticCRS::GeodeticCRS(osgeo::proj::crs::GeodeticCRS const&)
Line
Count
Source
1810
18.3k
    : SingleCRS(other), d(std::make_unique<Private>(*other.d)) {}
Unexecuted instantiation: osgeo::proj::crs::GeodeticCRS::GeodeticCRS(osgeo::proj::crs::GeodeticCRS const&)
1811
1812
// ---------------------------------------------------------------------------
1813
1814
//! @cond Doxygen_Suppress
1815
3.37M
GeodeticCRS::~GeodeticCRS() = default;
1816
//! @endcond
1817
1818
// ---------------------------------------------------------------------------
1819
1820
0
CRSNNPtr GeodeticCRS::_shallowClone() const {
1821
0
    auto crs(GeodeticCRS::nn_make_shared<GeodeticCRS>(*this));
1822
0
    crs->assignSelf(crs);
1823
0
    return crs;
1824
0
}
1825
1826
// ---------------------------------------------------------------------------
1827
1828
/** \brief Return the datum::GeodeticReferenceFrame associated with the CRS.
1829
 *
1830
 * @return a GeodeticReferenceFrame or null (in which case datumEnsemble()
1831
 * should return a non-null pointer.)
1832
 */
1833
2.64M
const datum::GeodeticReferenceFramePtr &GeodeticCRS::datum() PROJ_PURE_DEFN {
1834
2.64M
    return d->datum_;
1835
2.64M
}
1836
1837
// ---------------------------------------------------------------------------
1838
1839
//! @cond Doxygen_Suppress
1840
/** \brief Return the real datum or a synthesized one if a datumEnsemble.
1841
 */
1842
const datum::GeodeticReferenceFrameNNPtr
1843
22.9M
GeodeticCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const {
1844
22.9M
    return NN_NO_CHECK(
1845
22.9M
        d->datum_
1846
22.9M
            ? d->datum_
1847
22.9M
            : util::nn_dynamic_pointer_cast<datum::GeodeticReferenceFrame>(
1848
22.9M
                  SingleCRS::getPrivate()->datumEnsemble->asDatum(dbContext)));
1849
22.9M
}
1850
//! @endcond
1851
1852
// ---------------------------------------------------------------------------
1853
1854
//! @cond Doxygen_Suppress
1855
2.42M
static datum::GeodeticReferenceFrame *oneDatum(const GeodeticCRS *crs) {
1856
2.42M
    const auto &l_datumEnsemble = crs->datumEnsemble();
1857
2.42M
    assert(l_datumEnsemble);
1858
2.42M
    const auto &l_datums = l_datumEnsemble->datums();
1859
2.42M
    return static_cast<datum::GeodeticReferenceFrame *>(l_datums[0].get());
1860
2.42M
}
1861
//! @endcond
1862
1863
// ---------------------------------------------------------------------------
1864
1865
/** \brief Return the PrimeMeridian associated with the GeodeticReferenceFrame
1866
 * or with one of the GeodeticReferenceFrame of the datumEnsemble().
1867
 *
1868
 * @return the PrimeMeridian.
1869
 */
1870
3.04M
const datum::PrimeMeridianNNPtr &GeodeticCRS::primeMeridian() PROJ_PURE_DEFN {
1871
3.04M
    if (d->datum_) {
1872
2.38M
        return d->datum_->primeMeridian();
1873
2.38M
    }
1874
658k
    return oneDatum(this)->primeMeridian();
1875
3.04M
}
1876
1877
// ---------------------------------------------------------------------------
1878
1879
/** \brief Return the ellipsoid associated with the GeodeticReferenceFrame
1880
 * or with one of the GeodeticReferenceFrame of the datumEnsemble().
1881
 *
1882
 * @return the PrimeMeridian.
1883
 */
1884
9.57M
const datum::EllipsoidNNPtr &GeodeticCRS::ellipsoid() PROJ_PURE_DEFN {
1885
9.57M
    if (d->datum_) {
1886
7.81M
        return d->datum_->ellipsoid();
1887
7.81M
    }
1888
1.76M
    return oneDatum(this)->ellipsoid();
1889
9.57M
}
1890
1891
// ---------------------------------------------------------------------------
1892
1893
/** \brief Return the velocity model associated with the CRS.
1894
 *
1895
 * @return a velocity model. might be null.
1896
 */
1897
const std::vector<operation::PointMotionOperationNNPtr> &
1898
0
GeodeticCRS::velocityModel() PROJ_PURE_DEFN {
1899
0
    return d->velocityModel;
1900
0
}
1901
1902
// ---------------------------------------------------------------------------
1903
1904
/** \brief Return whether the CRS is a Cartesian geocentric one.
1905
 *
1906
 * A geocentric CRS is a geodetic CRS that has a Cartesian coordinate system
1907
 * with three axis, whose direction is respectively
1908
 * cs::AxisDirection::GEOCENTRIC_X,
1909
 * cs::AxisDirection::GEOCENTRIC_Y and cs::AxisDirection::GEOCENTRIC_Z.
1910
 *
1911
 * @return true if the CRS is a geocentric CRS.
1912
 */
1913
1.64M
bool GeodeticCRS::isGeocentric() PROJ_PURE_DEFN {
1914
1.64M
    const auto &cs = coordinateSystem();
1915
1.64M
    const auto &axisList = cs->axisList();
1916
1.64M
    return axisList.size() == 3 &&
1917
629k
           dynamic_cast<cs::CartesianCS *>(cs.get()) != nullptr &&
1918
484k
           &axisList[0]->direction() == &cs::AxisDirection::GEOCENTRIC_X &&
1919
484k
           &axisList[1]->direction() == &cs::AxisDirection::GEOCENTRIC_Y &&
1920
484k
           &axisList[2]->direction() == &cs::AxisDirection::GEOCENTRIC_Z;
1921
1.64M
}
1922
1923
// ---------------------------------------------------------------------------
1924
1925
/** \brief Return whether the CRS is a Spherical planetocentric one.
1926
 *
1927
 * A Spherical planetocentric CRS is a geodetic CRS that has a spherical
1928
 * (angular) coordinate system with 2 axis, which represent geocentric latitude/
1929
 * longitude or longitude/geocentric latitude.
1930
 *
1931
 * Such CRS are typically used in use case that apply to non-Earth bodies.
1932
 *
1933
 * @return true if the CRS is a Spherical planetocentric CRS.
1934
 *
1935
 * @since 8.2
1936
 */
1937
1.29M
bool GeodeticCRS::isSphericalPlanetocentric() PROJ_PURE_DEFN {
1938
1.29M
    const auto &cs = coordinateSystem();
1939
1.29M
    const auto &axisList = cs->axisList();
1940
1.29M
    return axisList.size() == 2 &&
1941
973k
           dynamic_cast<cs::SphericalCS *>(cs.get()) != nullptr &&
1942
9.64k
           ((ci_equal(axisList[0]->nameStr(), "planetocentric latitude") &&
1943
796
             ci_equal(axisList[1]->nameStr(), "planetocentric longitude")) ||
1944
8.84k
            (ci_equal(axisList[0]->nameStr(), "planetocentric longitude") &&
1945
8.84k
             ci_equal(axisList[1]->nameStr(), "planetocentric latitude")));
1946
1.29M
}
1947
1948
// ---------------------------------------------------------------------------
1949
1950
/** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame and a
1951
 * cs::SphericalCS.
1952
 *
1953
 * @param properties See \ref general_properties.
1954
 * At minimum the name should be defined.
1955
 * @param datum The datum of the CRS.
1956
 * @param cs a SphericalCS.
1957
 * @return new GeodeticCRS.
1958
 */
1959
GeodeticCRSNNPtr
1960
GeodeticCRS::create(const util::PropertyMap &properties,
1961
                    const datum::GeodeticReferenceFrameNNPtr &datum,
1962
25.8k
                    const cs::SphericalCSNNPtr &cs) {
1963
25.8k
    return create(properties, datum.as_nullable(), nullptr, cs);
1964
25.8k
}
1965
1966
// ---------------------------------------------------------------------------
1967
1968
/** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame or
1969
 * datum::DatumEnsemble and a cs::SphericalCS.
1970
 *
1971
 * One and only one of datum or datumEnsemble should be set to a non-null value.
1972
 *
1973
 * @param properties See \ref general_properties.
1974
 * At minimum the name should be defined.
1975
 * @param datum The datum of the CRS, or nullptr
1976
 * @param datumEnsemble The datum ensemble of the CRS, or nullptr.
1977
 * @param cs a SphericalCS.
1978
 * @return new GeodeticCRS.
1979
 */
1980
GeodeticCRSNNPtr
1981
GeodeticCRS::create(const util::PropertyMap &properties,
1982
                    const datum::GeodeticReferenceFramePtr &datum,
1983
                    const datum::DatumEnsemblePtr &datumEnsemble,
1984
33.2k
                    const cs::SphericalCSNNPtr &cs) {
1985
33.2k
    auto crs(
1986
33.2k
        GeodeticCRS::nn_make_shared<GeodeticCRS>(datum, datumEnsemble, cs));
1987
33.2k
    crs->assignSelf(crs);
1988
33.2k
    crs->setProperties(properties);
1989
1990
33.2k
    return crs;
1991
33.2k
}
1992
1993
// ---------------------------------------------------------------------------
1994
1995
/** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame and a
1996
 * cs::CartesianCS.
1997
 *
1998
 * @param properties See \ref general_properties.
1999
 * At minimum the name should be defined.
2000
 * @param datum The datum of the CRS.
2001
 * @param cs a CartesianCS.
2002
 * @return new GeodeticCRS.
2003
 */
2004
GeodeticCRSNNPtr
2005
GeodeticCRS::create(const util::PropertyMap &properties,
2006
                    const datum::GeodeticReferenceFrameNNPtr &datum,
2007
46.2k
                    const cs::CartesianCSNNPtr &cs) {
2008
46.2k
    return create(properties, datum.as_nullable(), nullptr, cs);
2009
46.2k
}
2010
2011
// ---------------------------------------------------------------------------
2012
2013
/** \brief Instantiate a GeodeticCRS from a datum::GeodeticReferenceFrame or
2014
 * datum::DatumEnsemble and a cs::CartesianCS.
2015
 *
2016
 * One and only one of datum or datumEnsemble should be set to a non-null value.
2017
 *
2018
 * @param properties See \ref general_properties.
2019
 * At minimum the name should be defined.
2020
 * @param datum The datum of the CRS, or nullptr
2021
 * @param datumEnsemble The datum ensemble of the CRS, or nullptr.
2022
 * @param cs a CartesianCS
2023
 * @return new GeodeticCRS.
2024
 */
2025
GeodeticCRSNNPtr
2026
GeodeticCRS::create(const util::PropertyMap &properties,
2027
                    const datum::GeodeticReferenceFramePtr &datum,
2028
                    const datum::DatumEnsemblePtr &datumEnsemble,
2029
188k
                    const cs::CartesianCSNNPtr &cs) {
2030
188k
    auto crs(
2031
188k
        GeodeticCRS::nn_make_shared<GeodeticCRS>(datum, datumEnsemble, cs));
2032
188k
    crs->assignSelf(crs);
2033
188k
    crs->setProperties(properties);
2034
2035
188k
    return crs;
2036
188k
}
2037
2038
// ---------------------------------------------------------------------------
2039
2040
//! @cond Doxygen_Suppress
2041
2042
// Try to format a Geographic/ProjectedCRS 3D CRS as a
2043
// GEOGCS[]/PROJCS[],VERTCS[...,DATUM[],...] if we find corresponding objects
2044
static bool exportAsESRIWktCompoundCRSWithEllipsoidalHeight(
2045
0
    const CRS *self, const GeodeticCRS *geodCRS, io::WKTFormatter *formatter) {
2046
0
    const auto &dbContext = formatter->databaseContext();
2047
0
    if (!dbContext) {
2048
0
        return false;
2049
0
    }
2050
0
    const auto l_datum = geodCRS->datumNonNull(formatter->databaseContext());
2051
0
    auto l_esri_name = dbContext->getAliasFromOfficialName(
2052
0
        l_datum->nameStr(), "geodetic_datum", "ESRI");
2053
0
    if (l_esri_name.empty()) {
2054
0
        l_esri_name = l_datum->nameStr();
2055
0
    }
2056
0
    auto authFactory =
2057
0
        io::AuthorityFactory::create(NN_NO_CHECK(dbContext), std::string());
2058
0
    auto list = authFactory->createObjectsFromName(
2059
0
        l_esri_name,
2060
0
        {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME},
2061
0
        false /* approximate=false*/);
2062
0
    if (list.empty()) {
2063
0
        return false;
2064
0
    }
2065
0
    auto gdatum = util::nn_dynamic_pointer_cast<datum::Datum>(list.front());
2066
0
    if (gdatum == nullptr || gdatum->identifiers().empty()) {
2067
0
        return false;
2068
0
    }
2069
0
    const auto &gdatum_ids = gdatum->identifiers();
2070
0
    auto vertCRSList = authFactory->createVerticalCRSFromDatum(
2071
0
        "ESRI", "from_geogdatum_" + *gdatum_ids[0]->codeSpace() + '_' +
2072
0
                    gdatum_ids[0]->code());
2073
0
    self->demoteTo2D(std::string(), dbContext)->_exportToWKT(formatter);
2074
0
    if (vertCRSList.size() == 1) {
2075
0
        vertCRSList.front()->_exportToWKT(formatter);
2076
0
    } else {
2077
        // This will not be recognized properly by ESRI software
2078
        // See https://github.com/OSGeo/PROJ/issues/2757
2079
2080
0
        const auto &axisList = geodCRS->coordinateSystem()->axisList();
2081
0
        assert(axisList.size() == 3U);
2082
2083
0
        formatter->startNode(io::WKTConstants::VERTCS, false);
2084
0
        auto vertcs_name = std::move(l_esri_name);
2085
0
        if (starts_with(vertcs_name.c_str(), "GCS_"))
2086
0
            vertcs_name = vertcs_name.substr(4);
2087
0
        formatter->addQuotedString(vertcs_name);
2088
2089
0
        gdatum->_exportToWKT(formatter);
2090
2091
        // Seems to be a constant value...
2092
0
        formatter->startNode(io::WKTConstants::PARAMETER, false);
2093
0
        formatter->addQuotedString("Vertical_Shift");
2094
0
        formatter->add(0.0);
2095
0
        formatter->endNode();
2096
2097
0
        formatter->startNode(io::WKTConstants::PARAMETER, false);
2098
0
        formatter->addQuotedString("Direction");
2099
0
        formatter->add(
2100
0
            axisList[2]->direction() == cs::AxisDirection::UP ? 1.0 : -1.0);
2101
0
        formatter->endNode();
2102
2103
0
        axisList[2]->unit()._exportToWKT(formatter);
2104
0
        formatter->endNode();
2105
0
    }
2106
0
    return true;
2107
0
}
2108
2109
// ---------------------------------------------------------------------------
2110
2111
// Try to format a Geographic/ProjectedCRS 3D CRS as a
2112
// GEOGCS[]/PROJCS[],VERTCS["Ellipsoid (metre)",DATUM["Ellipsoid",2002],...]
2113
static void exportAsWKT1CompoundCRSWithEllipsoidalHeight(
2114
    const CRSNNPtr &base2DCRS,
2115
    const cs::CoordinateSystemAxisNNPtr &verticalAxis,
2116
0
    io::WKTFormatter *formatter) {
2117
0
    std::string verticalCRSName = "Ellipsoid (";
2118
0
    verticalCRSName += verticalAxis->unit().name();
2119
0
    verticalCRSName += ')';
2120
0
    auto vertDatum = datum::VerticalReferenceFrame::create(
2121
0
        util::PropertyMap()
2122
0
            .set(common::IdentifiedObject::NAME_KEY, "Ellipsoid")
2123
0
            .set("VERT_DATUM_TYPE", "2002"));
2124
0
    auto vertCRS = VerticalCRS::create(
2125
0
        util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
2126
0
                                verticalCRSName),
2127
0
        vertDatum.as_nullable(), nullptr,
2128
0
        cs::VerticalCS::create(util::PropertyMap(), verticalAxis));
2129
0
    formatter->startNode(io::WKTConstants::COMPD_CS, false);
2130
0
    formatter->addQuotedString(base2DCRS->nameStr() + " + " + verticalCRSName);
2131
0
    base2DCRS->_exportToWKT(formatter);
2132
0
    vertCRS->_exportToWKT(formatter);
2133
0
    formatter->endNode();
2134
0
}
2135
//! @endcond
2136
2137
// ---------------------------------------------------------------------------
2138
2139
//! @cond Doxygen_Suppress
2140
443k
void GeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {
2141
443k
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2142
443k
    const bool isGeographic =
2143
443k
        dynamic_cast<const GeographicCRS *>(this) != nullptr;
2144
2145
443k
    const auto &cs = coordinateSystem();
2146
443k
    const auto &axisList = cs->axisList();
2147
443k
    const bool isGeographic3D = isGeographic && axisList.size() == 3;
2148
443k
    const auto oldAxisOutputRule = formatter->outputAxis();
2149
443k
    std::string l_name = nameStr();
2150
443k
    const auto &dbContext = formatter->databaseContext();
2151
2152
443k
    const bool isESRIExport = !isWKT2 && formatter->useESRIDialect();
2153
443k
    const auto &l_identifiers = identifiers();
2154
2155
443k
    if (isESRIExport && axisList.size() == 3) {
2156
60
        if (!isGeographic) {
2157
37
            io::FormattingException::Throw(
2158
37
                "Geocentric CRS not supported in WKT1_ESRI");
2159
37
        }
2160
23
        if (!formatter->isAllowedLINUNITNode()) {
2161
            // Try to format the Geographic 3D CRS as a
2162
            // GEOGCS[],VERTCS[...,DATUM[]] if we find corresponding objects
2163
0
            if (dbContext) {
2164
0
                if (exportAsESRIWktCompoundCRSWithEllipsoidalHeight(
2165
0
                        this, this, formatter)) {
2166
0
                    return;
2167
0
                }
2168
0
            }
2169
0
            io::FormattingException::Throw(
2170
0
                "Cannot export this Geographic 3D CRS in WKT1_ESRI");
2171
0
        }
2172
23
    }
2173
2174
443k
    if (!isWKT2 && !isESRIExport && formatter->isStrict() && isGeographic &&
2175
94.9k
        axisList.size() == 3 &&
2176
1.91k
        oldAxisOutputRule != io::WKTFormatter::OutputAxisRule::NO) {
2177
2178
1.77k
        auto geogCRS2D = demoteTo2D(std::string(), dbContext);
2179
1.77k
        if (dbContext) {
2180
1.77k
            const auto res = geogCRS2D->identify(io::AuthorityFactory::create(
2181
1.77k
                NN_NO_CHECK(dbContext), metadata::Identifier::EPSG));
2182
1.77k
            if (res.size() == 1) {
2183
100
                const auto &front = res.front();
2184
100
                if (front.second == 100) {
2185
95
                    geogCRS2D = front.first;
2186
95
                }
2187
100
            }
2188
1.77k
        }
2189
2190
1.77k
        if (CRS::getPrivate()->allowNonConformantWKT1Export_) {
2191
566
            formatter->startNode(io::WKTConstants::COMPD_CS, false);
2192
566
            formatter->addQuotedString(l_name + " + " + l_name);
2193
566
            geogCRS2D->_exportToWKT(formatter);
2194
566
            const std::vector<double> oldTOWGSParameters(
2195
566
                formatter->getTOWGS84Parameters());
2196
566
            formatter->setTOWGS84Parameters({});
2197
566
            geogCRS2D->_exportToWKT(formatter);
2198
566
            formatter->setTOWGS84Parameters(oldTOWGSParameters);
2199
566
            formatter->endNode();
2200
566
            return;
2201
566
        }
2202
2203
1.21k
        auto &originalCompoundCRS = CRS::getPrivate()->originalCompoundCRS_;
2204
1.21k
        if (originalCompoundCRS) {
2205
968
            originalCompoundCRS->_exportToWKT(formatter);
2206
968
            return;
2207
968
        }
2208
2209
242
        if (formatter->isAllowedEllipsoidalHeightAsVerticalCRS()) {
2210
0
            exportAsWKT1CompoundCRSWithEllipsoidalHeight(geogCRS2D, axisList[2],
2211
0
                                                         formatter);
2212
0
            return;
2213
0
        }
2214
2215
242
        io::FormattingException::Throw(
2216
242
            "WKT1 does not support Geographic 3D CRS.");
2217
242
    }
2218
2219
442k
    formatter->startNode(isWKT2
2220
442k
                             ? ((formatter->use2019Keywords() && isGeographic)
2221
20.4k
                                    ? io::WKTConstants::GEOGCRS
2222
20.4k
                                    : io::WKTConstants::GEODCRS)
2223
442k
                         : isGeocentric() ? io::WKTConstants::GEOCCS
2224
421k
                                          : io::WKTConstants::GEOGCS,
2225
442k
                         !l_identifiers.empty());
2226
2227
442k
    if (isESRIExport) {
2228
1.34k
        std::string l_esri_name;
2229
1.34k
        if (l_name == "WGS 84") {
2230
329
            l_esri_name = isGeographic3D ? "WGS_1984_3D" : "GCS_WGS_1984";
2231
1.01k
        } else {
2232
1.01k
            if (dbContext) {
2233
1.01k
                const auto tableName =
2234
1.01k
                    isGeographic3D ? "geographic_3D_crs" : "geodetic_crs";
2235
1.01k
                if (!l_identifiers.empty()) {
2236
                    // Try to find the ESRI alias from the CRS identified by its
2237
                    // id
2238
947
                    const auto aliases =
2239
947
                        dbContext->getAliases(*(l_identifiers[0]->codeSpace()),
2240
947
                                              l_identifiers[0]->code(),
2241
947
                                              std::string(), // officialName,
2242
947
                                              tableName, "ESRI");
2243
947
                    if (aliases.size() == 1)
2244
895
                        l_esri_name = aliases.front();
2245
947
                }
2246
1.01k
                if (l_esri_name.empty()) {
2247
                    // Then find the ESRI alias from the CRS name
2248
118
                    l_esri_name = dbContext->getAliasFromOfficialName(
2249
118
                        l_name, tableName, "ESRI");
2250
118
                }
2251
1.01k
                if (l_esri_name.empty()) {
2252
                    // Then try to build an ESRI CRS from the CRS name, and if
2253
                    // there's one, the ESRI name is the CRS name
2254
118
                    auto authFactory = io::AuthorityFactory::create(
2255
118
                        NN_NO_CHECK(dbContext), "ESRI");
2256
118
                    const bool found = authFactory
2257
118
                                           ->createObjectsFromName(
2258
118
                                               l_name,
2259
118
                                               {io::AuthorityFactory::
2260
118
                                                    ObjectType::GEODETIC_CRS},
2261
118
                                               false // approximateMatch
2262
118
                                               )
2263
118
                                           .size() == 1;
2264
118
                    if (found)
2265
0
                        l_esri_name = l_name;
2266
118
                }
2267
1.01k
                if (l_esri_name.empty() && !l_identifiers.empty()) {
2268
                    // Case for example for ETRS89-NOR [EUREF89] that has no
2269
                    // ESRI alias. Fallback to ETRS89
2270
52
                    const auto EPSGOldAliases =
2271
52
                        dbContext->getAliases(*(l_identifiers[0]->codeSpace()),
2272
52
                                              l_identifiers[0]->code(),
2273
52
                                              std::string(), // officialName,
2274
52
                                              tableName, "EPSG_OLD");
2275
52
                    if (EPSGOldAliases.size() == 1) {
2276
34
                        auto authFactoryEPSG = io::AuthorityFactory::create(
2277
34
                            NN_NO_CHECK(dbContext), "EPSG");
2278
34
                        auto objCandidates =
2279
34
                            authFactoryEPSG->createObjectsFromNameEx(
2280
34
                                EPSGOldAliases.front(),
2281
34
                                {io::AuthorityFactory::ObjectType::
2282
34
                                     GEODETIC_CRS},
2283
34
                                false, // approximateMatch
2284
34
                                0,     // limitResultCount
2285
34
                                false  // useAliases
2286
34
                            );
2287
34
                        const auto thisNature = isGeographic3D ? "geographic_3D"
2288
34
                                                : isGeocentric()
2289
34
                                                    ? "geocentric"
2290
34
                                                    : "geographic_2D";
2291
34
                        for (const auto &[obj, name] : objCandidates) {
2292
34
                            (void)name;
2293
34
                            const auto objGeodetic =
2294
34
                                dynamic_cast<const GeodeticCRS *>(obj.get());
2295
34
                            if (objGeodetic) {
2296
34
                                const auto objGeographic =
2297
34
                                    dynamic_cast<const GeographicCRS *>(
2298
34
                                        obj.get());
2299
34
                                const auto objNature =
2300
34
                                    objGeographic &&
2301
34
                                            objGeographic->coordinateSystem()
2302
34
                                                    ->axisList()
2303
34
                                                    .size() == 3
2304
34
                                        ? "geographic_3D"
2305
34
                                    : objGeodetic->isGeocentric()
2306
34
                                        ? "geocentric"
2307
34
                                        : "geographic_2D";
2308
34
                                const auto &objIdentifiers = obj->identifiers();
2309
34
                                if (!objIdentifiers.empty() &&
2310
34
                                    strcmp(thisNature, objNature) == 0) {
2311
34
                                    const auto ESRIAliases =
2312
34
                                        dbContext->getAliases(
2313
34
                                            *(objIdentifiers[0]->codeSpace()),
2314
34
                                            objIdentifiers[0]->code(),
2315
34
                                            std::string(), // officialName,
2316
34
                                            tableName, "ESRI");
2317
34
                                    if (ESRIAliases.size() == 1) {
2318
34
                                        l_esri_name = ESRIAliases.front();
2319
34
                                        break;
2320
34
                                    }
2321
34
                                }
2322
34
                            }
2323
34
                        }
2324
34
                    }
2325
52
                }
2326
1.01k
            }
2327
1.01k
            if (l_esri_name.empty()) {
2328
84
                l_esri_name = io::WKTFormatter::morphNameToESRI(l_name);
2329
84
                if (!starts_with(l_esri_name, "GCS_")) {
2330
84
                    l_esri_name = "GCS_" + l_esri_name;
2331
84
                }
2332
84
            }
2333
1.01k
        }
2334
1.34k
        const std::string &l_esri_name_ref(l_esri_name);
2335
1.34k
        l_name = l_esri_name_ref;
2336
440k
    } else if (!isWKT2 && isDeprecated()) {
2337
591
        l_name += " (deprecated)";
2338
591
    }
2339
442k
    formatter->addQuotedString(l_name);
2340
2341
442k
    const auto &unit = axisList[0]->unit();
2342
442k
    formatter->pushAxisAngularUnit(common::UnitOfMeasure::create(unit));
2343
442k
    exportDatumOrDatumEnsembleToWkt(formatter);
2344
442k
    primeMeridian()->_exportToWKT(formatter);
2345
442k
    formatter->popAxisAngularUnit();
2346
442k
    if (!isWKT2) {
2347
421k
        unit._exportToWKT(formatter);
2348
421k
    }
2349
442k
    if (isGeographic3D && isESRIExport) {
2350
23
        axisList[2]->unit()._exportToWKT(formatter, io::WKTConstants::LINUNIT);
2351
23
    }
2352
2353
442k
    if (oldAxisOutputRule ==
2354
442k
            io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE &&
2355
15
        isGeocentric()) {
2356
0
        formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
2357
0
    }
2358
442k
    cs->_exportToWKT(formatter);
2359
442k
    formatter->setOutputAxis(oldAxisOutputRule);
2360
2361
442k
    ObjectUsage::baseExportToWKT(formatter);
2362
2363
442k
    if (!isWKT2 && !isESRIExport) {
2364
420k
        const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
2365
420k
        if (!extensionProj4.empty()) {
2366
6.38k
            formatter->startNode(io::WKTConstants::EXTENSION, false);
2367
6.38k
            formatter->addQuotedString("PROJ4");
2368
6.38k
            formatter->addQuotedString(extensionProj4);
2369
6.38k
            formatter->endNode();
2370
6.38k
        }
2371
420k
    }
2372
2373
442k
    formatter->endNode();
2374
442k
}
2375
//! @endcond
2376
2377
// ---------------------------------------------------------------------------
2378
2379
//! @cond Doxygen_Suppress
2380
void GeodeticCRS::addGeocentricUnitConversionIntoPROJString(
2381
91.3k
    io::PROJStringFormatter *formatter) const {
2382
2383
91.3k
    const auto &axisList = coordinateSystem()->axisList();
2384
91.3k
    const auto &unit = axisList[0]->unit();
2385
91.3k
    if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
2386
91.3k
                              util::IComparable::Criterion::EQUIVALENT)) {
2387
3.79k
        if (formatter->getCRSExport()) {
2388
263
            io::FormattingException::Throw(
2389
263
                "GeodeticCRS::exportToPROJString() only "
2390
263
                "supports metre unit");
2391
263
        }
2392
3.53k
        formatter->addStep("unitconvert");
2393
3.53k
        formatter->addParam("xy_in", "m");
2394
3.53k
        formatter->addParam("z_in", "m");
2395
3.53k
        {
2396
3.53k
            auto projUnit = unit.exportToPROJString();
2397
3.53k
            if (!projUnit.empty()) {
2398
2.99k
                formatter->addParam("xy_out", projUnit);
2399
2.99k
                formatter->addParam("z_out", projUnit);
2400
2.99k
                return;
2401
2.99k
            }
2402
3.53k
        }
2403
2404
535
        const auto &toSI = unit.conversionToSI();
2405
535
        formatter->addParam("xy_out", toSI);
2406
535
        formatter->addParam("z_out", toSI);
2407
87.5k
    } else if (formatter->getCRSExport()) {
2408
6.16k
        formatter->addParam("units", "m");
2409
6.16k
    }
2410
91.3k
}
2411
//! @endcond
2412
2413
// ---------------------------------------------------------------------------
2414
2415
//! @cond Doxygen_Suppress
2416
1.55M
void GeodeticCRS::addAxisSwap(io::PROJStringFormatter *formatter) const {
2417
1.55M
    const auto &axisList = coordinateSystem()->axisList();
2418
2419
1.55M
    const char *order[2] = {nullptr, nullptr};
2420
1.55M
    const char *one = "1";
2421
1.55M
    const char *two = "2";
2422
4.65M
    for (int i = 0; i < 2; i++) {
2423
3.10M
        const auto &dir = axisList[i]->direction();
2424
3.10M
        if (&dir == &cs::AxisDirection::WEST) {
2425
820
            order[i] = "-1";
2426
3.10M
        } else if (&dir == &cs::AxisDirection::EAST) {
2427
1.55M
            order[i] = one;
2428
1.55M
        } else if (&dir == &cs::AxisDirection::SOUTH) {
2429
1.31k
            order[i] = "-2";
2430
1.55M
        } else if (&dir == &cs::AxisDirection::NORTH) {
2431
1.55M
            order[i] = two;
2432
1.55M
        }
2433
3.10M
    }
2434
1.55M
    if (order[0] && order[1] && (order[0] != one || order[1] != two)) {
2435
1.45M
        formatter->addStep("axisswap");
2436
1.45M
        char orderStr[10];
2437
1.45M
        snprintf(orderStr, sizeof(orderStr), "%.2s,%.2s", order[0], order[1]);
2438
1.45M
        formatter->addParam("order", orderStr);
2439
1.45M
    }
2440
1.55M
}
2441
//! @endcond
2442
2443
// ---------------------------------------------------------------------------
2444
2445
//! @cond Doxygen_Suppress
2446
void GeodeticCRS::addAngularUnitConvertAndAxisSwap(
2447
1.55M
    io::PROJStringFormatter *formatter) const {
2448
1.55M
    const auto &axisList = coordinateSystem()->axisList();
2449
2450
1.55M
    formatter->addStep("unitconvert");
2451
1.55M
    formatter->addParam("xy_in", "rad");
2452
1.55M
    if (axisList.size() == 3 && !formatter->omitZUnitConversion()) {
2453
50.7k
        formatter->addParam("z_in", "m");
2454
50.7k
    }
2455
1.55M
    {
2456
1.55M
        const auto &unitHoriz = axisList[0]->unit();
2457
1.55M
        const auto projUnit = unitHoriz.exportToPROJString();
2458
1.55M
        if (projUnit.empty()) {
2459
12.1k
            formatter->addParam("xy_out", unitHoriz.conversionToSI());
2460
1.53M
        } else {
2461
1.53M
            formatter->addParam("xy_out", projUnit);
2462
1.53M
        }
2463
1.55M
    }
2464
1.55M
    if (axisList.size() == 3 && !formatter->omitZUnitConversion()) {
2465
50.7k
        const auto &unitZ = axisList[2]->unit();
2466
50.7k
        auto projVUnit = unitZ.exportToPROJString();
2467
50.7k
        if (projVUnit.empty()) {
2468
961
            formatter->addParam("z_out", unitZ.conversionToSI());
2469
49.7k
        } else {
2470
49.7k
            formatter->addParam("z_out", projVUnit);
2471
49.7k
        }
2472
50.7k
    }
2473
2474
1.55M
    addAxisSwap(formatter);
2475
1.55M
}
2476
//! @endcond
2477
2478
// ---------------------------------------------------------------------------
2479
2480
//! @cond Doxygen_Suppress
2481
void GeodeticCRS::_exportToPROJString(
2482
    io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
2483
56.4k
{
2484
56.4k
    const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
2485
56.4k
    if (!extensionProj4.empty()) {
2486
1.53k
        formatter->ingestPROJString(
2487
1.53k
            replaceAll(extensionProj4, " +type=crs", ""));
2488
1.53k
        formatter->addNoDefs(false);
2489
1.53k
        return;
2490
1.53k
    }
2491
2492
54.8k
    if (isGeocentric()) {
2493
54.7k
        if (!formatter->getCRSExport()) {
2494
48.3k
            formatter->addStep("cart");
2495
48.3k
        } else {
2496
6.42k
            formatter->addStep("geocent");
2497
6.42k
        }
2498
2499
54.7k
        addDatumInfoToPROJString(formatter);
2500
54.7k
        addGeocentricUnitConversionIntoPROJString(formatter);
2501
54.7k
    } else if (isSphericalPlanetocentric()) {
2502
107
        if (!formatter->getCRSExport()) {
2503
2504
0
            if (!formatter->omitProjLongLatIfPossible() ||
2505
0
                primeMeridian()->longitude().getSIValue() != 0.0 ||
2506
0
                !ellipsoid()->isSphere() ||
2507
0
                !formatter->getTOWGS84Parameters().empty() ||
2508
0
                !formatter->getHDatumExtension().empty()) {
2509
0
                formatter->addStep("geoc");
2510
0
                addDatumInfoToPROJString(formatter);
2511
0
            }
2512
2513
0
            addAngularUnitConvertAndAxisSwap(formatter);
2514
107
        } else {
2515
107
            io::FormattingException::Throw(
2516
107
                "GeodeticCRS::exportToPROJString() not supported on spherical "
2517
107
                "planetocentric coordinate systems");
2518
            // The below code now works as input to PROJ, but I'm not sure we
2519
            // want to propagate this, given that we got cs2cs doing conversion
2520
            // in the wrong direction in past versions.
2521
            /*formatter->addStep("longlat");
2522
            formatter->addParam("geoc");
2523
2524
            addDatumInfoToPROJString(formatter);*/
2525
107
        }
2526
107
    } else {
2527
0
        io::FormattingException::Throw(
2528
0
            "GeodeticCRS::exportToPROJString() only "
2529
0
            "supports geocentric or spherical planetocentric "
2530
0
            "coordinate systems");
2531
0
    }
2532
54.8k
}
2533
//! @endcond
2534
2535
// ---------------------------------------------------------------------------
2536
2537
//! @cond Doxygen_Suppress
2538
void GeodeticCRS::addDatumInfoToPROJString(
2539
    io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
2540
966k
{
2541
966k
    const auto &TOWGS84Params = formatter->getTOWGS84Parameters();
2542
966k
    bool datumWritten = false;
2543
966k
    const auto &nadgrids = formatter->getHDatumExtension();
2544
966k
    const auto l_datum = datumNonNull(formatter->databaseContext());
2545
966k
    if (formatter->getCRSExport() && TOWGS84Params.empty() &&
2546
61.6k
        nadgrids.empty() && l_datum->nameStr() != "unknown") {
2547
55.9k
        if (l_datum->_isEquivalentTo(
2548
55.9k
                datum::GeodeticReferenceFrame::EPSG_6326.get(),
2549
55.9k
                util::IComparable::Criterion::EQUIVALENT)) {
2550
33.1k
            datumWritten = true;
2551
33.1k
            formatter->addParam("datum", "WGS84");
2552
33.1k
        } else if (l_datum->_isEquivalentTo(
2553
22.7k
                       datum::GeodeticReferenceFrame::EPSG_6267.get(),
2554
22.7k
                       util::IComparable::Criterion::EQUIVALENT)) {
2555
667
            datumWritten = true;
2556
667
            formatter->addParam("datum", "NAD27");
2557
22.1k
        } else if (l_datum->_isEquivalentTo(
2558
22.1k
                       datum::GeodeticReferenceFrame::EPSG_6269.get(),
2559
22.1k
                       util::IComparable::Criterion::EQUIVALENT)) {
2560
1.42k
            datumWritten = true;
2561
1.42k
            if (formatter->getLegacyCRSToCRSContext()) {
2562
                // We do not want datum=NAD83 to cause a useless towgs84=0,0,0
2563
0
                formatter->addParam("ellps", "GRS80");
2564
1.42k
            } else {
2565
1.42k
                formatter->addParam("datum", "NAD83");
2566
1.42k
            }
2567
1.42k
        }
2568
55.9k
    }
2569
966k
    if (!datumWritten) {
2570
931k
        ellipsoid()->_exportToPROJString(formatter);
2571
931k
        primeMeridian()->_exportToPROJString(formatter);
2572
931k
    }
2573
966k
    if (TOWGS84Params.size() == 7) {
2574
4.94k
        formatter->addParam("towgs84", TOWGS84Params);
2575
4.94k
    }
2576
966k
    if (!nadgrids.empty()) {
2577
1.90k
        formatter->addParam("nadgrids", nadgrids);
2578
1.90k
    }
2579
966k
}
2580
//! @endcond
2581
2582
// ---------------------------------------------------------------------------
2583
2584
//! @cond Doxygen_Suppress
2585
2586
void GeodeticCRS::_exportToJSONInternal(
2587
    io::JSONFormatter *formatter,
2588
    const char *objectName) const // throw(io::FormattingException)
2589
75.3k
{
2590
75.3k
    auto writer = formatter->writer();
2591
75.3k
    auto objectContext(
2592
75.3k
        formatter->MakeObjectContext(objectName, !identifiers().empty()));
2593
2594
75.3k
    writer->AddObjKey("name");
2595
75.3k
    const auto &l_name = nameStr();
2596
75.3k
    if (l_name.empty()) {
2597
0
        writer->Add("unnamed");
2598
75.3k
    } else {
2599
75.3k
        writer->Add(l_name);
2600
75.3k
    }
2601
2602
75.3k
    const auto &l_datum(datum());
2603
75.3k
    if (l_datum) {
2604
75.2k
        writer->AddObjKey("datum");
2605
75.2k
        l_datum->_exportToJSON(formatter);
2606
75.2k
    } else {
2607
137
        writer->AddObjKey("datum_ensemble");
2608
137
        formatter->setOmitTypeInImmediateChild();
2609
137
        datumEnsemble()->_exportToJSON(formatter);
2610
137
    }
2611
2612
75.3k
    writer->AddObjKey("coordinate_system");
2613
75.3k
    formatter->setOmitTypeInImmediateChild();
2614
75.3k
    coordinateSystem()->_exportToJSON(formatter);
2615
2616
75.3k
    if (const auto dynamicGRF =
2617
75.3k
            dynamic_cast<datum::DynamicGeodeticReferenceFrame *>(
2618
75.3k
                l_datum.get())) {
2619
0
        const auto &deformationModel = dynamicGRF->deformationModelName();
2620
0
        if (deformationModel.has_value()) {
2621
0
            writer->AddObjKey("deformation_models");
2622
0
            auto arrayContext(writer->MakeArrayContext(false));
2623
0
            auto objectContext2(formatter->MakeObjectContext(nullptr, false));
2624
0
            writer->AddObjKey("name");
2625
0
            writer->Add(*deformationModel);
2626
0
        }
2627
0
    }
2628
2629
75.3k
    ObjectUsage::baseExportToJSON(formatter);
2630
75.3k
}
2631
2632
void GeodeticCRS::_exportToJSON(
2633
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
2634
38
{
2635
38
    _exportToJSONInternal(formatter, "GeodeticCRS");
2636
38
}
2637
//! @endcond
2638
2639
// ---------------------------------------------------------------------------
2640
2641
//! @cond Doxygen_Suppress
2642
static util::IComparable::Criterion
2643
5.88M
getStandardCriterion(util::IComparable::Criterion criterion) {
2644
5.88M
    return criterion == util::IComparable::Criterion::
2645
5.88M
                            EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS
2646
5.88M
               ? util::IComparable::Criterion::EQUIVALENT
2647
5.88M
               : criterion;
2648
5.88M
}
2649
//! @endcond
2650
2651
// ---------------------------------------------------------------------------
2652
2653
//! @cond Doxygen_Suppress
2654
bool GeodeticCRS::_isEquivalentTo(
2655
    const util::IComparable *other, util::IComparable::Criterion criterion,
2656
314k
    const io::DatabaseContextPtr &dbContext) const {
2657
314k
    if (other == nullptr || !util::isOfExactType<GeodeticCRS>(*other)) {
2658
95.2k
        return false;
2659
95.2k
    }
2660
218k
    return _isEquivalentToNoTypeCheck(other, criterion, dbContext);
2661
314k
}
2662
2663
bool GeodeticCRS::_isEquivalentToNoTypeCheck(
2664
    const util::IComparable *other, util::IComparable::Criterion criterion,
2665
2.85M
    const io::DatabaseContextPtr &dbContext) const {
2666
2.85M
    const auto standardCriterion = getStandardCriterion(criterion);
2667
2668
    // TODO test velocityModel
2669
2.85M
    return SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext);
2670
2.85M
}
2671
//! @endcond
2672
2673
// ---------------------------------------------------------------------------
2674
2675
//! @cond Doxygen_Suppress
2676
1.29k
static util::PropertyMap createMapNameEPSGCode(const char *name, int code) {
2677
1.29k
    return util::PropertyMap()
2678
1.29k
        .set(common::IdentifiedObject::NAME_KEY, name)
2679
1.29k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
2680
1.29k
        .set(metadata::Identifier::CODE_KEY, code);
2681
1.29k
}
2682
//! @endcond
2683
2684
// ---------------------------------------------------------------------------
2685
2686
93
GeodeticCRSNNPtr GeodeticCRS::createEPSG_4978() {
2687
93
    return create(
2688
93
        createMapNameEPSGCode("WGS 84", 4978),
2689
93
        datum::GeodeticReferenceFrame::EPSG_6326,
2690
93
        cs::CartesianCS::createGeocentric(common::UnitOfMeasure::METRE));
2691
93
}
2692
2693
// ---------------------------------------------------------------------------
2694
2695
//! @cond Doxygen_Suppress
2696
2697
static bool hasCodeCompatibleOfAuthorityFactory(
2698
    const common::IdentifiedObject *obj,
2699
72.0k
    const io::AuthorityFactoryPtr &authorityFactory) {
2700
72.0k
    const auto &ids = obj->identifiers();
2701
72.0k
    if (!ids.empty() && authorityFactory->getAuthority().empty()) {
2702
17.2k
        return true;
2703
17.2k
    }
2704
54.8k
    for (const auto &id : ids) {
2705
137
        if (*(id->codeSpace()) == authorityFactory->getAuthority()) {
2706
137
            return true;
2707
137
        }
2708
137
    }
2709
54.7k
    return false;
2710
54.8k
}
2711
2712
static bool hasCodeCompatibleOfAuthorityFactory(
2713
    const metadata::IdentifierNNPtr &id,
2714
22.2k
    const io::AuthorityFactoryPtr &authorityFactory) {
2715
22.2k
    if (authorityFactory->getAuthority().empty()) {
2716
22.0k
        return true;
2717
22.0k
    }
2718
137
    return *(id->codeSpace()) == authorityFactory->getAuthority();
2719
22.2k
}
2720
2721
//! @endcond
2722
2723
// ---------------------------------------------------------------------------
2724
2725
/** \brief Identify the CRS with reference CRSs.
2726
 *
2727
 * The candidate CRSs are either hard-coded, or looked in the database when
2728
 * authorityFactory is not null.
2729
 *
2730
 * Note that the implementation uses a set of heuristics to have a good
2731
 * compromise of successful identifications over execution time. It might miss
2732
 * legitimate matches in some circumstances.
2733
 *
2734
 * The method returns a list of matching reference CRS, and the percentage
2735
 * (0-100) of confidence in the match:
2736
 * <ul>
2737
 * <li>100% means that the name of the reference entry
2738
 * perfectly matches the CRS name, and both are equivalent. In which case a
2739
 * single result is returned.
2740
 * Note: in the case of a GeographicCRS whose axis
2741
 * order is implicit in the input definition (for example ESRI WKT), then axis
2742
 * order is ignored for the purpose of identification. That is the CRS built
2743
 * from
2744
 * GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],
2745
 * PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]
2746
 * will be identified to EPSG:4326, but will not pass a
2747
 * isEquivalentTo(EPSG_4326, util::IComparable::Criterion::EQUIVALENT) test,
2748
 * but rather isEquivalentTo(EPSG_4326,
2749
 * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS)
2750
 * </li>
2751
 * <li>90% means that CRS are equivalent, but the names are not exactly the
2752
 * same.
2753
 * <li>70% means that CRS are equivalent (equivalent datum and coordinate
2754
 * system),
2755
 * but the names are not equivalent.</li>
2756
 * <li>60% means that ellipsoid, prime meridian and coordinate systems are
2757
 * equivalent, but the CRS and datum names do not match.</li>
2758
 * <li>25% means that the CRS are not equivalent, but there is some similarity
2759
 * in
2760
 * the names.</li>
2761
 * </ul>
2762
 *
2763
 * @param authorityFactory Authority factory (or null, but degraded
2764
 * functionality)
2765
 * @return a list of matching reference CRS, and the percentage (0-100) of
2766
 * confidence in the match.
2767
 */
2768
std::list<std::pair<GeodeticCRSNNPtr, int>>
2769
79.8k
GeodeticCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
2770
79.8k
    typedef std::pair<GeodeticCRSNNPtr, int> Pair;
2771
79.8k
    std::list<Pair> res;
2772
79.8k
    const auto &thisName(nameStr());
2773
2774
79.8k
    io::DatabaseContextPtr dbContext =
2775
79.8k
        authorityFactory ? authorityFactory->databaseContext().as_nullable()
2776
79.8k
                         : nullptr;
2777
79.8k
    const bool l_implicitCS = hasImplicitCS();
2778
79.8k
    const auto crsCriterion =
2779
79.8k
        l_implicitCS
2780
79.8k
            ? util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS
2781
79.8k
            : util::IComparable::Criterion::EQUIVALENT;
2782
2783
79.8k
    if (authorityFactory == nullptr ||
2784
79.8k
        authorityFactory->getAuthority().empty() ||
2785
79.8k
        authorityFactory->getAuthority() == metadata::Identifier::EPSG) {
2786
79.8k
        const GeographicCRSNNPtr candidatesCRS[] = {GeographicCRS::EPSG_4326,
2787
79.8k
                                                    GeographicCRS::EPSG_4267,
2788
79.8k
                                                    GeographicCRS::EPSG_4269};
2789
219k
        for (const auto &crs : candidatesCRS) {
2790
219k
            const bool nameEquivalent = metadata::Identifier::isEquivalentName(
2791
219k
                thisName.c_str(), crs->nameStr().c_str());
2792
219k
            const bool nameEqual = thisName == crs->nameStr();
2793
219k
            const bool isEq =
2794
219k
                _isEquivalentTo(crs.get(), crsCriterion, dbContext);
2795
219k
            if (nameEquivalent && isEq && (!authorityFactory || nameEqual)) {
2796
11.1k
                res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs),
2797
11.1k
                                 nameEqual ? 100 : 90);
2798
11.1k
                return res;
2799
208k
            } else if (nameEqual && !isEq && !authorityFactory) {
2800
0
                res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs),
2801
0
                                 25);
2802
0
                return res;
2803
208k
            } else if (isEq && !authorityFactory) {
2804
0
                res.emplace_back(util::nn_static_pointer_cast<GeodeticCRS>(crs),
2805
0
                                 70);
2806
0
                return res;
2807
0
            }
2808
219k
        }
2809
79.8k
    }
2810
2811
68.7k
    std::string geodetic_crs_type;
2812
68.7k
    if (isGeocentric()) {
2813
5.93k
        geodetic_crs_type = "geocentric";
2814
62.8k
    } else {
2815
62.8k
        auto geogCRS = dynamic_cast<const GeographicCRS *>(this);
2816
62.8k
        if (geogCRS) {
2817
62.8k
            if (coordinateSystem()->axisList().size() == 2) {
2818
62.6k
                geodetic_crs_type = "geographic 2D";
2819
62.6k
            } else {
2820
128
                geodetic_crs_type = "geographic 3D";
2821
128
            }
2822
62.8k
        }
2823
62.8k
    }
2824
2825
68.7k
    if (authorityFactory) {
2826
2827
68.7k
        const auto thisDatum(datumNonNull(dbContext));
2828
2829
68.7k
        auto searchByDatumCode =
2830
68.7k
            [this, &authorityFactory, &res, &geodetic_crs_type, crsCriterion,
2831
68.7k
             &dbContext](const common::IdentifiedObjectNNPtr &l_datum) {
2832
28.5k
                bool resModified = false;
2833
29.5k
                for (const auto &id : l_datum->identifiers()) {
2834
29.5k
                    try {
2835
29.5k
                        auto tempRes =
2836
29.5k
                            authorityFactory->createGeodeticCRSFromDatum(
2837
29.5k
                                *id->codeSpace(), id->code(),
2838
29.5k
                                geodetic_crs_type);
2839
39.9k
                        for (const auto &crs : tempRes) {
2840
39.9k
                            if (_isEquivalentTo(crs.get(), crsCriterion,
2841
39.9k
                                                dbContext)) {
2842
24.4k
                                res.emplace_back(crs, 70);
2843
24.4k
                                resModified = true;
2844
24.4k
                            }
2845
39.9k
                        }
2846
29.5k
                    } catch (const std::exception &) {
2847
0
                    }
2848
29.5k
                }
2849
28.5k
                return resModified;
2850
28.5k
            };
2851
2852
68.7k
        auto searchByEllipsoid = [this, &authorityFactory, &res, &thisDatum,
2853
68.7k
                                  &geodetic_crs_type, l_implicitCS,
2854
68.7k
                                  &dbContext]() {
2855
35.1k
            const auto &thisEllipsoid = thisDatum->ellipsoid();
2856
35.1k
            const std::list<datum::EllipsoidNNPtr> ellipsoids(
2857
35.1k
                thisEllipsoid->identifiers().empty()
2858
35.1k
                    ? authorityFactory->createEllipsoidFromExisting(
2859
31.1k
                          thisEllipsoid)
2860
35.1k
                    : std::list<datum::EllipsoidNNPtr>{thisEllipsoid});
2861
36.7k
            for (const auto &ellps : ellipsoids) {
2862
38.1k
                for (const auto &id : ellps->identifiers()) {
2863
38.1k
                    try {
2864
38.1k
                        auto tempRes =
2865
38.1k
                            authorityFactory->createGeodeticCRSFromEllipsoid(
2866
38.1k
                                *id->codeSpace(), id->code(),
2867
38.1k
                                geodetic_crs_type);
2868
1.28M
                        for (const auto &crs : tempRes) {
2869
1.28M
                            const auto crsDatum(crs->datumNonNull(dbContext));
2870
1.28M
                            if (crsDatum->ellipsoid()->_isEquivalentTo(
2871
1.28M
                                    ellps.get(),
2872
1.28M
                                    util::IComparable::Criterion::EQUIVALENT,
2873
1.28M
                                    dbContext) &&
2874
1.27M
                                crsDatum->primeMeridian()->_isEquivalentTo(
2875
1.27M
                                    thisDatum->primeMeridian().get(),
2876
1.27M
                                    util::IComparable::Criterion::EQUIVALENT,
2877
1.27M
                                    dbContext) &&
2878
1.18M
                                (l_implicitCS ||
2879
1.04M
                                 coordinateSystem()->_isEquivalentTo(
2880
1.04M
                                     crs->coordinateSystem().get(),
2881
1.04M
                                     util::IComparable::Criterion::EQUIVALENT,
2882
1.04M
                                     dbContext))) {
2883
844k
                                res.emplace_back(crs, 60);
2884
844k
                            }
2885
1.28M
                        }
2886
38.1k
                    } catch (const std::exception &) {
2887
0
                    }
2888
38.1k
                }
2889
36.7k
            }
2890
35.1k
        };
2891
2892
68.7k
        const auto searchByDatumOrEllipsoid = [&authorityFactory, &thisDatum,
2893
68.7k
                                               searchByDatumCode,
2894
68.7k
                                               searchByEllipsoid]() {
2895
63.5k
            if (!thisDatum->identifiers().empty()) {
2896
28.1k
                searchByDatumCode(thisDatum);
2897
35.3k
            } else {
2898
35.3k
                auto candidateDatums = authorityFactory->createObjectsFromName(
2899
35.3k
                    thisDatum->nameStr(),
2900
35.3k
                    {io::AuthorityFactory::ObjectType::
2901
35.3k
                         GEODETIC_REFERENCE_FRAME},
2902
35.3k
                    false);
2903
35.3k
                bool resModified = false;
2904
35.3k
                for (const auto &candidateDatum : candidateDatums) {
2905
344
                    if (searchByDatumCode(candidateDatum))
2906
219
                        resModified = true;
2907
344
                }
2908
35.3k
                if (!resModified) {
2909
35.1k
                    searchByEllipsoid();
2910
35.1k
                }
2911
35.3k
            }
2912
63.5k
        };
2913
2914
68.7k
        const bool insignificantName = thisName.empty() ||
2915
66.1k
                                       ci_equal(thisName, "unknown") ||
2916
30.2k
                                       ci_equal(thisName, "unnamed");
2917
2918
68.7k
        if (insignificantName) {
2919
38.5k
            searchByDatumOrEllipsoid();
2920
38.5k
        } else if (hasCodeCompatibleOfAuthorityFactory(this,
2921
30.2k
                                                       authorityFactory)) {
2922
            // If the CRS has already an id, check in the database for the
2923
            // official object, and verify that they are equivalent.
2924
5.00k
            for (const auto &id : identifiers()) {
2925
5.00k
                if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) {
2926
5.00k
                    try {
2927
5.00k
                        auto crs = io::AuthorityFactory::create(
2928
5.00k
                                       authorityFactory->databaseContext(),
2929
5.00k
                                       *id->codeSpace())
2930
5.00k
                                       ->createGeodeticCRS(id->code());
2931
5.00k
                        bool match =
2932
5.00k
                            _isEquivalentTo(crs.get(), crsCriterion, dbContext);
2933
5.00k
                        res.emplace_back(crs, match ? 100 : 25);
2934
5.00k
                        return res;
2935
5.00k
                    } catch (const std::exception &) {
2936
671
                    }
2937
5.00k
                }
2938
5.00k
            }
2939
25.2k
        } else {
2940
25.2k
            bool gotAbove25Pct = false;
2941
58.0k
            for (int ipass = 0; ipass < 2; ipass++) {
2942
43.9k
                const bool approximateMatch = ipass == 1;
2943
43.9k
                auto objects = authorityFactory->createObjectsFromName(
2944
43.9k
                    thisName, {io::AuthorityFactory::ObjectType::GEODETIC_CRS},
2945
43.9k
                    approximateMatch);
2946
636k
                for (const auto &obj : objects) {
2947
636k
                    auto crs = util::nn_dynamic_pointer_cast<GeodeticCRS>(obj);
2948
636k
                    assert(crs);
2949
636k
                    auto crsNN = NN_NO_CHECK(crs);
2950
636k
                    if (_isEquivalentTo(crs.get(), crsCriterion, dbContext)) {
2951
205
                        if (crs->nameStr() == thisName) {
2952
37
                            res.clear();
2953
37
                            res.emplace_back(crsNN, 100);
2954
37
                            return res;
2955
37
                        }
2956
168
                        const bool eqName =
2957
168
                            metadata::Identifier::isEquivalentName(
2958
168
                                thisName.c_str(), crs->nameStr().c_str());
2959
168
                        res.emplace_back(crsNN, eqName ? 90 : 70);
2960
168
                        gotAbove25Pct = true;
2961
636k
                    } else {
2962
636k
                        res.emplace_back(crsNN, 25);
2963
636k
                    }
2964
636k
                }
2965
43.8k
                if (!res.empty()) {
2966
11.0k
                    break;
2967
11.0k
                }
2968
43.8k
            }
2969
25.1k
            if (!gotAbove25Pct) {
2970
25.0k
                searchByDatumOrEllipsoid();
2971
25.0k
            }
2972
25.1k
        }
2973
2974
64.3k
        const auto &thisCS(coordinateSystem());
2975
        // Sort results
2976
64.3k
        res.sort([&thisName, &thisDatum, &thisCS, &dbContext](const Pair &a,
2977
8.57M
                                                              const Pair &b) {
2978
            // First consider confidence
2979
8.57M
            if (a.second > b.second) {
2980
123k
                return true;
2981
123k
            }
2982
8.45M
            if (a.second < b.second) {
2983
75
                return false;
2984
75
            }
2985
2986
            // Then consider exact name matching
2987
8.45M
            const auto &aName(a.first->nameStr());
2988
8.45M
            const auto &bName(b.first->nameStr());
2989
8.45M
            if (aName == thisName && bName != thisName) {
2990
1.15k
                return true;
2991
1.15k
            }
2992
8.45M
            if (bName == thisName && aName != thisName) {
2993
1.26k
                return false;
2994
1.26k
            }
2995
2996
            // Then datum matching
2997
8.45M
            const auto aDatum(a.first->datumNonNull(dbContext));
2998
8.45M
            const auto bDatum(b.first->datumNonNull(dbContext));
2999
8.45M
            const auto thisEquivADatum(thisDatum->_isEquivalentTo(
3000
8.45M
                aDatum.get(), util::IComparable::Criterion::EQUIVALENT,
3001
8.45M
                dbContext));
3002
8.45M
            const auto thisEquivBDatum(thisDatum->_isEquivalentTo(
3003
8.45M
                bDatum.get(), util::IComparable::Criterion::EQUIVALENT,
3004
8.45M
                dbContext));
3005
3006
8.45M
            if (thisEquivADatum && !thisEquivBDatum) {
3007
38
                return true;
3008
38
            }
3009
8.45M
            if (!thisEquivADatum && thisEquivBDatum) {
3010
34
                return false;
3011
34
            }
3012
3013
            // Then coordinate system matching
3014
8.45M
            const auto &aCS(a.first->coordinateSystem());
3015
8.45M
            const auto &bCS(b.first->coordinateSystem());
3016
8.45M
            const auto thisEquivACs(thisCS->_isEquivalentTo(
3017
8.45M
                aCS.get(), util::IComparable::Criterion::EQUIVALENT,
3018
8.45M
                dbContext));
3019
8.45M
            const auto thisEquivBCs(thisCS->_isEquivalentTo(
3020
8.45M
                bCS.get(), util::IComparable::Criterion::EQUIVALENT,
3021
8.45M
                dbContext));
3022
8.45M
            if (thisEquivACs && !thisEquivBCs) {
3023
131k
                return true;
3024
131k
            }
3025
8.31M
            if (!thisEquivACs && thisEquivBCs) {
3026
115k
                return false;
3027
115k
            }
3028
3029
            // Then dimension of the coordinate system matching
3030
8.20M
            const auto thisCSAxisListSize = thisCS->axisList().size();
3031
8.20M
            const auto aCSAxistListSize = aCS->axisList().size();
3032
8.20M
            const auto bCSAxistListSize = bCS->axisList().size();
3033
8.20M
            if (thisCSAxisListSize == aCSAxistListSize &&
3034
6.74M
                thisCSAxisListSize != bCSAxistListSize) {
3035
131k
                return true;
3036
131k
            }
3037
8.07M
            if (thisCSAxisListSize != aCSAxistListSize &&
3038
1.45M
                thisCSAxisListSize == bCSAxistListSize) {
3039
123k
                return false;
3040
123k
            }
3041
3042
            // Favor the CRS whole ellipsoid names matches the ellipsoid
3043
            // name (WGS84...)
3044
7.94M
            const bool aEllpsNameEqCRSName =
3045
7.94M
                metadata::Identifier::isEquivalentName(
3046
7.94M
                    aDatum->ellipsoid()->nameStr().c_str(),
3047
7.94M
                    a.first->nameStr().c_str());
3048
7.94M
            const bool bEllpsNameEqCRSName =
3049
7.94M
                metadata::Identifier::isEquivalentName(
3050
7.94M
                    bDatum->ellipsoid()->nameStr().c_str(),
3051
7.94M
                    b.first->nameStr().c_str());
3052
7.94M
            if (aEllpsNameEqCRSName && !bEllpsNameEqCRSName) {
3053
37.7k
                return true;
3054
37.7k
            }
3055
7.91M
            if (bEllpsNameEqCRSName && !aEllpsNameEqCRSName) {
3056
39.3k
                return false;
3057
39.3k
            }
3058
3059
            // Arbitrary final sorting criterion
3060
7.87M
            return aName < bName;
3061
7.91M
        });
3062
3063
        // If there are results with 90% confidence, only keep those
3064
64.3k
        if (res.size() >= 2 && res.front().second == 90) {
3065
74
            std::list<Pair> newRes;
3066
148
            for (const auto &pair : res) {
3067
148
                if (pair.second == 90) {
3068
74
                    newRes.push_back(pair);
3069
74
                } else {
3070
74
                    break;
3071
74
                }
3072
148
            }
3073
74
            return newRes;
3074
74
        }
3075
64.3k
    }
3076
64.2k
    return res;
3077
68.7k
}
3078
3079
// ---------------------------------------------------------------------------
3080
3081
//! @cond Doxygen_Suppress
3082
3083
std::list<std::pair<CRSNNPtr, int>>
3084
48.5k
GeodeticCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const {
3085
48.5k
    typedef std::pair<CRSNNPtr, int> Pair;
3086
48.5k
    std::list<Pair> res;
3087
48.5k
    auto resTemp = identify(authorityFactory);
3088
951k
    for (const auto &pair : resTemp) {
3089
951k
        res.emplace_back(pair.first, pair.second);
3090
951k
    }
3091
48.5k
    return res;
3092
48.5k
}
3093
3094
//! @endcond
3095
3096
// ---------------------------------------------------------------------------
3097
3098
//! @cond Doxygen_Suppress
3099
struct GeographicCRS::Private {
3100
    cs::EllipsoidalCSNNPtr coordinateSystem_;
3101
3102
    explicit Private(const cs::EllipsoidalCSNNPtr &csIn)
3103
3.13M
        : coordinateSystem_(csIn) {}
3104
};
3105
//! @endcond
3106
3107
// ---------------------------------------------------------------------------
3108
3109
GeographicCRS::GeographicCRS(const datum::GeodeticReferenceFramePtr &datumIn,
3110
                             const datum::DatumEnsemblePtr &datumEnsembleIn,
3111
                             const cs::EllipsoidalCSNNPtr &csIn)
3112
3.13M
    : SingleCRS(datumIn, datumEnsembleIn, csIn),
3113
3.13M
      GeodeticCRS(datumIn,
3114
3.13M
                  checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn),
3115
3.13M
      d(std::make_unique<Private>(csIn)) {}
osgeo::proj::crs::GeographicCRS::GeographicCRS(std::__1::shared_ptr<osgeo::proj::datum::GeodeticReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::EllipsoidalCS> > const&)
Line
Count
Source
3112
29.6k
    : SingleCRS(datumIn, datumEnsembleIn, csIn),
3113
29.6k
      GeodeticCRS(datumIn,
3114
29.6k
                  checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn),
3115
29.6k
      d(std::make_unique<Private>(csIn)) {}
osgeo::proj::crs::GeographicCRS::GeographicCRS(std::__1::shared_ptr<osgeo::proj::datum::GeodeticReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::EllipsoidalCS> > const&)
Line
Count
Source
3112
3.10M
    : SingleCRS(datumIn, datumEnsembleIn, csIn),
3113
3.10M
      GeodeticCRS(datumIn,
3114
3.10M
                  checkEnsembleForGeodeticCRS(datumIn, datumEnsembleIn), csIn),
3115
3.10M
      d(std::make_unique<Private>(csIn)) {}
3116
3117
// ---------------------------------------------------------------------------
3118
3119
GeographicCRS::GeographicCRS(const GeographicCRS &other)
3120
18.3k
    : SingleCRS(other), GeodeticCRS(other),
3121
18.3k
      d(std::make_unique<Private>(*other.d)) {}
osgeo::proj::crs::GeographicCRS::GeographicCRS(osgeo::proj::crs::GeographicCRS const&)
Line
Count
Source
3120
97
    : SingleCRS(other), GeodeticCRS(other),
3121
97
      d(std::make_unique<Private>(*other.d)) {}
osgeo::proj::crs::GeographicCRS::GeographicCRS(osgeo::proj::crs::GeographicCRS const&)
Line
Count
Source
3120
18.2k
    : SingleCRS(other), GeodeticCRS(other),
3121
18.2k
      d(std::make_unique<Private>(*other.d)) {}
3122
3123
// ---------------------------------------------------------------------------
3124
3125
//! @cond Doxygen_Suppress
3126
3.15M
GeographicCRS::~GeographicCRS() = default;
3127
//! @endcond
3128
3129
// ---------------------------------------------------------------------------
3130
3131
18.2k
CRSNNPtr GeographicCRS::_shallowClone() const {
3132
18.2k
    auto crs(GeographicCRS::nn_make_shared<GeographicCRS>(*this));
3133
18.2k
    crs->assignSelf(crs);
3134
18.2k
    return crs;
3135
18.2k
}
3136
3137
// ---------------------------------------------------------------------------
3138
3139
/** \brief Return the cs::EllipsoidalCS associated with the CRS.
3140
 *
3141
 * @return a EllipsoidalCS.
3142
 */
3143
6.33M
const cs::EllipsoidalCSNNPtr &GeographicCRS::coordinateSystem() PROJ_PURE_DEFN {
3144
6.33M
    return d->coordinateSystem_;
3145
6.33M
}
3146
3147
// ---------------------------------------------------------------------------
3148
3149
/** \brief Instantiate a GeographicCRS from a datum::GeodeticReferenceFrameNNPtr
3150
 * and a
3151
 * cs::EllipsoidalCS.
3152
 *
3153
 * @param properties See \ref general_properties.
3154
 * At minimum the name should be defined.
3155
 * @param datum The datum of the CRS.
3156
 * @param cs a EllipsoidalCS.
3157
 * @return new GeographicCRS.
3158
 */
3159
GeographicCRSNNPtr
3160
GeographicCRS::create(const util::PropertyMap &properties,
3161
                      const datum::GeodeticReferenceFrameNNPtr &datum,
3162
650k
                      const cs::EllipsoidalCSNNPtr &cs) {
3163
650k
    return create(properties, datum.as_nullable(), nullptr, cs);
3164
650k
}
3165
3166
// ---------------------------------------------------------------------------
3167
3168
/** \brief Instantiate a GeographicCRS from a datum::GeodeticReferenceFramePtr
3169
 * or
3170
 * datum::DatumEnsemble and a
3171
 * cs::EllipsoidalCS.
3172
 *
3173
 * One and only one of datum or datumEnsemble should be set to a non-null value.
3174
 *
3175
 * @param properties See \ref general_properties.
3176
 * At minimum the name should be defined.
3177
 * @param datum The datum of the CRS, or nullptr
3178
 * @param datumEnsemble The datum ensemble of the CRS, or nullptr.
3179
 * @param cs a EllipsoidalCS.
3180
 * @return new GeographicCRS.
3181
 */
3182
GeographicCRSNNPtr
3183
GeographicCRS::create(const util::PropertyMap &properties,
3184
                      const datum::GeodeticReferenceFramePtr &datum,
3185
                      const datum::DatumEnsemblePtr &datumEnsemble,
3186
3.10M
                      const cs::EllipsoidalCSNNPtr &cs) {
3187
3.10M
    GeographicCRSNNPtr crs(
3188
3.10M
        GeographicCRS::nn_make_shared<GeographicCRS>(datum, datumEnsemble, cs));
3189
3.10M
    crs->assignSelf(crs);
3190
3.10M
    crs->setProperties(properties);
3191
3.10M
    crs->CRS::getPrivate()->setNonStandardProperties(properties);
3192
3.10M
    return crs;
3193
3.10M
}
3194
3195
// ---------------------------------------------------------------------------
3196
3197
//! @cond Doxygen_Suppress
3198
3199
/** \brief Return whether the current GeographicCRS is the 2D part of the
3200
 * other 3D GeographicCRS.
3201
 */
3202
bool GeographicCRS::is2DPartOf3D(util::nn<const GeographicCRS *> other,
3203
                                 const io::DatabaseContextPtr &dbContext)
3204
2.24k
    PROJ_PURE_DEFN {
3205
2.24k
    const auto &axis = d->coordinateSystem_->axisList();
3206
2.24k
    const auto &otherAxis = other->d->coordinateSystem_->axisList();
3207
2.24k
    if (!(axis.size() == 2 && otherAxis.size() == 3)) {
3208
0
        return false;
3209
0
    }
3210
2.24k
    const auto &firstAxis = axis[0];
3211
2.24k
    const auto &secondAxis = axis[1];
3212
2.24k
    const auto &otherFirstAxis = otherAxis[0];
3213
2.24k
    const auto &otherSecondAxis = otherAxis[1];
3214
2.24k
    if (!(firstAxis->_isEquivalentTo(
3215
2.24k
              otherFirstAxis.get(), util::IComparable::Criterion::EQUIVALENT) &&
3216
2.24k
          secondAxis->_isEquivalentTo(
3217
2.24k
              otherSecondAxis.get(),
3218
2.24k
              util::IComparable::Criterion::EQUIVALENT))) {
3219
0
        return false;
3220
0
    }
3221
2.24k
    try {
3222
2.24k
        const auto thisDatum = datumNonNull(dbContext);
3223
2.24k
        const auto otherDatum = other->datumNonNull(dbContext);
3224
2.24k
        return thisDatum->_isEquivalentTo(
3225
2.24k
            otherDatum.get(), util::IComparable::Criterion::EQUIVALENT);
3226
2.24k
    } catch (const util::InvalidValueTypeException &) {
3227
        // should not happen really, but potentially thrown by
3228
        // Identifier::Private::setProperties()
3229
0
        assert(false);
3230
0
        return false;
3231
0
    }
3232
2.24k
}
3233
3234
//! @endcond
3235
3236
// ---------------------------------------------------------------------------
3237
3238
//! @cond Doxygen_Suppress
3239
bool GeographicCRS::_isEquivalentTo(
3240
    const util::IComparable *other, util::IComparable::Criterion criterion,
3241
2.46M
    const io::DatabaseContextPtr &dbContext) const {
3242
2.46M
    if (other == nullptr || !util::isOfExactType<GeographicCRS>(*other)) {
3243
128k
        return false;
3244
128k
    }
3245
3246
2.33M
    const auto standardCriterion = getStandardCriterion(criterion);
3247
2.33M
    const auto otherGeogCRS = dynamic_cast<const GeographicCRS *>(other);
3248
2.33M
    if (GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion,
3249
2.33M
                                                dbContext)) {
3250
        // Make sure GeoPackage "Undefined geographic SRS" != EPSG:4326
3251
1.57M
        if ((nameStr() == "Undefined geographic SRS" ||
3252
1.57M
             otherGeogCRS->nameStr() == "Undefined geographic SRS") &&
3253
0
            otherGeogCRS->nameStr() != nameStr()) {
3254
0
            return false;
3255
0
        }
3256
1.57M
        return true;
3257
1.57M
    }
3258
3259
760k
    if (criterion !=
3260
760k
        util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS) {
3261
457k
        return false;
3262
457k
    }
3263
3264
302k
    const auto axisOrder = coordinateSystem()->axisOrder();
3265
302k
    if (axisOrder == cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH ||
3266
302k
        axisOrder == cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST) {
3267
302k
        const auto &unit = coordinateSystem()->axisList()[0]->unit();
3268
302k
        return GeographicCRS::create(
3269
302k
                   util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
3270
302k
                                           nameStr()),
3271
302k
                   datum(), datumEnsemble(),
3272
302k
                   axisOrder ==
3273
302k
                           cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH
3274
302k
                       ? cs::EllipsoidalCS::createLatitudeLongitude(unit)
3275
302k
                       : cs::EllipsoidalCS::createLongitudeLatitude(unit))
3276
302k
            ->GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion,
3277
302k
                                                      dbContext);
3278
302k
    }
3279
451
    if (axisOrder ==
3280
451
            cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH_HEIGHT_UP ||
3281
419
        axisOrder ==
3282
439
            cs::EllipsoidalCS::AxisOrder::LAT_NORTH_LONG_EAST_HEIGHT_UP) {
3283
439
        const auto &angularUnit = coordinateSystem()->axisList()[0]->unit();
3284
439
        const auto &linearUnit = coordinateSystem()->axisList()[2]->unit();
3285
439
        return GeographicCRS::create(
3286
439
                   util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
3287
439
                                           nameStr()),
3288
439
                   datum(), datumEnsemble(),
3289
439
                   axisOrder == cs::EllipsoidalCS::AxisOrder::
3290
439
                                    LONG_EAST_LAT_NORTH_HEIGHT_UP
3291
439
                       ? cs::EllipsoidalCS::
3292
20
                             createLatitudeLongitudeEllipsoidalHeight(
3293
20
                                 angularUnit, linearUnit)
3294
439
                       : cs::EllipsoidalCS::
3295
419
                             createLongitudeLatitudeEllipsoidalHeight(
3296
419
                                 angularUnit, linearUnit))
3297
439
            ->GeodeticCRS::_isEquivalentToNoTypeCheck(other, standardCriterion,
3298
439
                                                      dbContext);
3299
439
    }
3300
12
    return false;
3301
451
}
3302
//! @endcond
3303
3304
// ---------------------------------------------------------------------------
3305
3306
93
GeographicCRSNNPtr GeographicCRS::createEPSG_4267() {
3307
93
    return create(createMapNameEPSGCode("NAD27", 4267),
3308
93
                  datum::GeodeticReferenceFrame::EPSG_6267,
3309
93
                  cs::EllipsoidalCS::createLatitudeLongitude(
3310
93
                      common::UnitOfMeasure::DEGREE));
3311
93
}
3312
3313
// ---------------------------------------------------------------------------
3314
3315
93
GeographicCRSNNPtr GeographicCRS::createEPSG_4269() {
3316
93
    return create(createMapNameEPSGCode("NAD83", 4269),
3317
93
                  datum::GeodeticReferenceFrame::EPSG_6269,
3318
93
                  cs::EllipsoidalCS::createLatitudeLongitude(
3319
93
                      common::UnitOfMeasure::DEGREE));
3320
93
}
3321
3322
// ---------------------------------------------------------------------------
3323
3324
93
GeographicCRSNNPtr GeographicCRS::createEPSG_4326() {
3325
93
    return create(createMapNameEPSGCode("WGS 84", 4326),
3326
93
                  datum::GeodeticReferenceFrame::EPSG_6326,
3327
93
                  cs::EllipsoidalCS::createLatitudeLongitude(
3328
93
                      common::UnitOfMeasure::DEGREE));
3329
93
}
3330
3331
// ---------------------------------------------------------------------------
3332
3333
93
GeographicCRSNNPtr GeographicCRS::createOGC_CRS84() {
3334
93
    util::PropertyMap propertiesCRS;
3335
93
    propertiesCRS
3336
93
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::OGC)
3337
93
        .set(metadata::Identifier::CODE_KEY, "CRS84")
3338
93
        .set(common::IdentifiedObject::NAME_KEY, "WGS 84 (CRS84)");
3339
93
    return create(propertiesCRS, datum::GeodeticReferenceFrame::EPSG_6326,
3340
93
                  cs::EllipsoidalCS::createLongitudeLatitude( // Long Lat !
3341
93
                      common::UnitOfMeasure::DEGREE));
3342
93
}
3343
3344
// ---------------------------------------------------------------------------
3345
3346
93
GeographicCRSNNPtr GeographicCRS::createEPSG_4979() {
3347
93
    return create(
3348
93
        createMapNameEPSGCode("WGS 84", 4979),
3349
93
        datum::GeodeticReferenceFrame::EPSG_6326,
3350
93
        cs::EllipsoidalCS::createLatitudeLongitudeEllipsoidalHeight(
3351
93
            common::UnitOfMeasure::DEGREE, common::UnitOfMeasure::METRE));
3352
93
}
3353
3354
// ---------------------------------------------------------------------------
3355
3356
93
GeographicCRSNNPtr GeographicCRS::createEPSG_4807() {
3357
93
    auto ellps(datum::Ellipsoid::createFlattenedSphere(
3358
93
        createMapNameEPSGCode("Clarke 1880 (IGN)", 7011),
3359
93
        common::Length(6378249.2), common::Scale(293.4660212936269)));
3360
3361
93
    auto cs(cs::EllipsoidalCS::createLatitudeLongitude(
3362
93
        common::UnitOfMeasure::GRAD));
3363
3364
93
    auto datum(datum::GeodeticReferenceFrame::create(
3365
93
        createMapNameEPSGCode("Nouvelle Triangulation Francaise (Paris)", 6807),
3366
93
        ellps, util::optional<std::string>(), datum::PrimeMeridian::PARIS));
3367
3368
93
    return create(createMapNameEPSGCode("NTF (Paris)", 4807), datum, cs);
3369
93
}
3370
3371
// ---------------------------------------------------------------------------
3372
3373
/** \brief Return a variant of this CRS "demoted" to a 2D one, if not already
3374
 * the case.
3375
 *
3376
 *
3377
 * @param newName Name of the new CRS. If empty, nameStr() will be used.
3378
 * @param dbContext Database context to look for potentially already registered
3379
 *                  2D CRS. May be nullptr.
3380
 * @return a new CRS demoted to 2D, or the current one if already 2D or not
3381
 * applicable.
3382
 * @since 6.3
3383
 */
3384
GeographicCRSNNPtr
3385
GeographicCRS::demoteTo2D(const std::string &newName,
3386
23.8k
                          const io::DatabaseContextPtr &dbContext) const {
3387
3388
23.8k
    const auto &axisList = coordinateSystem()->axisList();
3389
23.8k
    if (axisList.size() == 3) {
3390
15.3k
        const auto &l_identifiers = identifiers();
3391
        // First check if there is a Geographic 2D CRS in the database
3392
        // of the same name.
3393
        // This is the common practice in the EPSG dataset.
3394
15.3k
        if (dbContext && l_identifiers.size() == 1) {
3395
141
            auto authFactory = io::AuthorityFactory::create(
3396
141
                NN_NO_CHECK(dbContext), *(l_identifiers[0]->codeSpace()));
3397
141
            auto res = authFactory->createObjectsFromName(
3398
141
                nameStr(),
3399
141
                {io::AuthorityFactory::ObjectType::GEOGRAPHIC_2D_CRS}, false);
3400
141
            if (!res.empty()) {
3401
122
                const auto &firstRes = res.front();
3402
122
                auto firstResAsGeogCRS =
3403
122
                    util::nn_dynamic_pointer_cast<GeographicCRS>(firstRes);
3404
122
                if (firstResAsGeogCRS && firstResAsGeogCRS->is2DPartOf3D(
3405
122
                                             NN_NO_CHECK(this), dbContext)) {
3406
116
                    return NN_NO_CHECK(firstResAsGeogCRS);
3407
116
                }
3408
122
            }
3409
141
        }
3410
3411
15.2k
        auto cs = cs::EllipsoidalCS::create(util::PropertyMap(), axisList[0],
3412
15.2k
                                            axisList[1]);
3413
15.2k
        return GeographicCRS::create(
3414
15.2k
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
3415
15.2k
                                    !newName.empty() ? newName : nameStr()),
3416
15.2k
            datum(), datumEnsemble(), cs);
3417
15.3k
    }
3418
3419
8.52k
    return NN_NO_CHECK(std::dynamic_pointer_cast<GeographicCRS>(
3420
23.8k
        shared_from_this().as_nullable()));
3421
23.8k
}
3422
3423
// ---------------------------------------------------------------------------
3424
3425
//! @cond Doxygen_Suppress
3426
void GeographicCRS::_exportToPROJString(
3427
    io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
3428
1.00M
{
3429
1.00M
    const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
3430
1.00M
    if (!extensionProj4.empty()) {
3431
23.4k
        formatter->ingestPROJString(
3432
23.4k
            replaceAll(extensionProj4, " +type=crs", ""));
3433
23.4k
        formatter->addNoDefs(false);
3434
23.4k
        return;
3435
23.4k
    }
3436
3437
977k
    if (!formatter->omitProjLongLatIfPossible() ||
3438
124k
        primeMeridian()->longitude().getSIValue() != 0.0 ||
3439
122k
        !formatter->getTOWGS84Parameters().empty() ||
3440
855k
        !formatter->getHDatumExtension().empty()) {
3441
3442
855k
        formatter->addStep("longlat");
3443
855k
        bool done = false;
3444
855k
        if (formatter->getLegacyCRSToCRSContext() &&
3445
250
            formatter->getHDatumExtension().empty() &&
3446
250
            formatter->getTOWGS84Parameters().empty()) {
3447
250
            const auto l_datum = datumNonNull(formatter->databaseContext());
3448
250
            if (l_datum->_isEquivalentTo(
3449
250
                    datum::GeodeticReferenceFrame::EPSG_6326.get(),
3450
250
                    util::IComparable::Criterion::EQUIVALENT)) {
3451
250
                done = true;
3452
250
                formatter->addParam("ellps", "WGS84");
3453
250
            } else if (l_datum->_isEquivalentTo(
3454
0
                           datum::GeodeticReferenceFrame::EPSG_6269.get(),
3455
0
                           util::IComparable::Criterion::EQUIVALENT)) {
3456
0
                done = true;
3457
                // We do not want datum=NAD83 to cause a useless towgs84=0,0,0
3458
0
                formatter->addParam("ellps", "GRS80");
3459
0
            }
3460
250
        }
3461
855k
        if (!done) {
3462
854k
            addDatumInfoToPROJString(formatter);
3463
854k
        }
3464
855k
    }
3465
977k
    if (!formatter->getCRSExport()) {
3466
974k
        addAngularUnitConvertAndAxisSwap(formatter);
3467
974k
    }
3468
977k
    if (hasOver()) {
3469
0
        formatter->addParam("over");
3470
0
    }
3471
977k
}
3472
//! @endcond
3473
3474
// ---------------------------------------------------------------------------
3475
3476
//! @cond Doxygen_Suppress
3477
void GeographicCRS::_exportToJSON(
3478
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
3479
75.3k
{
3480
75.3k
    _exportToJSONInternal(formatter, "GeographicCRS");
3481
75.3k
}
3482
//! @endcond
3483
3484
// ---------------------------------------------------------------------------
3485
3486
//! @cond Doxygen_Suppress
3487
struct VerticalCRS::Private {
3488
    std::vector<operation::TransformationNNPtr> geoidModel{};
3489
    std::vector<operation::PointMotionOperationNNPtr> velocityModel{};
3490
};
3491
//! @endcond
3492
3493
// ---------------------------------------------------------------------------
3494
3495
//! @cond Doxygen_Suppress
3496
static const datum::DatumEnsemblePtr &
3497
checkEnsembleForVerticalCRS(const datum::VerticalReferenceFramePtr &datumIn,
3498
304k
                            const datum::DatumEnsemblePtr &ensemble) {
3499
304k
    const char *msg = "One of Datum or DatumEnsemble should be defined";
3500
304k
    if (datumIn) {
3501
296k
        if (!ensemble) {
3502
296k
            return ensemble;
3503
296k
        }
3504
7
        msg = "Datum and DatumEnsemble should not be defined";
3505
8.79k
    } else if (ensemble) {
3506
8.79k
        const auto &datums = ensemble->datums();
3507
8.79k
        assert(!datums.empty());
3508
8.79k
        auto grfFirst =
3509
8.79k
            dynamic_cast<datum::VerticalReferenceFrame *>(datums[0].get());
3510
8.79k
        if (grfFirst) {
3511
8.79k
            return ensemble;
3512
8.79k
        }
3513
1
        msg = "Ensemble should contain VerticalReferenceFrame";
3514
1
    }
3515
8
    throw util::Exception(msg);
3516
304k
}
3517
//! @endcond
3518
3519
// ---------------------------------------------------------------------------
3520
3521
VerticalCRS::VerticalCRS(const datum::VerticalReferenceFramePtr &datumIn,
3522
                         const datum::DatumEnsemblePtr &datumEnsembleIn,
3523
                         const cs::VerticalCSNNPtr &csIn)
3524
304k
    : SingleCRS(datumIn, checkEnsembleForVerticalCRS(datumIn, datumEnsembleIn),
3525
304k
                csIn),
3526
304k
      d(std::make_unique<Private>()) {}
Unexecuted instantiation: osgeo::proj::crs::VerticalCRS::VerticalCRS(std::__1::shared_ptr<osgeo::proj::datum::VerticalReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::VerticalCS> > const&)
osgeo::proj::crs::VerticalCRS::VerticalCRS(std::__1::shared_ptr<osgeo::proj::datum::VerticalReferenceFrame> const&, std::__1::shared_ptr<osgeo::proj::datum::DatumEnsemble> const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::VerticalCS> > const&)
Line
Count
Source
3524
304k
    : SingleCRS(datumIn, checkEnsembleForVerticalCRS(datumIn, datumEnsembleIn),
3525
304k
                csIn),
3526
304k
      d(std::make_unique<Private>()) {}
3527
3528
// ---------------------------------------------------------------------------
3529
3530
VerticalCRS::VerticalCRS(const VerticalCRS &other)
3531
0
    : SingleCRS(other), d(std::make_unique<Private>(*other.d)) {}
Unexecuted instantiation: osgeo::proj::crs::VerticalCRS::VerticalCRS(osgeo::proj::crs::VerticalCRS const&)
Unexecuted instantiation: osgeo::proj::crs::VerticalCRS::VerticalCRS(osgeo::proj::crs::VerticalCRS const&)
3532
3533
// ---------------------------------------------------------------------------
3534
3535
//! @cond Doxygen_Suppress
3536
304k
VerticalCRS::~VerticalCRS() = default;
3537
//! @endcond
3538
3539
// ---------------------------------------------------------------------------
3540
3541
0
CRSNNPtr VerticalCRS::_shallowClone() const {
3542
0
    auto crs(VerticalCRS::nn_make_shared<VerticalCRS>(*this));
3543
0
    crs->assignSelf(crs);
3544
0
    return crs;
3545
0
}
3546
3547
// ---------------------------------------------------------------------------
3548
3549
/** \brief Return the datum::VerticalReferenceFrame associated with the CRS.
3550
 *
3551
 * @return a VerticalReferenceFrame.
3552
 */
3553
186k
const datum::VerticalReferenceFramePtr VerticalCRS::datum() const {
3554
186k
    return std::static_pointer_cast<datum::VerticalReferenceFrame>(
3555
186k
        SingleCRS::getPrivate()->datum);
3556
186k
}
3557
3558
// ---------------------------------------------------------------------------
3559
3560
/** \brief Return the geoid model associated with the CRS.
3561
 *
3562
 * Geoid height model or height correction model linked to a geoid-based
3563
 * vertical CRS.
3564
 *
3565
 * @return a geoid model. might be null
3566
 */
3567
const std::vector<operation::TransformationNNPtr> &
3568
4.81k
VerticalCRS::geoidModel() PROJ_PURE_DEFN {
3569
4.81k
    return d->geoidModel;
3570
4.81k
}
3571
3572
// ---------------------------------------------------------------------------
3573
3574
/** \brief Return the velocity model associated with the CRS.
3575
 *
3576
 * @return a velocity model. might be null.
3577
 */
3578
const std::vector<operation::PointMotionOperationNNPtr> &
3579
0
VerticalCRS::velocityModel() PROJ_PURE_DEFN {
3580
0
    return d->velocityModel;
3581
0
}
3582
3583
// ---------------------------------------------------------------------------
3584
3585
/** \brief Return the cs::VerticalCS associated with the CRS.
3586
 *
3587
 * @return a VerticalCS.
3588
 */
3589
62.3k
const cs::VerticalCSNNPtr VerticalCRS::coordinateSystem() const {
3590
62.3k
    return util::nn_static_pointer_cast<cs::VerticalCS>(
3591
62.3k
        SingleCRS::getPrivate()->coordinateSystem);
3592
62.3k
}
3593
3594
// ---------------------------------------------------------------------------
3595
3596
//! @cond Doxygen_Suppress
3597
/** \brief Return the real datum or a synthesized one if a datumEnsemble.
3598
 */
3599
const datum::VerticalReferenceFrameNNPtr
3600
54.6k
VerticalCRS::datumNonNull(const io::DatabaseContextPtr &dbContext) const {
3601
54.6k
    return NN_NO_CHECK(
3602
54.6k
        util::nn_dynamic_pointer_cast<datum::VerticalReferenceFrame>(
3603
54.6k
            SingleCRS::datumNonNull(dbContext)));
3604
54.6k
}
3605
//! @endcond
3606
3607
// ---------------------------------------------------------------------------
3608
3609
//! @cond Doxygen_Suppress
3610
73.1k
void VerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const {
3611
73.1k
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
3612
73.1k
    formatter->startNode(isWKT2 ? io::WKTConstants::VERTCRS
3613
73.1k
                         : formatter->useESRIDialect()
3614
62.9k
                             ? io::WKTConstants::VERTCS
3615
62.9k
                             : io::WKTConstants::VERT_CS,
3616
73.1k
                         !identifiers().empty());
3617
3618
73.1k
    std::string l_name(nameStr());
3619
73.1k
    const auto &dbContext = formatter->databaseContext();
3620
73.1k
    if (formatter->useESRIDialect()) {
3621
372
        bool aliasFound = false;
3622
372
        if (dbContext) {
3623
372
            auto l_alias = dbContext->getAliasFromOfficialName(
3624
372
                l_name, "vertical_crs", "ESRI");
3625
372
            if (!l_alias.empty()) {
3626
372
                l_name = std::move(l_alias);
3627
372
                aliasFound = true;
3628
372
            }
3629
372
        }
3630
372
        if (!aliasFound && dbContext) {
3631
0
            auto authFactory =
3632
0
                io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI");
3633
0
            aliasFound =
3634
0
                authFactory
3635
0
                    ->createObjectsFromName(
3636
0
                        l_name,
3637
0
                        {io::AuthorityFactory::ObjectType::VERTICAL_CRS},
3638
0
                        false // approximateMatch
3639
0
                        )
3640
0
                    .size() == 1;
3641
0
        }
3642
372
        if (!aliasFound) {
3643
0
            l_name = io::WKTFormatter::morphNameToESRI(l_name);
3644
0
        }
3645
372
    }
3646
3647
73.1k
    formatter->addQuotedString(l_name);
3648
3649
73.1k
    const auto l_datum = datum();
3650
73.1k
    if (formatter->useESRIDialect() && l_datum &&
3651
371
        l_datum->getWKT1DatumType() == "2002") {
3652
0
        bool foundMatch = false;
3653
0
        if (dbContext) {
3654
0
            auto authFactory = io::AuthorityFactory::create(
3655
0
                NN_NO_CHECK(dbContext), std::string());
3656
0
            auto list = authFactory->createObjectsFromName(
3657
0
                l_datum->nameStr(),
3658
0
                {io::AuthorityFactory::ObjectType::GEODETIC_REFERENCE_FRAME},
3659
0
                false /* approximate=false*/);
3660
0
            if (!list.empty()) {
3661
0
                auto gdatum =
3662
0
                    util::nn_dynamic_pointer_cast<datum::Datum>(list.front());
3663
0
                if (gdatum) {
3664
0
                    gdatum->_exportToWKT(formatter);
3665
0
                    foundMatch = true;
3666
0
                }
3667
0
            }
3668
0
        }
3669
0
        if (!foundMatch) {
3670
            // We should export a geodetic datum, but we cannot really do better
3671
0
            l_datum->_exportToWKT(formatter);
3672
0
        }
3673
73.1k
    } else {
3674
73.1k
        exportDatumOrDatumEnsembleToWkt(formatter);
3675
73.1k
    }
3676
73.1k
    const auto &cs = SingleCRS::getPrivate()->coordinateSystem;
3677
73.1k
    const auto &axisList = cs->axisList();
3678
3679
73.1k
    if (formatter->useESRIDialect()) {
3680
        // Seems to be a constant value...
3681
372
        formatter->startNode(io::WKTConstants::PARAMETER, false);
3682
372
        formatter->addQuotedString("Vertical_Shift");
3683
372
        formatter->add(0.0);
3684
372
        formatter->endNode();
3685
3686
372
        formatter->startNode(io::WKTConstants::PARAMETER, false);
3687
372
        formatter->addQuotedString("Direction");
3688
372
        formatter->add(
3689
372
            axisList[0]->direction() == cs::AxisDirection::UP ? 1.0 : -1.0);
3690
372
        formatter->endNode();
3691
372
    }
3692
3693
73.1k
    if (!isWKT2) {
3694
62.9k
        axisList[0]->unit()._exportToWKT(formatter);
3695
62.9k
    }
3696
3697
73.1k
    const auto oldAxisOutputRule = formatter->outputAxis();
3698
73.1k
    if (oldAxisOutputRule ==
3699
73.1k
        io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) {
3700
0
        formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
3701
0
    }
3702
73.1k
    cs->_exportToWKT(formatter);
3703
73.1k
    formatter->setOutputAxis(oldAxisOutputRule);
3704
3705
73.1k
    if (isWKT2 && formatter->use2019Keywords() && !d->geoidModel.empty()) {
3706
1.29k
        for (const auto &model : d->geoidModel) {
3707
1.29k
            formatter->startNode(io::WKTConstants::GEOIDMODEL, false);
3708
1.29k
            formatter->addQuotedString(model->nameStr());
3709
1.29k
            model->formatID(formatter);
3710
1.29k
            formatter->endNode();
3711
1.29k
        }
3712
499
    }
3713
3714
73.1k
    ObjectUsage::baseExportToWKT(formatter);
3715
73.1k
    formatter->endNode();
3716
73.1k
}
3717
//! @endcond
3718
3719
// ---------------------------------------------------------------------------
3720
3721
//! @cond Doxygen_Suppress
3722
void VerticalCRS::_exportToPROJString(
3723
    io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
3724
7.94k
{
3725
7.94k
    const auto &geoidgrids = formatter->getVDatumExtension();
3726
7.94k
    if (!geoidgrids.empty()) {
3727
4.74k
        formatter->addParam("geoidgrids", geoidgrids);
3728
4.74k
    }
3729
7.94k
    const auto &geoidCRS = formatter->getGeoidCRSValue();
3730
7.94k
    if (!geoidCRS.empty()) {
3731
4.74k
        formatter->addParam("geoid_crs", geoidCRS);
3732
4.74k
    }
3733
3734
7.94k
    auto &axisList = coordinateSystem()->axisList();
3735
7.94k
    if (!axisList.empty()) {
3736
7.94k
        auto projUnit = axisList[0]->unit().exportToPROJString();
3737
7.94k
        if (projUnit.empty()) {
3738
281
            formatter->addParam("vto_meter",
3739
281
                                axisList[0]->unit().conversionToSI());
3740
7.66k
        } else {
3741
7.66k
            formatter->addParam("vunits", projUnit);
3742
7.66k
        }
3743
7.94k
    }
3744
7.94k
}
3745
//! @endcond
3746
3747
// ---------------------------------------------------------------------------
3748
3749
//! @cond Doxygen_Suppress
3750
void VerticalCRS::_exportToJSON(
3751
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
3752
2
{
3753
2
    auto writer = formatter->writer();
3754
2
    auto objectContext(
3755
2
        formatter->MakeObjectContext("VerticalCRS", !identifiers().empty()));
3756
3757
2
    writer->AddObjKey("name");
3758
2
    const auto &l_name = nameStr();
3759
2
    if (l_name.empty()) {
3760
0
        writer->Add("unnamed");
3761
2
    } else {
3762
2
        writer->Add(l_name);
3763
2
    }
3764
3765
2
    const auto &l_datum(datum());
3766
2
    if (l_datum) {
3767
2
        writer->AddObjKey("datum");
3768
2
        l_datum->_exportToJSON(formatter);
3769
2
    } else {
3770
0
        writer->AddObjKey("datum_ensemble");
3771
0
        formatter->setOmitTypeInImmediateChild();
3772
0
        datumEnsemble()->_exportToJSON(formatter);
3773
0
    }
3774
3775
2
    writer->AddObjKey("coordinate_system");
3776
2
    formatter->setOmitTypeInImmediateChild();
3777
2
    coordinateSystem()->_exportToJSON(formatter);
3778
3779
2
    const auto geoidModelExport =
3780
2
        [&writer, &formatter](const operation::TransformationNNPtr &model) {
3781
0
            auto objectContext2(formatter->MakeObjectContext(nullptr, false));
3782
0
            writer->AddObjKey("name");
3783
0
            writer->Add(model->nameStr());
3784
3785
0
            if (model->identifiers().empty()) {
3786
0
                const auto &interpCRS = model->interpolationCRS();
3787
0
                if (interpCRS) {
3788
0
                    writer->AddObjKey("interpolation_crs");
3789
0
                    interpCRS->_exportToJSON(formatter);
3790
0
                }
3791
0
            }
3792
3793
0
            model->formatID(formatter);
3794
0
        };
3795
3796
2
    if (d->geoidModel.size() == 1) {
3797
0
        writer->AddObjKey("geoid_model");
3798
0
        geoidModelExport(d->geoidModel[0]);
3799
2
    } else if (d->geoidModel.size() > 1) {
3800
0
        writer->AddObjKey("geoid_models");
3801
0
        auto geoidModelsArrayContext(writer->MakeArrayContext(false));
3802
0
        for (const auto &model : d->geoidModel) {
3803
0
            geoidModelExport(model);
3804
0
        }
3805
0
    }
3806
3807
2
    if (const auto dynamicVRF =
3808
2
            dynamic_cast<datum::DynamicVerticalReferenceFrame *>(
3809
2
                l_datum.get())) {
3810
0
        const auto &deformationModel = dynamicVRF->deformationModelName();
3811
0
        if (deformationModel.has_value()) {
3812
0
            writer->AddObjKey("deformation_models");
3813
0
            auto arrayContext(writer->MakeArrayContext(false));
3814
0
            auto objectContext2(formatter->MakeObjectContext(nullptr, false));
3815
0
            writer->AddObjKey("name");
3816
0
            writer->Add(*deformationModel);
3817
0
        }
3818
0
    }
3819
3820
2
    ObjectUsage::baseExportToJSON(formatter);
3821
2
}
3822
//! @endcond
3823
3824
// ---------------------------------------------------------------------------
3825
3826
//! @cond Doxygen_Suppress
3827
void VerticalCRS::addLinearUnitConvert(
3828
248
    io::PROJStringFormatter *formatter) const {
3829
248
    auto &axisList = coordinateSystem()->axisList();
3830
3831
248
    if (!axisList.empty()) {
3832
248
        if (axisList[0]->unit().conversionToSI() != 1.0) {
3833
1
            formatter->addStep("unitconvert");
3834
1
            formatter->addParam("z_in", "m");
3835
1
            auto projVUnit = axisList[0]->unit().exportToPROJString();
3836
1
            if (projVUnit.empty()) {
3837
1
                formatter->addParam("z_out",
3838
1
                                    axisList[0]->unit().conversionToSI());
3839
1
            } else {
3840
0
                formatter->addParam("z_out", projVUnit);
3841
0
            }
3842
1
        }
3843
248
    }
3844
248
}
3845
//! @endcond
3846
3847
// ---------------------------------------------------------------------------
3848
3849
/** \brief Instantiate a VerticalCRS from a datum::VerticalReferenceFrame and a
3850
 * cs::VerticalCS.
3851
 *
3852
 * @param properties See \ref general_properties.
3853
 * At minimum the name should be defined. The GEOID_MODEL property can be set
3854
 * to a TransformationNNPtr object.
3855
 * @param datumIn The datum of the CRS.
3856
 * @param csIn a VerticalCS.
3857
 * @return new VerticalCRS.
3858
 */
3859
VerticalCRSNNPtr
3860
VerticalCRS::create(const util::PropertyMap &properties,
3861
                    const datum::VerticalReferenceFrameNNPtr &datumIn,
3862
75.0k
                    const cs::VerticalCSNNPtr &csIn) {
3863
75.0k
    return create(properties, datumIn.as_nullable(), nullptr, csIn);
3864
75.0k
}
3865
3866
// ---------------------------------------------------------------------------
3867
3868
/** \brief Instantiate a VerticalCRS from a datum::VerticalReferenceFrame or
3869
 * datum::DatumEnsemble and a cs::VerticalCS.
3870
 *
3871
 * One and only one of datum or datumEnsemble should be set to a non-null value.
3872
 *
3873
 * @param properties See \ref general_properties.
3874
 * At minimum the name should be defined. The GEOID_MODEL property can be set
3875
 * to a TransformationNNPtr object.
3876
 * @param datumIn The datum of the CRS, or nullptr
3877
 * @param datumEnsembleIn The datum ensemble of the CRS, or nullptr.
3878
 * @param csIn a VerticalCS.
3879
 * @return new VerticalCRS.
3880
 */
3881
VerticalCRSNNPtr
3882
VerticalCRS::create(const util::PropertyMap &properties,
3883
                    const datum::VerticalReferenceFramePtr &datumIn,
3884
                    const datum::DatumEnsemblePtr &datumEnsembleIn,
3885
304k
                    const cs::VerticalCSNNPtr &csIn) {
3886
304k
    auto crs(VerticalCRS::nn_make_shared<VerticalCRS>(datumIn, datumEnsembleIn,
3887
304k
                                                      csIn));
3888
304k
    crs->assignSelf(crs);
3889
304k
    crs->setProperties(properties);
3890
304k
    const auto geoidModelPtr = properties.get("GEOID_MODEL");
3891
304k
    if (geoidModelPtr) {
3892
16.3k
        if (auto array = util::nn_dynamic_pointer_cast<util::ArrayOfBaseObject>(
3893
16.3k
                *geoidModelPtr)) {
3894
25.2k
            for (const auto &item : *array) {
3895
25.2k
                auto transf =
3896
25.2k
                    util::nn_dynamic_pointer_cast<operation::Transformation>(
3897
25.2k
                        item);
3898
25.2k
                if (transf) {
3899
25.2k
                    crs->d->geoidModel.emplace_back(NN_NO_CHECK(transf));
3900
25.2k
                }
3901
25.2k
            }
3902
8.58k
        } else if (auto transf =
3903
7.80k
                       util::nn_dynamic_pointer_cast<operation::Transformation>(
3904
7.80k
                           *geoidModelPtr)) {
3905
7.80k
            crs->d->geoidModel.emplace_back(NN_NO_CHECK(transf));
3906
7.80k
        }
3907
16.3k
    }
3908
304k
    return crs;
3909
304k
}
3910
3911
// ---------------------------------------------------------------------------
3912
3913
//! @cond Doxygen_Suppress
3914
bool VerticalCRS::_isEquivalentTo(
3915
    const util::IComparable *other, util::IComparable::Criterion criterion,
3916
101k
    const io::DatabaseContextPtr &dbContext) const {
3917
101k
    auto otherVertCRS = dynamic_cast<const VerticalCRS *>(other);
3918
101k
    if (otherVertCRS == nullptr ||
3919
101k
        !util::isOfExactType<VerticalCRS>(*otherVertCRS)) {
3920
29
        return false;
3921
29
    }
3922
    // TODO test geoidModel and velocityModel
3923
101k
    return SingleCRS::baseIsEquivalentTo(other, criterion, dbContext);
3924
101k
}
3925
//! @endcond
3926
3927
// ---------------------------------------------------------------------------
3928
3929
/** \brief Identify the CRS with reference CRSs.
3930
 *
3931
 * The candidate CRSs are looked in the database when
3932
 * authorityFactory is not null.
3933
 *
3934
 * Note that the implementation uses a set of heuristics to have a good
3935
 * compromise of successful identifications over execution time. It might miss
3936
 * legitimate matches in some circumstances.
3937
 *
3938
 * The method returns a list of matching reference CRS, and the percentage
3939
 * (0-100) of confidence in the match.
3940
 * 100% means that the name of the reference entry
3941
 * perfectly matches the CRS name, and both are equivalent. In which case a
3942
 * single result is returned.
3943
 * 90% means that CRS are equivalent, but the names are not exactly the same.
3944
 * 70% means that CRS are equivalent (equivalent datum and coordinate system),
3945
 * but the names are not equivalent.
3946
 * 25% means that the CRS are not equivalent, but there is some similarity in
3947
 * the names.
3948
 *
3949
 * @param authorityFactory Authority factory (if null, will return an empty
3950
 * list)
3951
 * @return a list of matching reference CRS, and the percentage (0-100) of
3952
 * confidence in the match.
3953
 */
3954
std::list<std::pair<VerticalCRSNNPtr, int>>
3955
6.71k
VerticalCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
3956
6.71k
    typedef std::pair<VerticalCRSNNPtr, int> Pair;
3957
6.71k
    std::list<Pair> res;
3958
3959
6.71k
    const auto &thisName(nameStr());
3960
3961
6.71k
    if (authorityFactory) {
3962
6.71k
        const io::DatabaseContextNNPtr &dbContext =
3963
6.71k
            authorityFactory->databaseContext();
3964
3965
6.71k
        const bool insignificantName = thisName.empty() ||
3966
6.71k
                                       ci_equal(thisName, "unknown") ||
3967
6.71k
                                       ci_equal(thisName, "unnamed");
3968
6.71k
        if (hasCodeCompatibleOfAuthorityFactory(this, authorityFactory)) {
3969
            // If the CRS has already an id, check in the database for the
3970
            // official object, and verify that they are equivalent.
3971
6.65k
            for (const auto &id : identifiers()) {
3972
6.65k
                if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) {
3973
6.65k
                    try {
3974
6.65k
                        auto crs = io::AuthorityFactory::create(
3975
6.65k
                                       dbContext, *id->codeSpace())
3976
6.65k
                                       ->createVerticalCRS(id->code());
3977
6.65k
                        bool match = _isEquivalentTo(
3978
6.65k
                            crs.get(), util::IComparable::Criterion::EQUIVALENT,
3979
6.65k
                            dbContext);
3980
6.65k
                        res.emplace_back(crs, match ? 100 : 25);
3981
6.65k
                        return res;
3982
6.65k
                    } catch (const std::exception &) {
3983
5
                    }
3984
6.65k
                }
3985
6.65k
            }
3986
6.65k
        } else if (!insignificantName) {
3987
130
            for (int ipass = 0; ipass < 2; ipass++) {
3988
92
                const bool approximateMatch = ipass == 1;
3989
92
                auto objects = authorityFactory->createObjectsFromName(
3990
92
                    thisName, {io::AuthorityFactory::ObjectType::VERTICAL_CRS},
3991
92
                    approximateMatch);
3992
92
                for (const auto &obj : objects) {
3993
16
                    auto crs = util::nn_dynamic_pointer_cast<VerticalCRS>(obj);
3994
16
                    assert(crs);
3995
16
                    auto crsNN = NN_NO_CHECK(crs);
3996
16
                    if (_isEquivalentTo(
3997
16
                            crs.get(), util::IComparable::Criterion::EQUIVALENT,
3998
16
                            dbContext)) {
3999
16
                        if (crs->nameStr() == thisName) {
4000
16
                            res.clear();
4001
16
                            res.emplace_back(crsNN, 100);
4002
16
                            return res;
4003
16
                        }
4004
0
                        res.emplace_back(crsNN, 90);
4005
0
                    } else {
4006
0
                        res.emplace_back(crsNN, 25);
4007
0
                    }
4008
16
                }
4009
76
                if (!res.empty()) {
4010
0
                    break;
4011
0
                }
4012
76
            }
4013
54
        }
4014
4015
        // Sort results
4016
43
        res.sort([&thisName](const Pair &a, const Pair &b) {
4017
            // First consider confidence
4018
0
            if (a.second > b.second) {
4019
0
                return true;
4020
0
            }
4021
0
            if (a.second < b.second) {
4022
0
                return false;
4023
0
            }
4024
4025
            // Then consider exact name matching
4026
0
            const auto &aName(a.first->nameStr());
4027
0
            const auto &bName(b.first->nameStr());
4028
0
            if (aName == thisName && bName != thisName) {
4029
0
                return true;
4030
0
            }
4031
0
            if (bName == thisName && aName != thisName) {
4032
0
                return false;
4033
0
            }
4034
4035
            // Arbitrary final sorting criterion
4036
0
            return aName < bName;
4037
0
        });
4038
4039
        // Keep only results of the highest confidence
4040
43
        if (res.size() >= 2) {
4041
0
            const auto highestConfidence = res.front().second;
4042
0
            std::list<Pair> newRes;
4043
0
            for (const auto &pair : res) {
4044
0
                if (pair.second == highestConfidence) {
4045
0
                    newRes.push_back(pair);
4046
0
                } else {
4047
0
                    break;
4048
0
                }
4049
0
            }
4050
0
            return newRes;
4051
0
        }
4052
43
    }
4053
4054
43
    return res;
4055
6.71k
}
4056
4057
// ---------------------------------------------------------------------------
4058
4059
//! @cond Doxygen_Suppress
4060
4061
std::list<std::pair<CRSNNPtr, int>>
4062
6.71k
VerticalCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const {
4063
6.71k
    typedef std::pair<CRSNNPtr, int> Pair;
4064
6.71k
    std::list<Pair> res;
4065
6.71k
    auto resTemp = identify(authorityFactory);
4066
6.71k
    for (const auto &pair : resTemp) {
4067
6.67k
        res.emplace_back(pair.first, pair.second);
4068
6.67k
    }
4069
6.71k
    return res;
4070
6.71k
}
4071
4072
//! @endcond
4073
4074
// ---------------------------------------------------------------------------
4075
4076
//! @cond Doxygen_Suppress
4077
struct DerivedCRS::Private {
4078
    SingleCRSNNPtr baseCRS_;
4079
    operation::ConversionNNPtr derivingConversion_;
4080
4081
    Private(const SingleCRSNNPtr &baseCRSIn,
4082
            const operation::ConversionNNPtr &derivingConversionIn)
4083
1.79M
        : baseCRS_(baseCRSIn), derivingConversion_(derivingConversionIn) {}
4084
4085
    // For the conversion make a _shallowClone(), so that we can later set
4086
    // its targetCRS to this.
4087
    Private(const Private &other)
4088
13.1k
        : baseCRS_(other.baseCRS_),
4089
13.1k
          derivingConversion_(other.derivingConversion_->shallowClone()) {}
4090
};
4091
4092
//! @endcond
4093
4094
// ---------------------------------------------------------------------------
4095
4096
// DerivedCRS is an abstract class, that virtually inherits from SingleCRS
4097
// Consequently the base constructor in SingleCRS will never be called by
4098
// that constructor. clang -Wabstract-vbase-init and VC++ underline this, but
4099
// other
4100
// compilers will complain if we don't call the base constructor.
4101
4102
DerivedCRS::DerivedCRS(const SingleCRSNNPtr &baseCRSIn,
4103
                       const operation::ConversionNNPtr &derivingConversionIn,
4104
                       const cs::CoordinateSystemNNPtr &
4105
#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
4106
                           cs
4107
#endif
4108
                       )
4109
    :
4110
#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
4111
      SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), cs),
4112
#endif
4113
1.79M
      d(std::make_unique<Private>(baseCRSIn, derivingConversionIn)) {
4114
1.79M
}
4115
4116
// ---------------------------------------------------------------------------
4117
4118
DerivedCRS::DerivedCRS(const DerivedCRS &other)
4119
    :
4120
#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
4121
      SingleCRS(other),
4122
#endif
4123
13.1k
      d(std::make_unique<Private>(*other.d)) {
4124
13.1k
}
4125
4126
// ---------------------------------------------------------------------------
4127
4128
//! @cond Doxygen_Suppress
4129
1.80M
DerivedCRS::~DerivedCRS() = default;
4130
//! @endcond
4131
4132
// ---------------------------------------------------------------------------
4133
4134
/** \brief Return the base CRS of a DerivedCRS.
4135
 *
4136
 * @return the base CRS.
4137
 */
4138
1.94M
const SingleCRSNNPtr &DerivedCRS::baseCRS() PROJ_PURE_DEFN {
4139
1.94M
    return d->baseCRS_;
4140
1.94M
}
4141
4142
// ---------------------------------------------------------------------------
4143
4144
/** \brief Return the deriving conversion from the base CRS to this CRS.
4145
 *
4146
 * @return the deriving conversion.
4147
 */
4148
475k
const operation::ConversionNNPtr DerivedCRS::derivingConversion() const {
4149
475k
    return d->derivingConversion_->shallowClone();
4150
475k
}
4151
4152
// ---------------------------------------------------------------------------
4153
4154
//! @cond Doxygen_Suppress
4155
const operation::ConversionNNPtr &
4156
2.89M
DerivedCRS::derivingConversionRef() PROJ_PURE_DEFN {
4157
2.89M
    return d->derivingConversion_;
4158
2.89M
}
4159
//! @endcond
4160
4161
// ---------------------------------------------------------------------------
4162
4163
bool DerivedCRS::_isEquivalentTo(
4164
    const util::IComparable *other, util::IComparable::Criterion criterion,
4165
695k
    const io::DatabaseContextPtr &dbContext) const {
4166
695k
    auto otherDerivedCRS = dynamic_cast<const DerivedCRS *>(other);
4167
695k
    const auto standardCriterion = getStandardCriterion(criterion);
4168
695k
    if (otherDerivedCRS == nullptr ||
4169
695k
        !SingleCRS::baseIsEquivalentTo(other, standardCriterion, dbContext)) {
4170
629k
        return false;
4171
629k
    }
4172
66.3k
    return d->baseCRS_->_isEquivalentTo(otherDerivedCRS->d->baseCRS_.get(),
4173
66.3k
                                        criterion, dbContext) &&
4174
59.3k
           d->derivingConversion_->_isEquivalentTo(
4175
59.3k
               otherDerivedCRS->d->derivingConversion_.get(), standardCriterion,
4176
59.3k
               dbContext);
4177
695k
}
4178
4179
// ---------------------------------------------------------------------------
4180
4181
1.80M
void DerivedCRS::setDerivingConversionCRS() {
4182
1.80M
    derivingConversionRef()->setWeakSourceTargetCRS(
4183
1.80M
        baseCRS().as_nullable(),
4184
1.80M
        std::static_pointer_cast<CRS>(shared_from_this().as_nullable()));
4185
1.80M
}
4186
4187
// ---------------------------------------------------------------------------
4188
4189
void DerivedCRS::baseExportToWKT(io::WKTFormatter *formatter,
4190
                                 const std::string &keyword,
4191
0
                                 const std::string &baseKeyword) const {
4192
0
    formatter->startNode(keyword, !identifiers().empty());
4193
0
    formatter->addQuotedString(nameStr());
4194
4195
0
    const auto &l_baseCRS = d->baseCRS_;
4196
0
    formatter->startNode(baseKeyword, formatter->use2019Keywords() &&
4197
0
                                          !l_baseCRS->identifiers().empty());
4198
0
    formatter->addQuotedString(l_baseCRS->nameStr());
4199
0
    l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter);
4200
0
    if (formatter->use2019Keywords() &&
4201
0
        !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())) {
4202
0
        l_baseCRS->formatID(formatter);
4203
0
    }
4204
0
    formatter->endNode();
4205
4206
0
    formatter->setUseDerivingConversion(true);
4207
0
    derivingConversionRef()->_exportToWKT(formatter);
4208
0
    formatter->setUseDerivingConversion(false);
4209
4210
0
    coordinateSystem()->_exportToWKT(formatter);
4211
0
    ObjectUsage::baseExportToWKT(formatter);
4212
0
    formatter->endNode();
4213
0
}
4214
4215
// ---------------------------------------------------------------------------
4216
4217
//! @cond Doxygen_Suppress
4218
void DerivedCRS::_exportToJSON(
4219
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
4220
4
{
4221
4
    auto writer = formatter->writer();
4222
4
    auto objectContext(
4223
4
        formatter->MakeObjectContext(className(), !identifiers().empty()));
4224
4225
4
    writer->AddObjKey("name");
4226
4
    const auto &l_name = nameStr();
4227
4
    if (l_name.empty()) {
4228
0
        writer->Add("unnamed");
4229
4
    } else {
4230
4
        writer->Add(l_name);
4231
4
    }
4232
4233
4
    writer->AddObjKey("base_crs");
4234
4
    baseCRS()->_exportToJSON(formatter);
4235
4236
4
    writer->AddObjKey("conversion");
4237
4
    formatter->setOmitTypeInImmediateChild();
4238
4
    derivingConversionRef()->_exportToJSON(formatter);
4239
4240
4
    writer->AddObjKey("coordinate_system");
4241
4
    formatter->setOmitTypeInImmediateChild();
4242
4
    coordinateSystem()->_exportToJSON(formatter);
4243
4244
4
    ObjectUsage::baseExportToJSON(formatter);
4245
4
}
4246
//! @endcond
4247
4248
// ---------------------------------------------------------------------------
4249
4250
//! @cond Doxygen_Suppress
4251
struct ProjectedCRS::Private {
4252
    GeodeticCRSNNPtr baseCRS_;
4253
    cs::CartesianCSNNPtr cs_;
4254
    Private(const GeodeticCRSNNPtr &baseCRSIn, const cs::CartesianCSNNPtr &csIn)
4255
1.77M
        : baseCRS_(baseCRSIn), cs_(csIn) {}
4256
4257
4.38M
    inline const GeodeticCRSNNPtr &baseCRS() const { return baseCRS_; }
4258
4259
1.31M
    inline const cs::CartesianCSNNPtr &coordinateSystem() const { return cs_; }
4260
};
4261
//! @endcond
4262
4263
// ---------------------------------------------------------------------------
4264
4265
ProjectedCRS::ProjectedCRS(
4266
    const GeodeticCRSNNPtr &baseCRSIn,
4267
    const operation::ConversionNNPtr &derivingConversionIn,
4268
    const cs::CartesianCSNNPtr &csIn)
4269
1.76M
    : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
4270
1.76M
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn),
4271
1.76M
      d(std::make_unique<Private>(baseCRSIn, csIn)) {}
Unexecuted instantiation: osgeo::proj::crs::ProjectedCRS::ProjectedCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::GeodeticCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CartesianCS> > const&)
osgeo::proj::crs::ProjectedCRS::ProjectedCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::GeodeticCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CartesianCS> > const&)
Line
Count
Source
4269
1.76M
    : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
4270
1.76M
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn),
4271
1.76M
      d(std::make_unique<Private>(baseCRSIn, csIn)) {}
4272
4273
// ---------------------------------------------------------------------------
4274
4275
ProjectedCRS::ProjectedCRS(const ProjectedCRS &other)
4276
13.0k
    : SingleCRS(other), DerivedCRS(other),
4277
13.0k
      d(std::make_unique<Private>(other.baseCRS(), other.coordinateSystem())) {}
Unexecuted instantiation: osgeo::proj::crs::ProjectedCRS::ProjectedCRS(osgeo::proj::crs::ProjectedCRS const&)
osgeo::proj::crs::ProjectedCRS::ProjectedCRS(osgeo::proj::crs::ProjectedCRS const&)
Line
Count
Source
4276
13.0k
    : SingleCRS(other), DerivedCRS(other),
4277
13.0k
      d(std::make_unique<Private>(other.baseCRS(), other.coordinateSystem())) {}
4278
4279
// ---------------------------------------------------------------------------
4280
4281
//! @cond Doxygen_Suppress
4282
1.77M
ProjectedCRS::~ProjectedCRS() = default;
4283
//! @endcond
4284
4285
// ---------------------------------------------------------------------------
4286
4287
13.0k
CRSNNPtr ProjectedCRS::_shallowClone() const {
4288
13.0k
    auto crs(ProjectedCRS::nn_make_shared<ProjectedCRS>(*this));
4289
13.0k
    crs->assignSelf(crs);
4290
13.0k
    crs->setDerivingConversionCRS();
4291
13.0k
    return crs;
4292
13.0k
}
4293
4294
// ---------------------------------------------------------------------------
4295
4296
/** \brief Return the base CRS (a GeodeticCRS, which is generally a
4297
 * GeographicCRS) of the ProjectedCRS.
4298
 *
4299
 * @return the base CRS.
4300
 */
4301
4.08M
const GeodeticCRSNNPtr &ProjectedCRS::baseCRS() PROJ_PURE_DEFN {
4302
4.08M
    return d->baseCRS();
4303
4.08M
}
4304
4305
// ---------------------------------------------------------------------------
4306
4307
/** \brief Return the cs::CartesianCS associated with the CRS.
4308
 *
4309
 * @return a CartesianCS
4310
 */
4311
836k
const cs::CartesianCSNNPtr &ProjectedCRS::coordinateSystem() PROJ_PURE_DEFN {
4312
836k
    return d->coordinateSystem();
4313
836k
}
4314
4315
// ---------------------------------------------------------------------------
4316
4317
//! @cond Doxygen_Suppress
4318
314k
void ProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
4319
314k
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
4320
4321
314k
    const auto &l_identifiers = identifiers();
4322
    // Try to perfectly round-trip ESRI projectedCRS if the current object
4323
    // perfectly matches the database definition
4324
314k
    const auto &dbContext = formatter->databaseContext();
4325
4326
314k
    std::string l_name(nameStr());
4327
314k
    const auto &l_coordinateSystem = d->coordinateSystem();
4328
314k
    const auto &axisList = l_coordinateSystem->axisList();
4329
314k
    if (axisList.size() == 3 && !(isWKT2 && formatter->use2019Keywords())) {
4330
15.6k
        auto projCRS2D = demoteTo2D(std::string(), dbContext);
4331
15.6k
        if (dbContext) {
4332
15.6k
            const auto res = projCRS2D->identify(io::AuthorityFactory::create(
4333
15.6k
                NN_NO_CHECK(dbContext), metadata::Identifier::EPSG));
4334
15.6k
            if (res.size() == 1) {
4335
2.42k
                const auto &front = res.front();
4336
2.42k
                if (front.second == 100) {
4337
0
                    projCRS2D = front.first;
4338
0
                }
4339
2.42k
            }
4340
15.6k
        }
4341
4342
15.6k
        if (formatter->useESRIDialect() && dbContext) {
4343
            // Try to format the ProjecteD 3D CRS as a
4344
            // PROJCS[],VERTCS[...,DATUM[]]
4345
            // if we find corresponding objects
4346
0
            if (exportAsESRIWktCompoundCRSWithEllipsoidalHeight(
4347
0
                    this, baseCRS().as_nullable().get(), formatter)) {
4348
0
                return;
4349
0
            }
4350
0
        }
4351
4352
15.6k
        if (!formatter->useESRIDialect() &&
4353
15.6k
            CRS::getPrivate()->allowNonConformantWKT1Export_) {
4354
0
            formatter->startNode(io::WKTConstants::COMPD_CS, false);
4355
0
            formatter->addQuotedString(l_name + " + " + baseCRS()->nameStr());
4356
0
            projCRS2D->_exportToWKT(formatter);
4357
0
            baseCRS()
4358
0
                ->demoteTo2D(std::string(), dbContext)
4359
0
                ->_exportToWKT(formatter);
4360
0
            formatter->endNode();
4361
0
            return;
4362
0
        }
4363
4364
15.6k
        auto &originalCompoundCRS = CRS::getPrivate()->originalCompoundCRS_;
4365
15.6k
        if (!formatter->useESRIDialect() && originalCompoundCRS) {
4366
12
            originalCompoundCRS->_exportToWKT(formatter);
4367
12
            return;
4368
12
        }
4369
4370
15.5k
        if (!formatter->useESRIDialect() &&
4371
15.5k
            formatter->isAllowedEllipsoidalHeightAsVerticalCRS()) {
4372
0
            exportAsWKT1CompoundCRSWithEllipsoidalHeight(projCRS2D, axisList[2],
4373
0
                                                         formatter);
4374
0
            return;
4375
0
        }
4376
4377
15.5k
        io::FormattingException::Throw(
4378
15.5k
            "Projected 3D CRS can only be exported since WKT2:2019");
4379
15.5k
    }
4380
4381
298k
    std::string l_esri_name;
4382
298k
    if (formatter->useESRIDialect() && dbContext) {
4383
4384
1.01k
        if (!l_identifiers.empty()) {
4385
            // Try to find the ESRI alias from the CRS identified by its id
4386
944
            const auto aliases = dbContext->getAliases(
4387
944
                *(l_identifiers[0]->codeSpace()), l_identifiers[0]->code(),
4388
944
                std::string(), // officialName,
4389
944
                "projected_crs", "ESRI");
4390
944
            if (aliases.size() == 1)
4391
835
                l_esri_name = aliases.front();
4392
944
        }
4393
1.01k
        if (l_esri_name.empty()) {
4394
            // Then find the ESRI alias from the CRS name
4395
175
            l_esri_name = dbContext->getAliasFromOfficialName(
4396
175
                l_name, "projected_crs", "ESRI");
4397
175
        }
4398
1.01k
        if (l_esri_name.empty()) {
4399
            // Then try to build an ESRI CRS from the CRS name, and if there's
4400
            // one, the ESRI name is the CRS name
4401
173
            auto authFactory =
4402
173
                io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI");
4403
173
            const bool found =
4404
173
                authFactory
4405
173
                    ->createObjectsFromName(
4406
173
                        l_name,
4407
173
                        {io::AuthorityFactory::ObjectType::PROJECTED_CRS},
4408
173
                        false // approximateMatch
4409
173
                        )
4410
173
                    .size() == 1;
4411
173
            if (found)
4412
1
                l_esri_name = l_name;
4413
173
        }
4414
4415
1.01k
        if (!isWKT2 && !l_identifiers.empty() &&
4416
944
            *(l_identifiers[0]->codeSpace()) == "ESRI") {
4417
1
            try {
4418
                // If the id of the object is in the ESRI namespace, then
4419
                // try to find the full ESRI WKT from the database
4420
1
                const auto definition = dbContext->getTextDefinition(
4421
1
                    "projected_crs", "ESRI", l_identifiers[0]->code());
4422
1
                if (starts_with(definition, "PROJCS")) {
4423
0
                    auto crsFromFromDef = io::WKTParser()
4424
0
                                              .attachDatabaseContext(dbContext)
4425
0
                                              .createFromWKT(definition);
4426
0
                    if (_isEquivalentTo(
4427
0
                            dynamic_cast<IComparable *>(crsFromFromDef.get()),
4428
0
                            util::IComparable::Criterion::EQUIVALENT)) {
4429
0
                        formatter->ingestWKTNode(
4430
0
                            io::WKTNode::createFrom(definition));
4431
0
                        return;
4432
0
                    }
4433
0
                }
4434
1
            } catch (const std::exception &) {
4435
0
            }
4436
1.00k
        } else if (!isWKT2 && !l_esri_name.empty()) {
4437
837
            try {
4438
837
                auto res =
4439
837
                    io::AuthorityFactory::create(NN_NO_CHECK(dbContext), "ESRI")
4440
837
                        ->createObjectsFromName(
4441
837
                            l_esri_name,
4442
837
                            {io::AuthorityFactory::ObjectType::PROJECTED_CRS},
4443
837
                            false);
4444
837
                if (res.size() == 1) {
4445
213
                    const auto definition = dbContext->getTextDefinition(
4446
213
                        "projected_crs", "ESRI",
4447
213
                        res.front()->identifiers()[0]->code());
4448
213
                    if (starts_with(definition, "PROJCS")) {
4449
65
                        if (_isEquivalentTo(
4450
65
                                dynamic_cast<IComparable *>(res.front().get()),
4451
65
                                util::IComparable::Criterion::EQUIVALENT)) {
4452
65
                            formatter->ingestWKTNode(
4453
65
                                io::WKTNode::createFrom(definition));
4454
65
                            return;
4455
65
                        }
4456
65
                    }
4457
213
                }
4458
837
            } catch (const std::exception &) {
4459
0
            }
4460
837
        }
4461
1.01k
    }
4462
4463
298k
    const auto exportAxis = [&l_coordinateSystem, &axisList, &formatter]() {
4464
291k
        const auto oldAxisOutputRule = formatter->outputAxis();
4465
291k
        if (oldAxisOutputRule ==
4466
291k
            io::WKTFormatter::OutputAxisRule::WKT1_GDAL_EPSG_STYLE) {
4467
159
            if (&axisList[0]->direction() == &cs::AxisDirection::EAST &&
4468
148
                &axisList[1]->direction() == &cs::AxisDirection::NORTH) {
4469
148
                formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
4470
148
            }
4471
159
        }
4472
291k
        l_coordinateSystem->_exportToWKT(formatter);
4473
291k
        formatter->setOutputAxis(oldAxisOutputRule);
4474
291k
    };
4475
4476
298k
    if (!isWKT2 && !formatter->useESRIDialect() &&
4477
266k
        starts_with(nameStr(), "Popular Visualisation CRS / Mercator")) {
4478
587
        formatter->startNode(io::WKTConstants::PROJCS, !l_identifiers.empty());
4479
587
        formatter->addQuotedString(nameStr());
4480
587
        formatter->setTOWGS84Parameters({0, 0, 0, 0, 0, 0, 0});
4481
587
        baseCRS()->_exportToWKT(formatter);
4482
587
        formatter->setTOWGS84Parameters({});
4483
4484
587
        formatter->startNode(io::WKTConstants::PROJECTION, false);
4485
587
        formatter->addQuotedString("Mercator_1SP");
4486
587
        formatter->endNode();
4487
4488
587
        formatter->startNode(io::WKTConstants::PARAMETER, false);
4489
587
        formatter->addQuotedString("central_meridian");
4490
587
        formatter->add(0.0);
4491
587
        formatter->endNode();
4492
4493
587
        formatter->startNode(io::WKTConstants::PARAMETER, false);
4494
587
        formatter->addQuotedString("scale_factor");
4495
587
        formatter->add(1.0);
4496
587
        formatter->endNode();
4497
4498
587
        formatter->startNode(io::WKTConstants::PARAMETER, false);
4499
587
        formatter->addQuotedString("false_easting");
4500
587
        formatter->add(0.0);
4501
587
        formatter->endNode();
4502
4503
587
        formatter->startNode(io::WKTConstants::PARAMETER, false);
4504
587
        formatter->addQuotedString("false_northing");
4505
587
        formatter->add(0.0);
4506
587
        formatter->endNode();
4507
4508
587
        axisList[0]->unit()._exportToWKT(formatter);
4509
587
        exportAxis();
4510
587
        derivingConversionRef()->addWKTExtensionNode(formatter);
4511
587
        ObjectUsage::baseExportToWKT(formatter);
4512
587
        formatter->endNode();
4513
587
        return;
4514
587
    }
4515
4516
297k
    formatter->startNode(isWKT2 ? io::WKTConstants::PROJCRS
4517
297k
                                : io::WKTConstants::PROJCS,
4518
297k
                         !l_identifiers.empty());
4519
4520
297k
    if (formatter->useESRIDialect()) {
4521
945
        if (l_esri_name.empty()) {
4522
172
            l_name = io::WKTFormatter::morphNameToESRI(l_name);
4523
773
        } else {
4524
773
            const std::string &l_esri_name_ref(l_esri_name);
4525
773
            l_name = l_esri_name_ref;
4526
773
        }
4527
945
    }
4528
297k
    if (!isWKT2 && !formatter->useESRIDialect() && isDeprecated()) {
4529
654
        l_name += " (deprecated)";
4530
654
    }
4531
297k
    formatter->addQuotedString(l_name);
4532
4533
297k
    const auto &l_baseCRS = d->baseCRS();
4534
297k
    const auto &geodeticCRSAxisList = l_baseCRS->coordinateSystem()->axisList();
4535
4536
297k
    if (isWKT2) {
4537
30.7k
        formatter->startNode(
4538
30.7k
            (formatter->use2019Keywords() &&
4539
30.6k
             dynamic_cast<const GeographicCRS *>(l_baseCRS.get()))
4540
30.7k
                ? io::WKTConstants::BASEGEOGCRS
4541
30.7k
                : io::WKTConstants::BASEGEODCRS,
4542
30.7k
            formatter->use2019Keywords() && !l_baseCRS->identifiers().empty());
4543
30.7k
        formatter->addQuotedString(l_baseCRS->nameStr());
4544
30.7k
        l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter);
4545
        // insert ellipsoidal cs unit when the units of the map
4546
        // projection angular parameters are not explicitly given within those
4547
        // parameters. See
4548
        // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61
4549
30.7k
        if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis()) {
4550
0
            geodeticCRSAxisList[0]->unit()._exportToWKT(formatter);
4551
0
        }
4552
30.7k
        l_baseCRS->primeMeridian()->_exportToWKT(formatter);
4553
30.7k
        if (formatter->use2019Keywords() &&
4554
30.6k
            !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())) {
4555
30.6k
            l_baseCRS->formatID(formatter);
4556
30.6k
        }
4557
30.7k
        formatter->endNode();
4558
267k
    } else {
4559
267k
        const auto oldAxisOutputRule = formatter->outputAxis();
4560
267k
        formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::NO);
4561
267k
        l_baseCRS->_exportToWKT(formatter);
4562
267k
        formatter->setOutputAxis(oldAxisOutputRule);
4563
267k
    }
4564
4565
297k
    formatter->pushAxisLinearUnit(
4566
297k
        common::UnitOfMeasure::create(axisList[0]->unit()));
4567
4568
297k
    formatter->pushAxisAngularUnit(
4569
297k
        common::UnitOfMeasure::create(geodeticCRSAxisList[0]->unit()));
4570
4571
297k
    derivingConversionRef()->_exportToWKT(formatter);
4572
4573
297k
    formatter->popAxisAngularUnit();
4574
4575
297k
    formatter->popAxisLinearUnit();
4576
4577
297k
    if (!isWKT2) {
4578
259k
        axisList[0]->unit()._exportToWKT(formatter);
4579
259k
    }
4580
4581
297k
    exportAxis();
4582
4583
297k
    if (!isWKT2 && !formatter->useESRIDialect()) {
4584
258k
        const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
4585
258k
        if (!extensionProj4.empty()) {
4586
9.23k
            formatter->startNode(io::WKTConstants::EXTENSION, false);
4587
9.23k
            formatter->addQuotedString("PROJ4");
4588
9.23k
            formatter->addQuotedString(extensionProj4);
4589
9.23k
            formatter->endNode();
4590
249k
        } else {
4591
249k
            derivingConversionRef()->addWKTExtensionNode(formatter);
4592
249k
        }
4593
258k
    }
4594
4595
297k
    ObjectUsage::baseExportToWKT(formatter);
4596
297k
    formatter->endNode();
4597
297k
    return;
4598
298k
}
4599
//! @endcond
4600
4601
// ---------------------------------------------------------------------------
4602
4603
//! @cond Doxygen_Suppress
4604
void ProjectedCRS::_exportToJSON(
4605
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
4606
37.3k
{
4607
37.3k
    auto writer = formatter->writer();
4608
37.3k
    auto objectContext(
4609
37.3k
        formatter->MakeObjectContext("ProjectedCRS", !identifiers().empty()));
4610
4611
37.3k
    writer->AddObjKey("name");
4612
37.3k
    const auto &l_name = nameStr();
4613
37.3k
    if (l_name.empty()) {
4614
0
        writer->Add("unnamed");
4615
37.3k
    } else {
4616
37.3k
        writer->Add(l_name);
4617
37.3k
    }
4618
4619
37.3k
    writer->AddObjKey("base_crs");
4620
37.3k
    formatter->setAllowIDInImmediateChild();
4621
37.3k
    baseCRS()->_exportToJSON(formatter);
4622
4623
37.3k
    writer->AddObjKey("conversion");
4624
37.3k
    formatter->setOmitTypeInImmediateChild();
4625
37.3k
    derivingConversionRef()->_exportToJSON(formatter);
4626
4627
37.3k
    writer->AddObjKey("coordinate_system");
4628
37.3k
    formatter->setOmitTypeInImmediateChild();
4629
37.3k
    coordinateSystem()->_exportToJSON(formatter);
4630
4631
37.3k
    ObjectUsage::baseExportToJSON(formatter);
4632
37.3k
}
4633
//! @endcond
4634
4635
// ---------------------------------------------------------------------------
4636
4637
void ProjectedCRS::_exportToPROJString(
4638
    io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
4639
67.1k
{
4640
67.1k
    const auto &extensionProj4 = CRS::getPrivate()->extensionProj4_;
4641
67.1k
    if (!extensionProj4.empty()) {
4642
17.5k
        formatter->ingestPROJString(
4643
17.5k
            replaceAll(extensionProj4, " +type=crs", ""));
4644
17.5k
        formatter->addNoDefs(false);
4645
17.5k
        return;
4646
17.5k
    }
4647
4648
49.5k
    derivingConversionRef()->_exportToPROJString(formatter);
4649
49.5k
}
4650
4651
// ---------------------------------------------------------------------------
4652
4653
/** \brief Instantiate a ProjectedCRS from a base CRS, a deriving
4654
 * operation::Conversion
4655
 * and a coordinate system.
4656
 *
4657
 * @param properties See \ref general_properties.
4658
 * At minimum the name should be defined.
4659
 * @param baseCRSIn The base CRS, a GeodeticCRS that is generally a
4660
 * GeographicCRS.
4661
 * @param derivingConversionIn The deriving operation::Conversion (typically
4662
 * using a map
4663
 * projection method)
4664
 * @param csIn The coordniate system.
4665
 * @return new ProjectedCRS.
4666
 */
4667
ProjectedCRSNNPtr
4668
ProjectedCRS::create(const util::PropertyMap &properties,
4669
                     const GeodeticCRSNNPtr &baseCRSIn,
4670
                     const operation::ConversionNNPtr &derivingConversionIn,
4671
1.76M
                     const cs::CartesianCSNNPtr &csIn) {
4672
1.76M
    auto crs = ProjectedCRS::nn_make_shared<ProjectedCRS>(
4673
1.76M
        baseCRSIn, derivingConversionIn, csIn);
4674
1.76M
    crs->assignSelf(crs);
4675
1.76M
    crs->setProperties(properties);
4676
1.76M
    crs->setDerivingConversionCRS();
4677
1.76M
    crs->CRS::getPrivate()->setNonStandardProperties(properties);
4678
1.76M
    return crs;
4679
1.76M
}
4680
4681
// ---------------------------------------------------------------------------
4682
4683
bool ProjectedCRS::_isEquivalentTo(
4684
    const util::IComparable *other, util::IComparable::Criterion criterion,
4685
772k
    const io::DatabaseContextPtr &dbContext) const {
4686
772k
    auto otherProjCRS = dynamic_cast<const ProjectedCRS *>(other);
4687
772k
    if (otherProjCRS != nullptr &&
4688
695k
        criterion == util::IComparable::Criterion::EQUIVALENT &&
4689
2.27k
        (d->baseCRS_->hasImplicitCS() ||
4690
2.22k
         otherProjCRS->d->baseCRS_->hasImplicitCS())) {
4691
        // If one of the 2 base CRS has implicit coordinate system, then
4692
        // relax the check. The axis order of the base CRS doesn't matter
4693
        // for most purposes.
4694
174
        criterion =
4695
174
            util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS;
4696
174
    }
4697
772k
    return other != nullptr && util::isOfExactType<ProjectedCRS>(*other) &&
4698
695k
           DerivedCRS::_isEquivalentTo(other, criterion, dbContext);
4699
772k
}
4700
4701
// ---------------------------------------------------------------------------
4702
4703
//! @cond Doxygen_Suppress
4704
ProjectedCRSNNPtr
4705
ProjectedCRS::alterParametersLinearUnit(const common::UnitOfMeasure &unit,
4706
129k
                                        bool convertToNewUnit) const {
4707
129k
    return create(
4708
129k
        createPropertyMap(this), baseCRS(),
4709
129k
        derivingConversion()->alterParametersLinearUnit(unit, convertToNewUnit),
4710
129k
        coordinateSystem());
4711
129k
}
4712
//! @endcond
4713
4714
// ---------------------------------------------------------------------------
4715
4716
//! @cond Doxygen_Suppress
4717
void ProjectedCRS::addUnitConvertAndAxisSwap(io::PROJStringFormatter *formatter,
4718
166k
                                             bool axisSpecFound) const {
4719
166k
    ProjectedCRS::addUnitConvertAndAxisSwap(d->coordinateSystem()->axisList(),
4720
166k
                                            formatter, axisSpecFound);
4721
166k
}
4722
4723
void ProjectedCRS::addUnitConvertAndAxisSwap(
4724
    const std::vector<cs::CoordinateSystemAxisNNPtr> &axisListIn,
4725
166k
    io::PROJStringFormatter *formatter, bool axisSpecFound) {
4726
166k
    const auto &unit = axisListIn[0]->unit();
4727
166k
    const auto *zUnit =
4728
166k
        axisListIn.size() == 3 ? &(axisListIn[2]->unit()) : nullptr;
4729
166k
    if (!unit._isEquivalentTo(common::UnitOfMeasure::METRE,
4730
166k
                              util::IComparable::Criterion::EQUIVALENT) ||
4731
151k
        (zUnit &&
4732
1.01k
         !zUnit->_isEquivalentTo(common::UnitOfMeasure::METRE,
4733
15.4k
                                 util::IComparable::Criterion::EQUIVALENT))) {
4734
15.4k
        auto projUnit = unit.exportToPROJString();
4735
15.4k
        const double toSI = unit.conversionToSI();
4736
15.4k
        if (!formatter->getCRSExport()) {
4737
7.71k
            formatter->addStep("unitconvert");
4738
7.71k
            formatter->addParam("xy_in", "m");
4739
7.71k
            if (zUnit)
4740
0
                formatter->addParam("z_in", "m");
4741
4742
7.71k
            if (projUnit.empty()) {
4743
566
                formatter->addParam("xy_out", toSI);
4744
7.14k
            } else {
4745
7.14k
                formatter->addParam("xy_out", projUnit);
4746
7.14k
            }
4747
7.71k
            if (zUnit) {
4748
0
                auto projZUnit = zUnit->exportToPROJString();
4749
0
                const double zToSI = zUnit->conversionToSI();
4750
0
                if (projZUnit.empty()) {
4751
0
                    formatter->addParam("z_out", zToSI);
4752
0
                } else {
4753
0
                    formatter->addParam("z_out", projZUnit);
4754
0
                }
4755
0
            }
4756
7.78k
        } else {
4757
7.78k
            if (projUnit.empty()) {
4758
4.47k
                formatter->addParam("to_meter", toSI);
4759
4.47k
            } else {
4760
3.31k
                formatter->addParam("units", projUnit);
4761
3.31k
            }
4762
7.78k
        }
4763
151k
    } else if (formatter->getCRSExport() &&
4764
39.4k
               !formatter->getLegacyCRSToCRSContext()) {
4765
39.4k
        formatter->addParam("units", "m");
4766
39.4k
    }
4767
4768
166k
    if (!axisSpecFound &&
4769
165k
        (!formatter->getCRSExport() || formatter->getLegacyCRSToCRSContext())) {
4770
119k
        const auto &dir0 = axisListIn[0]->direction();
4771
119k
        const auto &dir1 = axisListIn[1]->direction();
4772
119k
        if (!(&dir0 == &cs::AxisDirection::EAST &&
4773
94.3k
              &dir1 == &cs::AxisDirection::NORTH) &&
4774
            // For polar projections, that have south+south direction,
4775
            // we don't want to mess with axes.
4776
25.0k
            dir0 != dir1) {
4777
4778
5.40k
            const char *order[2] = {nullptr, nullptr};
4779
16.2k
            for (int i = 0; i < 2; i++) {
4780
10.8k
                const auto &dir = axisListIn[i]->direction();
4781
10.8k
                if (&dir == &cs::AxisDirection::WEST)
4782
52
                    order[i] = "-1";
4783
10.7k
                else if (&dir == &cs::AxisDirection::EAST)
4784
5.35k
                    order[i] = "1";
4785
5.40k
                else if (&dir == &cs::AxisDirection::SOUTH)
4786
52
                    order[i] = "-2";
4787
5.35k
                else if (&dir == &cs::AxisDirection::NORTH)
4788
5.35k
                    order[i] = "2";
4789
10.8k
            }
4790
4791
5.40k
            if (order[0] && order[1]) {
4792
5.40k
                formatter->addStep("axisswap");
4793
5.40k
                char orderStr[10];
4794
5.40k
                snprintf(orderStr, sizeof(orderStr), "%.2s,%.2s", order[0],
4795
5.40k
                         order[1]);
4796
5.40k
                formatter->addParam("order", orderStr);
4797
5.40k
            }
4798
114k
        } else {
4799
114k
            const auto &name0 = axisListIn[0]->nameStr();
4800
114k
            const auto &name1 = axisListIn[1]->nameStr();
4801
114k
            const bool northingEasting = ci_starts_with(name0, "northing") &&
4802
1.16k
                                         ci_starts_with(name1, "easting");
4803
            // case of EPSG:32661 ["WGS 84 / UPS North (N,E)]"
4804
            // case of EPSG:32761 ["WGS 84 / UPS South (N,E)]"
4805
114k
            if (((&dir0 == &cs::AxisDirection::SOUTH &&
4806
14.0k
                  &dir1 == &cs::AxisDirection::SOUTH) ||
4807
100k
                 (&dir0 == &cs::AxisDirection::NORTH &&
4808
5.69k
                  &dir1 == &cs::AxisDirection::NORTH)) &&
4809
19.6k
                northingEasting) {
4810
1.16k
                formatter->addStep("axisswap");
4811
1.16k
                formatter->addParam("order", "2,1");
4812
1.16k
            }
4813
114k
        }
4814
119k
    }
4815
166k
}
4816
//! @endcond
4817
4818
// ---------------------------------------------------------------------------
4819
4820
/** \brief Identify the CRS with reference CRSs.
4821
 *
4822
 * The candidate CRSs are either hard-coded, or looked in the database when
4823
 * authorityFactory is not null.
4824
 *
4825
 * Note that the implementation uses a set of heuristics to have a good
4826
 * compromise of successful identifications over execution time. It might miss
4827
 * legitimate matches in some circumstances.
4828
 *
4829
 * The method returns a list of matching reference CRS, and the percentage
4830
 * (0-100) of confidence in the match. The list is sorted by decreasing
4831
 * confidence.
4832
 *
4833
 * 100% means that the name of the reference entry
4834
 * perfectly matches the CRS name, and both are equivalent. In which case a
4835
 * single result is returned.
4836
 * 90% means that CRS are equivalent, but the names are not exactly the same.
4837
 * 70% means that CRS are equivalent (equivalent base CRS, conversion and
4838
 * coordinate system), but the names are not equivalent.
4839
 * 60% means that CRS have strong similarity (equivalent base datum, conversion
4840
 * and coordinate system), but the names are not equivalent.
4841
 * 50% means that CRS have similarity (equivalent base ellipsoid and
4842
 * conversion),
4843
 * but the coordinate system do not match (e.g. different axis ordering or
4844
 * axis unit).
4845
 * 25% means that the CRS are not equivalent, but there is some similarity in
4846
 * the names.
4847
 *
4848
 * For the purpose of this function, equivalence is tested with the
4849
 * util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS, that is
4850
 * to say that the axis order of the base GeographicCRS is ignored.
4851
 *
4852
 * @param authorityFactory Authority factory (or null, but degraded
4853
 * functionality)
4854
 * @return a list of matching reference CRS, and the percentage (0-100) of
4855
 * confidence in the match.
4856
 */
4857
std::list<std::pair<ProjectedCRSNNPtr, int>>
4858
31.3k
ProjectedCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
4859
31.3k
    typedef std::pair<ProjectedCRSNNPtr, int> Pair;
4860
31.3k
    std::list<Pair> res;
4861
4862
31.3k
    const auto &thisName(nameStr());
4863
31.3k
    io::DatabaseContextPtr dbContext =
4864
31.3k
        authorityFactory ? authorityFactory->databaseContext().as_nullable()
4865
31.3k
                         : nullptr;
4866
4867
31.3k
    std::list<std::pair<GeodeticCRSNNPtr, int>> baseRes;
4868
31.3k
    const auto &l_baseCRS(baseCRS());
4869
31.3k
    const auto l_datum = l_baseCRS->datumNonNull(dbContext);
4870
31.3k
    const bool significantNameForDatum =
4871
31.3k
        !ci_starts_with(l_datum->nameStr(), "unknown") &&
4872
26.5k
        l_datum->nameStr() != "unnamed";
4873
31.3k
    const auto &ellipsoid = l_baseCRS->ellipsoid();
4874
31.3k
    auto geogCRS = dynamic_cast<const GeographicCRS *>(l_baseCRS.get());
4875
31.3k
    if (geogCRS && geogCRS->coordinateSystem()->axisOrder() ==
4876
28.6k
                       cs::EllipsoidalCS::AxisOrder::LONG_EAST_LAT_NORTH) {
4877
16.6k
        baseRes =
4878
16.6k
            GeographicCRS::create(
4879
16.6k
                util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
4880
16.6k
                                        geogCRS->nameStr()),
4881
16.6k
                geogCRS->datum(), geogCRS->datumEnsemble(),
4882
16.6k
                cs::EllipsoidalCS::createLatitudeLongitude(
4883
16.6k
                    geogCRS->coordinateSystem()->axisList()[0]->unit()))
4884
16.6k
                ->identify(authorityFactory);
4885
16.6k
    } else {
4886
14.6k
        baseRes = l_baseCRS->identify(authorityFactory);
4887
14.6k
    }
4888
4889
31.3k
    int zone = 0;
4890
31.3k
    bool north = false;
4891
4892
31.3k
    auto computeConfidence = [&thisName](const std::string &crsName) {
4893
548
        return crsName == thisName ? 100
4894
548
               : metadata::Identifier::isEquivalentName(crsName.c_str(),
4895
423
                                                        thisName.c_str())
4896
423
                   ? 90
4897
423
                   : 70;
4898
548
    };
4899
4900
31.3k
    const auto &conv = derivingConversionRef();
4901
31.3k
    const auto &cs = coordinateSystem();
4902
4903
31.3k
    if (baseRes.size() == 1 && baseRes.front().second >= 70 &&
4904
14.8k
        (authorityFactory == nullptr ||
4905
14.8k
         authorityFactory->getAuthority().empty() ||
4906
7.75k
         authorityFactory->getAuthority() == metadata::Identifier::EPSG) &&
4907
14.8k
        conv->isUTM(zone, north) &&
4908
1.14k
        cs->_isEquivalentTo(
4909
1.14k
            cs::CartesianCS::createEastingNorthing(common::UnitOfMeasure::METRE)
4910
1.14k
                .get(),
4911
1.14k
            util::IComparable::Criterion::EQUIVALENT, dbContext)) {
4912
4913
1.12k
        auto computeUTMCRSName = [](const char *base, int l_zone,
4914
1.12k
                                    bool l_north) {
4915
548
            return base + toString(l_zone) + (l_north ? "N" : "S");
4916
548
        };
4917
4918
1.12k
        if (baseRes.front().first->_isEquivalentTo(
4919
1.12k
                GeographicCRS::EPSG_4326.get(),
4920
1.12k
                util::IComparable::Criterion::EQUIVALENT, dbContext)) {
4921
397
            std::string crsName(
4922
397
                computeUTMCRSName("WGS 84 / UTM zone ", zone, north));
4923
397
            res.emplace_back(
4924
397
                ProjectedCRS::create(
4925
397
                    createMapNameEPSGCode(crsName.c_str(),
4926
397
                                          (north ? 32600 : 32700) + zone),
4927
397
                    GeographicCRS::EPSG_4326, conv->identify(), cs),
4928
397
                computeConfidence(crsName));
4929
397
            return res;
4930
729
        } else if (((zone >= 1 && zone <= 22) || zone == 59 || zone == 60) &&
4931
262
                   north &&
4932
189
                   baseRes.front().first->_isEquivalentTo(
4933
189
                       GeographicCRS::EPSG_4267.get(),
4934
189
                       util::IComparable::Criterion::EQUIVALENT, dbContext)) {
4935
126
            std::string crsName(
4936
126
                computeUTMCRSName("NAD27 / UTM zone ", zone, north));
4937
126
            res.emplace_back(
4938
126
                ProjectedCRS::create(
4939
126
                    createMapNameEPSGCode(crsName.c_str(),
4940
126
                                          (zone >= 59) ? 3370 + zone - 59
4941
126
                                                       : 26700 + zone),
4942
126
                    GeographicCRS::EPSG_4267, conv->identify(), cs),
4943
126
                computeConfidence(crsName));
4944
126
            return res;
4945
603
        } else if (((zone >= 1 && zone <= 23) || zone == 59 || zone == 60) &&
4946
159
                   north &&
4947
86
                   baseRes.front().first->_isEquivalentTo(
4948
86
                       GeographicCRS::EPSG_4269.get(),
4949
86
                       util::IComparable::Criterion::EQUIVALENT, dbContext)) {
4950
25
            std::string crsName(
4951
25
                computeUTMCRSName("NAD83 / UTM zone ", zone, north));
4952
25
            res.emplace_back(
4953
25
                ProjectedCRS::create(
4954
25
                    createMapNameEPSGCode(crsName.c_str(),
4955
25
                                          (zone >= 59) ? 3372 + zone - 59
4956
25
                                                       : 26900 + zone),
4957
25
                    GeographicCRS::EPSG_4269, conv->identify(), cs),
4958
25
                computeConfidence(crsName));
4959
25
            return res;
4960
25
        }
4961
1.12k
    }
4962
4963
30.7k
    const bool l_implicitCS = hasImplicitCS();
4964
30.7k
    const auto addCRS = [&](const ProjectedCRSNNPtr &crs, const bool eqName,
4965
458k
                            bool hasNonMatchingId) -> Pair {
4966
458k
        const auto &l_unit = cs->axisList()[0]->unit();
4967
458k
        if ((_isEquivalentTo(crs.get(),
4968
458k
                             util::IComparable::Criterion::
4969
458k
                                 EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
4970
458k
                             dbContext) ||
4971
455k
             (l_implicitCS &&
4972
52.5k
              l_unit._isEquivalentTo(
4973
52.5k
                  crs->coordinateSystem()->axisList()[0]->unit(),
4974
52.5k
                  util::IComparable::Criterion::EQUIVALENT) &&
4975
36.5k
              l_baseCRS->_isEquivalentTo(
4976
36.5k
                  crs->baseCRS().get(),
4977
36.5k
                  util::IComparable::Criterion::
4978
36.5k
                      EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
4979
36.5k
                  dbContext) &&
4980
7.78k
              derivingConversionRef()->_isEquivalentTo(
4981
7.78k
                  crs->derivingConversionRef().get(),
4982
7.78k
                  util::IComparable::Criterion::EQUIVALENT, dbContext))) &&
4983
2.83k
            !((baseCRS()->datumNonNull(dbContext)->nameStr() == "unknown" &&
4984
0
               crs->baseCRS()->datumNonNull(dbContext)->nameStr() !=
4985
0
                   "unknown") ||
4986
2.83k
              (baseCRS()->datumNonNull(dbContext)->nameStr() != "unknown" &&
4987
2.83k
               crs->baseCRS()->datumNonNull(dbContext)->nameStr() ==
4988
2.83k
                   "unknown"))) {
4989
2.83k
            if (crs->nameStr() == thisName) {
4990
7
                res.clear();
4991
7
                res.emplace_back(crs, hasNonMatchingId ? 70 : 100);
4992
2.82k
            } else {
4993
2.82k
                res.emplace_back(crs, eqName ? 90 : 70);
4994
2.82k
            }
4995
455k
        } else if (ellipsoid->_isEquivalentTo(
4996
455k
                       crs->baseCRS()->ellipsoid().get(),
4997
455k
                       util::IComparable::Criterion::EQUIVALENT, dbContext) &&
4998
148k
                   derivingConversionRef()->_isEquivalentTo(
4999
148k
                       crs->derivingConversionRef().get(),
5000
148k
                       util::IComparable::Criterion::EQUIVALENT, dbContext)) {
5001
5.00k
            if ((l_implicitCS &&
5002
19
                 l_unit._isEquivalentTo(
5003
19
                     crs->coordinateSystem()->axisList()[0]->unit(),
5004
19
                     util::IComparable::Criterion::EQUIVALENT)) ||
5005
4.98k
                cs->_isEquivalentTo(crs->coordinateSystem().get(),
5006
4.98k
                                    util::IComparable::Criterion::EQUIVALENT,
5007
4.98k
                                    dbContext)) {
5008
2.96k
                if (!significantNameForDatum ||
5009
630
                    l_datum->_isEquivalentTo(
5010
630
                        crs->baseCRS()->datumNonNull(dbContext).get(),
5011
2.35k
                        util::IComparable::Criterion::EQUIVALENT)) {
5012
2.35k
                    res.emplace_back(crs, 70);
5013
2.35k
                } else {
5014
614
                    res.emplace_back(crs, 60);
5015
614
                }
5016
2.96k
            } else {
5017
2.03k
                res.emplace_back(crs, 50);
5018
2.03k
            }
5019
450k
        } else {
5020
450k
            res.emplace_back(crs, 25);
5021
450k
        }
5022
458k
        return res.back();
5023
458k
    };
5024
5025
30.7k
    if (authorityFactory) {
5026
5027
30.7k
        const bool insignificantName = thisName.empty() ||
5028
30.5k
                                       ci_equal(thisName, "unknown") ||
5029
16.6k
                                       ci_equal(thisName, "unnamed");
5030
30.7k
        bool foundEquivalentName = false;
5031
5032
30.7k
        bool hasNonMatchingId = false;
5033
30.7k
        if (hasCodeCompatibleOfAuthorityFactory(this, authorityFactory)) {
5034
            // If the CRS has already an id, check in the database for the
5035
            // official object, and verify that they are equivalent.
5036
10.5k
            for (const auto &id : identifiers()) {
5037
10.5k
                if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) {
5038
10.5k
                    try {
5039
10.5k
                        auto crs = io::AuthorityFactory::create(
5040
10.5k
                                       authorityFactory->databaseContext(),
5041
10.5k
                                       *id->codeSpace())
5042
10.5k
                                       ->createProjectedCRS(id->code());
5043
10.5k
                        bool match = _isEquivalentTo(
5044
10.5k
                            crs.get(),
5045
10.5k
                            util::IComparable::Criterion::
5046
10.5k
                                EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS,
5047
10.5k
                            dbContext);
5048
10.5k
                        res.emplace_back(crs, match ? 100 : 25);
5049
10.5k
                        if (match) {
5050
834
                            return res;
5051
834
                        }
5052
10.5k
                    } catch (const std::exception &) {
5053
5.12k
                    }
5054
10.5k
                }
5055
10.5k
            }
5056
4.84k
            hasNonMatchingId = true;
5057
25.0k
        } else if (!insignificantName) {
5058
3.64k
            const double semiMajorAxis =
5059
3.64k
                ellipsoid->semiMajorAxis().getSIValue();
5060
9.63k
            for (int ipass = 0; ipass < 2; ipass++) {
5061
7.00k
                const bool approximateMatch = ipass == 1;
5062
7.00k
                auto objects = authorityFactory->createObjectsFromNameEx(
5063
7.00k
                    thisName, {io::AuthorityFactory::ObjectType::PROJECTED_CRS},
5064
7.00k
                    approximateMatch);
5065
420k
                for (const auto &pairObjName : objects) {
5066
420k
                    auto crs = util::nn_dynamic_pointer_cast<ProjectedCRS>(
5067
420k
                        pairObjName.first);
5068
420k
                    assert(crs);
5069
420k
                    auto crsNN = NN_NO_CHECK(crs);
5070
420k
                    const bool eqName = metadata::Identifier::isEquivalentName(
5071
420k
                        thisName.c_str(), pairObjName.second.c_str());
5072
420k
                    foundEquivalentName |= eqName;
5073
5074
420k
                    if (std::fabs(semiMajorAxis - crsNN->baseCRS()
5075
420k
                                                      ->ellipsoid()
5076
420k
                                                      ->semiMajorAxis()
5077
420k
                                                      .getSIValue()) <=
5078
420k
                        1e-4 * semiMajorAxis) {
5079
237k
                        if (addCRS(crsNN, eqName, false).second == 100) {
5080
1
                            return res;
5081
1
                        }
5082
237k
                    }
5083
420k
                }
5084
7.00k
                if (!res.empty()) {
5085
1.01k
                    break;
5086
1.01k
                }
5087
7.00k
            }
5088
3.64k
        }
5089
5090
3.85M
        const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) {
5091
            // First consider confidence
5092
3.85M
            if (a.second > b.second) {
5093
3.64k
                return true;
5094
3.64k
            }
5095
3.85M
            if (a.second < b.second) {
5096
1.34k
                return false;
5097
1.34k
            }
5098
5099
            // Then consider exact name matching
5100
3.85M
            const auto &aName(a.first->nameStr());
5101
3.85M
            const auto &bName(b.first->nameStr());
5102
3.85M
            if (aName == thisName && bName != thisName) {
5103
0
                return true;
5104
0
            }
5105
3.85M
            if (bName == thisName && aName != thisName) {
5106
8.93k
                return false;
5107
8.93k
            }
5108
5109
            // Arbitrary final sorting criterion
5110
3.84M
            return aName < bName;
5111
3.85M
        };
5112
5113
        // Sort results
5114
29.9k
        res.sort(lambdaSort);
5115
5116
29.9k
        if (!foundEquivalentName && (res.empty() || res.front().second < 50)) {
5117
29.6k
            std::set<std::pair<std::string, std::string>> alreadyKnown;
5118
238k
            for (const auto &pair : res) {
5119
238k
                const auto &ids = pair.first->identifiers();
5120
238k
                assert(!ids.empty());
5121
238k
                alreadyKnown.insert(std::pair<std::string, std::string>(
5122
238k
                    *(ids[0]->codeSpace()), ids[0]->code()));
5123
238k
            }
5124
5125
29.6k
            auto self = NN_NO_CHECK(std::dynamic_pointer_cast<ProjectedCRS>(
5126
29.6k
                shared_from_this().as_nullable()));
5127
29.6k
            auto candidates =
5128
29.6k
                authorityFactory->createProjectedCRSFromExisting(self);
5129
221k
            for (const auto &crs : candidates) {
5130
221k
                const auto &ids = crs->identifiers();
5131
221k
                assert(!ids.empty());
5132
221k
                if (alreadyKnown.find(std::pair<std::string, std::string>(
5133
221k
                        *(ids[0]->codeSpace()), ids[0]->code())) !=
5134
221k
                    alreadyKnown.end()) {
5135
1.36k
                    continue;
5136
1.36k
                }
5137
5138
220k
                addCRS(crs, insignificantName, hasNonMatchingId);
5139
220k
            }
5140
5141
29.6k
            res.sort(lambdaSort);
5142
29.6k
        }
5143
5144
        // Keep only results of the highest confidence
5145
29.9k
        if (res.size() >= 2) {
5146
10.7k
            const auto highestConfidence = res.front().second;
5147
10.7k
            std::list<Pair> newRes;
5148
440k
            for (const auto &pair : res) {
5149
440k
                if (pair.second == highestConfidence) {
5150
439k
                    newRes.push_back(pair);
5151
439k
                } else {
5152
1.35k
                    break;
5153
1.35k
                }
5154
440k
            }
5155
10.7k
            return newRes;
5156
10.7k
        }
5157
29.9k
    }
5158
5159
19.1k
    return res;
5160
30.7k
}
5161
5162
// ---------------------------------------------------------------------------
5163
5164
/** \brief Return a variant of this CRS "demoted" to a 2D one, if not already
5165
 * the case.
5166
 *
5167
 *
5168
 * @param newName Name of the new CRS. If empty, nameStr() will be used.
5169
 * @param dbContext Database context to look for potentially already registered
5170
 *                  2D CRS. May be nullptr.
5171
 * @return a new CRS demoted to 2D, or the current one if already 2D or not
5172
 * applicable.
5173
 * @since 6.3
5174
 */
5175
ProjectedCRSNNPtr
5176
ProjectedCRS::demoteTo2D(const std::string &newName,
5177
16.8k
                         const io::DatabaseContextPtr &dbContext) const {
5178
5179
16.8k
    const auto &axisList = coordinateSystem()->axisList();
5180
16.8k
    if (axisList.size() == 3) {
5181
15.6k
        auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0],
5182
15.6k
                                          axisList[1]);
5183
15.6k
        const auto &l_baseCRS = baseCRS();
5184
15.6k
        const auto geogCRS =
5185
15.6k
            dynamic_cast<const GeographicCRS *>(l_baseCRS.get());
5186
15.6k
        const auto newBaseCRS =
5187
15.6k
            geogCRS ? util::nn_static_pointer_cast<GeodeticCRS>(
5188
12.8k
                          geogCRS->demoteTo2D(std::string(), dbContext))
5189
15.6k
                    : l_baseCRS;
5190
15.6k
        return ProjectedCRS::create(
5191
15.6k
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
5192
15.6k
                                    !newName.empty() ? newName : nameStr()),
5193
15.6k
            newBaseCRS, derivingConversion(), cs);
5194
15.6k
    }
5195
5196
1.25k
    return NN_NO_CHECK(std::dynamic_pointer_cast<ProjectedCRS>(
5197
16.8k
        shared_from_this().as_nullable()));
5198
16.8k
}
5199
5200
// ---------------------------------------------------------------------------
5201
5202
//! @cond Doxygen_Suppress
5203
5204
std::list<std::pair<CRSNNPtr, int>>
5205
15.7k
ProjectedCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const {
5206
15.7k
    typedef std::pair<CRSNNPtr, int> Pair;
5207
15.7k
    std::list<Pair> res;
5208
15.7k
    auto resTemp = identify(authorityFactory);
5209
436k
    for (const auto &pair : resTemp) {
5210
436k
        res.emplace_back(pair.first, pair.second);
5211
436k
    }
5212
15.7k
    return res;
5213
15.7k
}
5214
5215
//! @endcond
5216
5217
// ---------------------------------------------------------------------------
5218
5219
//! @cond Doxygen_Suppress
5220
InvalidCompoundCRSException::InvalidCompoundCRSException(const char *message)
5221
24.4k
    : Exception(message) {}
5222
5223
// ---------------------------------------------------------------------------
5224
5225
InvalidCompoundCRSException::InvalidCompoundCRSException(
5226
    const std::string &message)
5227
0
    : Exception(message) {}
5228
5229
// ---------------------------------------------------------------------------
5230
5231
24.4k
InvalidCompoundCRSException::~InvalidCompoundCRSException() = default;
5232
5233
// ---------------------------------------------------------------------------
5234
5235
InvalidCompoundCRSException::InvalidCompoundCRSException(
5236
0
    const InvalidCompoundCRSException &) = default;
5237
//! @endcond
5238
5239
// ---------------------------------------------------------------------------
5240
5241
//! @cond Doxygen_Suppress
5242
struct CompoundCRS::Private {
5243
    std::vector<CRSNNPtr> components_{};
5244
};
5245
//! @endcond
5246
5247
// ---------------------------------------------------------------------------
5248
5249
CompoundCRS::CompoundCRS(const std::vector<CRSNNPtr> &components)
5250
409k
    : CRS(), d(std::make_unique<Private>()) {
5251
409k
    d->components_ = components;
5252
409k
}
5253
5254
// ---------------------------------------------------------------------------
5255
5256
CompoundCRS::CompoundCRS(const CompoundCRS &other)
5257
0
    : CRS(other), d(std::make_unique<Private>(*other.d)) {}
5258
5259
// ---------------------------------------------------------------------------
5260
5261
//! @cond Doxygen_Suppress
5262
409k
CompoundCRS::~CompoundCRS() = default;
5263
//! @endcond
5264
5265
// ---------------------------------------------------------------------------
5266
5267
0
CRSNNPtr CompoundCRS::_shallowClone() const {
5268
0
    auto crs(CompoundCRS::nn_make_shared<CompoundCRS>(*this));
5269
0
    crs->assignSelf(crs);
5270
0
    return crs;
5271
0
}
5272
5273
// ---------------------------------------------------------------------------
5274
5275
/** \brief Return the components of a CompoundCRS.
5276
 *
5277
 * @return the components.
5278
 */
5279
const std::vector<CRSNNPtr> &
5280
2.10M
CompoundCRS::componentReferenceSystems() PROJ_PURE_DEFN {
5281
2.10M
    return d->components_;
5282
2.10M
}
5283
5284
// ---------------------------------------------------------------------------
5285
5286
/** \brief Instantiate a CompoundCRS from a vector of CRS.
5287
 *
5288
 * @param properties See \ref general_properties.
5289
 * At minimum the name should be defined.
5290
 * @param components the component CRS of the CompoundCRS.
5291
 * @return new CompoundCRS.
5292
 * @throw InvalidCompoundCRSException if the object cannot be constructed.
5293
 */
5294
CompoundCRSNNPtr CompoundCRS::create(const util::PropertyMap &properties,
5295
427k
                                     const std::vector<CRSNNPtr> &components) {
5296
5297
427k
    if (components.size() < 2) {
5298
3.54k
        throw InvalidCompoundCRSException(
5299
3.54k
            "compound CRS should have at least 2 components");
5300
3.54k
    }
5301
5302
423k
    auto comp0 = components[0].get();
5303
423k
    auto comp0Bound = dynamic_cast<const BoundCRS *>(comp0);
5304
423k
    if (comp0Bound) {
5305
16.1k
        comp0 = comp0Bound->baseCRS().get();
5306
16.1k
    }
5307
423k
    auto comp0Geog = dynamic_cast<const GeographicCRS *>(comp0);
5308
423k
    auto comp0Proj = dynamic_cast<const ProjectedCRS *>(comp0);
5309
423k
    auto comp0DerPr = dynamic_cast<const DerivedProjectedCRS *>(comp0);
5310
423k
    auto comp0Eng = dynamic_cast<const EngineeringCRS *>(comp0);
5311
5312
423k
    auto comp1 = components[1].get();
5313
423k
    auto comp1Bound = dynamic_cast<const BoundCRS *>(comp1);
5314
423k
    if (comp1Bound) {
5315
34.1k
        comp1 = comp1Bound->baseCRS().get();
5316
34.1k
    }
5317
423k
    auto comp1Vert = dynamic_cast<const VerticalCRS *>(comp1);
5318
423k
    auto comp1Eng = dynamic_cast<const EngineeringCRS *>(comp1);
5319
    // Loose validation based on
5320
    // http://docs.opengeospatial.org/as/18-005r5/18-005r5.html#34
5321
423k
    bool ok = false;
5322
423k
    const bool comp1IsVertOrEng1 =
5323
423k
        comp1Vert ||
5324
5.83k
        (comp1Eng && comp1Eng->coordinateSystem()->axisList().size() == 1);
5325
423k
    if ((comp0Geog && comp0Geog->coordinateSystem()->axisList().size() == 2 &&
5326
113k
         comp1IsVertOrEng1) ||
5327
312k
        (comp0Proj && comp0Proj->coordinateSystem()->axisList().size() == 2 &&
5328
274k
         comp1IsVertOrEng1) ||
5329
39.4k
        (comp0DerPr && comp0DerPr->coordinateSystem()->axisList().size() == 2 &&
5330
0
         comp1IsVertOrEng1) ||
5331
39.4k
        (comp0Eng && comp0Eng->coordinateSystem()->axisList().size() <= 2 &&
5332
409k
         comp1Vert)) {
5333
        // Spatial compound coordinate reference system
5334
409k
        ok = true;
5335
409k
    } else {
5336
13.9k
        bool isComp0Spatial = comp0Geog || comp0Proj || comp0DerPr ||
5337
8.32k
                              comp0Eng ||
5338
6.59k
                              dynamic_cast<const GeodeticCRS *>(comp0) ||
5339
2.07k
                              dynamic_cast<const VerticalCRS *>(comp0);
5340
13.9k
        if (isComp0Spatial && dynamic_cast<const TemporalCRS *>(comp1)) {
5341
            // Spatio-temporal compound coordinate reference system
5342
0
            ok = true;
5343
13.9k
        } else if (isComp0Spatial &&
5344
13.6k
                   dynamic_cast<const ParametricCRS *>(comp1)) {
5345
            // Spatio-parametric compound coordinate reference system
5346
0
            ok = true;
5347
0
        }
5348
13.9k
    }
5349
423k
    if (!ok) {
5350
13.9k
        throw InvalidCompoundCRSException(
5351
13.9k
            "components of the compound CRS do not belong to one of the "
5352
13.9k
            "allowed combinations of "
5353
13.9k
            "http://docs.opengeospatial.org/as/18-005r5/18-005r5.html#34");
5354
13.9k
    }
5355
5356
409k
    auto compoundCRS(CompoundCRS::nn_make_shared<CompoundCRS>(components));
5357
409k
    compoundCRS->assignSelf(compoundCRS);
5358
409k
    compoundCRS->setProperties(properties);
5359
409k
    if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
5360
0
        std::string name;
5361
0
        for (const auto &crs : components) {
5362
0
            if (!name.empty()) {
5363
0
                name += " + ";
5364
0
            }
5365
0
            const auto &l_name = crs->nameStr();
5366
0
            if (!l_name.empty()) {
5367
0
                name += l_name;
5368
0
            } else {
5369
0
                name += "unnamed";
5370
0
            }
5371
0
        }
5372
0
        util::PropertyMap propertyName;
5373
0
        propertyName.set(common::IdentifiedObject::NAME_KEY, name);
5374
0
        compoundCRS->setProperties(propertyName);
5375
0
    }
5376
5377
409k
    return compoundCRS;
5378
423k
}
5379
5380
// ---------------------------------------------------------------------------
5381
5382
//! @cond Doxygen_Suppress
5383
5384
/** \brief Instantiate a CompoundCRS, a Geographic 3D CRS or a Projected CRS
5385
 * from a vector of CRS.
5386
 *
5387
 * Be a bit "lax", in allowing formulations like EPSG:4326+4326 or
5388
 * EPSG:32631+4326 to express Geographic 3D CRS / Projected3D CRS.
5389
 *
5390
 * @param properties See \ref general_properties.
5391
 * At minimum the name should be defined.
5392
 * @param components the component CRS of the CompoundCRS.
5393
 * @return new CRS.
5394
 * @throw InvalidCompoundCRSException if the object cannot be constructed.
5395
 */
5396
CRSNNPtr CompoundCRS::createLax(const util::PropertyMap &properties,
5397
                                const std::vector<CRSNNPtr> &components,
5398
81.0k
                                const io::DatabaseContextPtr &dbContext) {
5399
5400
81.0k
    if (components.size() == 2) {
5401
66.1k
        auto comp0 = components[0].get();
5402
66.1k
        auto comp1 = components[1].get();
5403
66.1k
        auto comp0Geog = dynamic_cast<const GeographicCRS *>(comp0);
5404
66.1k
        auto comp0Proj = dynamic_cast<const ProjectedCRS *>(comp0);
5405
66.1k
        auto comp0Bound = dynamic_cast<const BoundCRS *>(comp0);
5406
66.1k
        if (comp0Geog == nullptr && comp0Proj == nullptr) {
5407
22.9k
            if (comp0Bound) {
5408
3.03k
                const auto *baseCRS = comp0Bound->baseCRS().get();
5409
3.03k
                comp0Geog = dynamic_cast<const GeographicCRS *>(baseCRS);
5410
3.03k
                comp0Proj = dynamic_cast<const ProjectedCRS *>(baseCRS);
5411
3.03k
            }
5412
22.9k
        }
5413
66.1k
        auto comp1Geog = dynamic_cast<const GeographicCRS *>(comp1);
5414
66.1k
        if ((comp0Geog != nullptr || comp0Proj != nullptr) &&
5415
46.1k
            comp1Geog != nullptr) {
5416
8.48k
            const auto horizGeog =
5417
8.48k
                (comp0Proj != nullptr)
5418
8.48k
                    ? comp0Proj->baseCRS().as_nullable().get()
5419
8.48k
                    : comp0Geog;
5420
8.48k
            if (horizGeog->_isEquivalentTo(
5421
8.48k
                    comp1Geog->demoteTo2D(std::string(), dbContext).get())) {
5422
1.46k
                return components[0]
5423
1.46k
                    ->promoteTo3D(std::string(), dbContext)
5424
1.46k
                    ->allowNonConformantWKT1Export();
5425
1.46k
            }
5426
7.02k
            throw InvalidCompoundCRSException(
5427
7.02k
                "The 'vertical' geographic CRS is not equivalent to the "
5428
7.02k
                "geographic CRS of the horizontal part");
5429
8.48k
        }
5430
5431
        // Detect a COMPD_CS whose VERT_CS is for ellipoidal heights
5432
57.6k
        auto comp1Vert =
5433
57.6k
            util::nn_dynamic_pointer_cast<VerticalCRS>(components[1]);
5434
57.6k
        if (comp1Vert != nullptr && comp1Vert->datum() &&
5435
56.3k
            comp1Vert->datum()->getWKT1DatumType() == "2002") {
5436
2.76k
            const auto &axis = comp1Vert->coordinateSystem()->axisList()[0];
5437
2.76k
            std::string name(components[0]->nameStr());
5438
2.76k
            if (!(axis->unit()._isEquivalentTo(
5439
2.76k
                      common::UnitOfMeasure::METRE,
5440
2.76k
                      util::IComparable::Criterion::EQUIVALENT) &&
5441
1.89k
                  &(axis->direction()) == &(cs::AxisDirection::UP))) {
5442
1.89k
                name += " (" + comp1Vert->nameStr() + ')';
5443
1.89k
            }
5444
2.76k
            auto newVertAxis = cs::CoordinateSystemAxis::create(
5445
2.76k
                util::PropertyMap().set(IdentifiedObject::NAME_KEY,
5446
2.76k
                                        cs::AxisName::Ellipsoidal_height),
5447
2.76k
                cs::AxisAbbreviation::h, axis->direction(), axis->unit());
5448
2.76k
            return components[0]
5449
2.76k
                ->promoteTo3D(name, dbContext, newVertAxis)
5450
2.76k
                ->attachOriginalCompoundCRS(create(
5451
2.76k
                    properties,
5452
2.76k
                    comp0Bound ? std::vector<CRSNNPtr>{comp0Bound->baseCRS(),
5453
274
                                                       components[1]}
5454
2.76k
                               : components));
5455
2.76k
        }
5456
57.6k
    }
5457
5458
69.7k
    return create(properties, components);
5459
81.0k
}
5460
//! @endcond
5461
5462
// ---------------------------------------------------------------------------
5463
5464
//! @cond Doxygen_Suppress
5465
62.3k
void CompoundCRS::_exportToWKT(io::WKTFormatter *formatter) const {
5466
62.3k
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
5467
62.3k
    const auto &l_components = componentReferenceSystems();
5468
62.3k
    if (!isWKT2 && formatter->useESRIDialect() && l_components.size() == 2) {
5469
341
        l_components[0]->_exportToWKT(formatter);
5470
341
        l_components[1]->_exportToWKT(formatter);
5471
62.0k
    } else {
5472
62.0k
        formatter->startNode(isWKT2 ? io::WKTConstants::COMPOUNDCRS
5473
62.0k
                                    : io::WKTConstants::COMPD_CS,
5474
62.0k
                             !identifiers().empty());
5475
62.0k
        formatter->addQuotedString(nameStr());
5476
62.0k
        if (!l_components.empty()) {
5477
62.0k
            formatter->setGeogCRSOfCompoundCRS(
5478
62.0k
                l_components[0]->extractGeographicCRS());
5479
62.0k
        }
5480
189k
        for (const auto &crs : l_components) {
5481
189k
            crs->_exportToWKT(formatter);
5482
189k
        }
5483
62.0k
        formatter->setGeogCRSOfCompoundCRS(nullptr);
5484
62.0k
        ObjectUsage::baseExportToWKT(formatter);
5485
62.0k
        formatter->endNode();
5486
62.0k
    }
5487
62.3k
}
5488
//! @endcond
5489
5490
// ---------------------------------------------------------------------------
5491
5492
//! @cond Doxygen_Suppress
5493
void CompoundCRS::_exportToJSON(
5494
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
5495
2
{
5496
2
    auto writer = formatter->writer();
5497
2
    auto objectContext(
5498
2
        formatter->MakeObjectContext("CompoundCRS", !identifiers().empty()));
5499
5500
2
    writer->AddObjKey("name");
5501
2
    const auto &l_name = nameStr();
5502
2
    if (l_name.empty()) {
5503
0
        writer->Add("unnamed");
5504
2
    } else {
5505
2
        writer->Add(l_name);
5506
2
    }
5507
5508
2
    writer->AddObjKey("components");
5509
2
    {
5510
2
        auto componentsContext(writer->MakeArrayContext(false));
5511
4
        for (const auto &crs : componentReferenceSystems()) {
5512
4
            crs->_exportToJSON(formatter);
5513
4
        }
5514
2
    }
5515
5516
2
    ObjectUsage::baseExportToJSON(formatter);
5517
2
}
5518
//! @endcond
5519
5520
// ---------------------------------------------------------------------------
5521
5522
void CompoundCRS::_exportToPROJString(
5523
    io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
5524
6.30k
{
5525
6.30k
    const auto &l_components = componentReferenceSystems();
5526
6.30k
    if (!l_components.empty()) {
5527
6.30k
        formatter->setGeogCRSOfCompoundCRS(
5528
6.30k
            l_components[0]->extractGeographicCRS());
5529
6.30k
    }
5530
12.5k
    for (const auto &crs : l_components) {
5531
12.5k
        auto crs_exportable =
5532
12.5k
            dynamic_cast<const IPROJStringExportable *>(crs.get());
5533
12.5k
        if (crs_exportable) {
5534
12.5k
            crs_exportable->_exportToPROJString(formatter);
5535
12.5k
        }
5536
12.5k
    }
5537
6.30k
    formatter->setGeogCRSOfCompoundCRS(nullptr);
5538
6.30k
}
5539
5540
// ---------------------------------------------------------------------------
5541
5542
bool CompoundCRS::_isEquivalentTo(
5543
    const util::IComparable *other, util::IComparable::Criterion criterion,
5544
308k
    const io::DatabaseContextPtr &dbContext) const {
5545
308k
    auto otherCompoundCRS = dynamic_cast<const CompoundCRS *>(other);
5546
308k
    if (otherCompoundCRS == nullptr ||
5547
308k
        (criterion == util::IComparable::Criterion::STRICT &&
5548
243
         !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
5549
243
        return false;
5550
243
    }
5551
308k
    const auto &components = componentReferenceSystems();
5552
308k
    const auto &otherComponents = otherCompoundCRS->componentReferenceSystems();
5553
308k
    if (components.size() != otherComponents.size()) {
5554
0
        return false;
5555
0
    }
5556
317k
    for (size_t i = 0; i < components.size(); i++) {
5557
312k
        if (!components[i]->_isEquivalentTo(otherComponents[i].get(), criterion,
5558
312k
                                            dbContext)) {
5559
303k
            return false;
5560
303k
        }
5561
312k
    }
5562
4.53k
    return true;
5563
308k
}
5564
5565
// ---------------------------------------------------------------------------
5566
5567
/** \brief Identify the CRS with reference CRSs.
5568
 *
5569
 * The candidate CRSs are looked in the database when
5570
 * authorityFactory is not null.
5571
 *
5572
 * Note that the implementation uses a set of heuristics to have a good
5573
 * compromise of successful identifications over execution time. It might miss
5574
 * legitimate matches in some circumstances.
5575
 *
5576
 * The method returns a list of matching reference CRS, and the percentage
5577
 * (0-100) of confidence in the match. The list is sorted by decreasing
5578
 * confidence.
5579
 *
5580
 * 100% means that the name of the reference entry
5581
 * perfectly matches the CRS name, and both are equivalent. In which case a
5582
 * single result is returned.
5583
 * 90% means that CRS are equivalent, but the names are not exactly the same.
5584
 * 70% means that CRS are equivalent (equivalent horizontal and vertical CRS),
5585
 * but the names are not equivalent.
5586
 * 25% means that the CRS are not equivalent, but there is some similarity in
5587
 * the names.
5588
 *
5589
 * @param authorityFactory Authority factory (if null, will return an empty
5590
 * list)
5591
 * @return a list of matching reference CRS, and the percentage (0-100) of
5592
 * confidence in the match.
5593
 */
5594
std::list<std::pair<CompoundCRSNNPtr, int>>
5595
4.37k
CompoundCRS::identify(const io::AuthorityFactoryPtr &authorityFactory) const {
5596
4.37k
    typedef std::pair<CompoundCRSNNPtr, int> Pair;
5597
4.37k
    std::list<Pair> res;
5598
5599
4.37k
    const auto &thisName(nameStr());
5600
5601
4.37k
    const auto &components = componentReferenceSystems();
5602
4.37k
    bool l_implicitCS = components[0]->hasImplicitCS();
5603
4.37k
    if (!l_implicitCS) {
5604
4.37k
        const auto projCRS =
5605
4.37k
            dynamic_cast<const ProjectedCRS *>(components[0].get());
5606
4.37k
        if (projCRS) {
5607
2.83k
            l_implicitCS = projCRS->baseCRS()->hasImplicitCS();
5608
2.83k
        }
5609
4.37k
    }
5610
4.37k
    const auto crsCriterion =
5611
4.37k
        l_implicitCS
5612
4.37k
            ? util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS
5613
4.37k
            : util::IComparable::Criterion::EQUIVALENT;
5614
5615
4.37k
    if (authorityFactory) {
5616
4.37k
        const io::DatabaseContextNNPtr &dbContext =
5617
4.37k
            authorityFactory->databaseContext();
5618
5619
4.37k
        const bool insignificantName = thisName.empty() ||
5620
4.36k
                                       ci_equal(thisName, "unknown") ||
5621
4.36k
                                       ci_equal(thisName, "unnamed");
5622
4.37k
        bool foundEquivalentName = false;
5623
5624
4.37k
        if (hasCodeCompatibleOfAuthorityFactory(this, authorityFactory)) {
5625
            // If the CRS has already an id, check in the database for the
5626
            // official object, and verify that they are equivalent.
5627
0
            for (const auto &id : identifiers()) {
5628
0
                if (hasCodeCompatibleOfAuthorityFactory(id, authorityFactory)) {
5629
0
                    try {
5630
0
                        auto crs = io::AuthorityFactory::create(
5631
0
                                       dbContext, *id->codeSpace())
5632
0
                                       ->createCompoundCRS(id->code());
5633
0
                        bool match =
5634
0
                            _isEquivalentTo(crs.get(), crsCriterion, dbContext);
5635
0
                        res.emplace_back(crs, match ? 100 : 25);
5636
0
                        return res;
5637
0
                    } catch (const std::exception &) {
5638
0
                    }
5639
0
                }
5640
0
            }
5641
4.37k
        } else if (!insignificantName) {
5642
11.1k
            for (int ipass = 0; ipass < 2; ipass++) {
5643
8.72k
                const bool approximateMatch = ipass == 1;
5644
8.72k
                auto objects = authorityFactory->createObjectsFromName(
5645
8.72k
                    thisName, {io::AuthorityFactory::ObjectType::COMPOUND_CRS},
5646
8.72k
                    approximateMatch);
5647
301k
                for (const auto &obj : objects) {
5648
301k
                    auto crs = util::nn_dynamic_pointer_cast<CompoundCRS>(obj);
5649
301k
                    assert(crs);
5650
301k
                    auto crsNN = NN_NO_CHECK(crs);
5651
301k
                    const bool eqName = metadata::Identifier::isEquivalentName(
5652
301k
                        thisName.c_str(), crs->nameStr().c_str());
5653
301k
                    foundEquivalentName |= eqName;
5654
301k
                    if (_isEquivalentTo(crs.get(), crsCriterion, dbContext)) {
5655
14
                        if (crs->nameStr() == thisName) {
5656
3
                            res.clear();
5657
3
                            res.emplace_back(crsNN, 100);
5658
3
                            return res;
5659
3
                        }
5660
11
                        res.emplace_back(crsNN, eqName ? 90 : 70);
5661
301k
                    } else {
5662
5663
301k
                        res.emplace_back(crsNN, 25);
5664
301k
                    }
5665
301k
                }
5666
8.72k
                if (!res.empty()) {
5667
1.92k
                    break;
5668
1.92k
                }
5669
8.72k
            }
5670
4.36k
        }
5671
5672
3.19M
        const auto lambdaSort = [&thisName](const Pair &a, const Pair &b) {
5673
            // First consider confidence
5674
3.19M
            if (a.second > b.second) {
5675
60
                return true;
5676
60
            }
5677
3.19M
            if (a.second < b.second) {
5678
40
                return false;
5679
40
            }
5680
5681
            // Then consider exact name matching
5682
3.19M
            const auto &aName(a.first->nameStr());
5683
3.19M
            const auto &bName(b.first->nameStr());
5684
3.19M
            if (aName == thisName && bName != thisName) {
5685
0
                return true;
5686
0
            }
5687
3.19M
            if (bName == thisName && aName != thisName) {
5688
0
                return false;
5689
0
            }
5690
5691
            // Arbitrary final sorting criterion
5692
3.19M
            return aName < bName;
5693
3.19M
        };
5694
5695
        // Sort results
5696
4.36k
        res.sort(lambdaSort);
5697
5698
4.36k
        if (identifiers().empty() && !foundEquivalentName &&
5699
4.36k
            (res.empty() || res.front().second < 50)) {
5700
4.35k
            std::set<std::pair<std::string, std::string>> alreadyKnown;
5701
297k
            for (const auto &pair : res) {
5702
297k
                const auto &ids = pair.first->identifiers();
5703
297k
                assert(!ids.empty());
5704
297k
                alreadyKnown.insert(std::pair<std::string, std::string>(
5705
297k
                    *(ids[0]->codeSpace()), ids[0]->code()));
5706
297k
            }
5707
5708
4.35k
            auto self = NN_NO_CHECK(std::dynamic_pointer_cast<CompoundCRS>(
5709
4.35k
                shared_from_this().as_nullable()));
5710
4.35k
            auto candidates =
5711
4.35k
                authorityFactory->createCompoundCRSFromExisting(self);
5712
4.35k
            for (const auto &crs : candidates) {
5713
2.32k
                const auto &ids = crs->identifiers();
5714
2.32k
                assert(!ids.empty());
5715
2.32k
                if (alreadyKnown.find(std::pair<std::string, std::string>(
5716
2.32k
                        *(ids[0]->codeSpace()), ids[0]->code())) !=
5717
2.32k
                    alreadyKnown.end()) {
5718
126
                    continue;
5719
126
                }
5720
5721
2.20k
                if (_isEquivalentTo(crs.get(), crsCriterion, dbContext)) {
5722
16
                    res.emplace_back(crs, insignificantName ? 90 : 70);
5723
2.18k
                } else {
5724
2.18k
                    res.emplace_back(crs, 25);
5725
2.18k
                }
5726
2.20k
            }
5727
5728
4.35k
            res.sort(lambdaSort);
5729
5730
            // If there's a single candidate at 90% confidence with same name,
5731
            // then promote it to 100%
5732
4.35k
            if (res.size() == 1 && res.front().second == 90 &&
5733
0
                thisName == res.front().first->nameStr()) {
5734
0
                res.front().second = 100;
5735
0
            }
5736
4.35k
        }
5737
5738
        // If we didn't find a match for the CompoundCRS, check if the
5739
        // horizontal and vertical parts are not themselves well known.
5740
4.36k
        if (identifiers().empty() && res.empty() && components.size() == 2) {
5741
2.29k
            auto candidatesHorizCRS = components[0]->identify(authorityFactory);
5742
2.29k
            auto candidatesVertCRS = components[1]->identify(authorityFactory);
5743
2.29k
            if (candidatesHorizCRS.size() == 1 &&
5744
1.36k
                candidatesVertCRS.size() == 1 &&
5745
1.36k
                candidatesHorizCRS.front().second >= 70 &&
5746
557
                candidatesVertCRS.front().second >= 70) {
5747
557
                auto newCRS = CompoundCRS::create(
5748
557
                    util::PropertyMap().set(
5749
557
                        common::IdentifiedObject::NAME_KEY,
5750
557
                        candidatesHorizCRS.front().first->nameStr() + " + " +
5751
557
                            candidatesVertCRS.front().first->nameStr()),
5752
557
                    {candidatesHorizCRS.front().first,
5753
557
                     candidatesVertCRS.front().first});
5754
557
                const bool eqName = metadata::Identifier::isEquivalentName(
5755
557
                    thisName.c_str(), newCRS->nameStr().c_str());
5756
557
                res.emplace_back(
5757
557
                    newCRS,
5758
557
                    std::min(thisName == newCRS->nameStr() ? 100
5759
557
                             : eqName                      ? 90
5760
335
                                                           : 70,
5761
557
                             std::min(candidatesHorizCRS.front().second,
5762
557
                                      candidatesVertCRS.front().second)));
5763
557
            }
5764
2.29k
        }
5765
5766
        // Keep only results of the highest confidence
5767
4.36k
        if (res.size() >= 2) {
5768
1.92k
            const auto highestConfidence = res.front().second;
5769
1.92k
            std::list<Pair> newRes;
5770
299k
            for (const auto &pair : res) {
5771
299k
                if (pair.second == highestConfidence) {
5772
299k
                    newRes.push_back(pair);
5773
299k
                } else {
5774
17
                    break;
5775
17
                }
5776
299k
            }
5777
1.92k
            return newRes;
5778
1.92k
        }
5779
4.36k
    }
5780
5781
2.43k
    return res;
5782
4.37k
}
5783
5784
// ---------------------------------------------------------------------------
5785
5786
//! @cond Doxygen_Suppress
5787
5788
std::list<std::pair<CRSNNPtr, int>>
5789
4.37k
CompoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const {
5790
4.37k
    typedef std::pair<CRSNNPtr, int> Pair;
5791
4.37k
    std::list<Pair> res;
5792
4.37k
    auto resTemp = identify(authorityFactory);
5793
299k
    for (const auto &pair : resTemp) {
5794
299k
        res.emplace_back(pair.first, pair.second);
5795
299k
    }
5796
4.37k
    return res;
5797
4.37k
}
5798
5799
//! @endcond
5800
5801
// ---------------------------------------------------------------------------
5802
5803
//! @cond Doxygen_Suppress
5804
struct PROJ_INTERNAL BoundCRS::Private {
5805
    CRSNNPtr baseCRS_;
5806
    CRSNNPtr hubCRS_;
5807
    operation::TransformationNNPtr transformation_;
5808
5809
    Private(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn,
5810
            const operation::TransformationNNPtr &transformationIn);
5811
5812
115k
    inline const CRSNNPtr &baseCRS() const { return baseCRS_; }
5813
139k
    inline const CRSNNPtr &hubCRS() const { return hubCRS_; }
5814
94.6k
    inline const operation::TransformationNNPtr &transformation() const {
5815
94.6k
        return transformation_;
5816
94.6k
    }
5817
};
5818
5819
BoundCRS::Private::Private(
5820
    const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn,
5821
    const operation::TransformationNNPtr &transformationIn)
5822
210k
    : baseCRS_(baseCRSIn), hubCRS_(hubCRSIn),
5823
210k
      transformation_(transformationIn) {}
5824
5825
//! @endcond
5826
5827
// ---------------------------------------------------------------------------
5828
5829
BoundCRS::BoundCRS(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn,
5830
                   const operation::TransformationNNPtr &transformationIn)
5831
210k
    : d(std::make_unique<Private>(baseCRSIn, hubCRSIn, transformationIn)) {}
5832
5833
// ---------------------------------------------------------------------------
5834
5835
BoundCRS::BoundCRS(const BoundCRS &other)
5836
0
    : CRS(other),
5837
0
      d(std::make_unique<Private>(other.d->baseCRS(), other.d->hubCRS(),
5838
0
                                  other.d->transformation())) {}
5839
5840
// ---------------------------------------------------------------------------
5841
5842
//! @cond Doxygen_Suppress
5843
210k
BoundCRS::~BoundCRS() = default;
5844
//! @endcond
5845
5846
// ---------------------------------------------------------------------------
5847
5848
0
BoundCRSNNPtr BoundCRS::shallowCloneAsBoundCRS() const {
5849
0
    auto crs(BoundCRS::nn_make_shared<BoundCRS>(*this));
5850
0
    crs->assignSelf(crs);
5851
0
    return crs;
5852
0
}
5853
5854
// ---------------------------------------------------------------------------
5855
5856
0
CRSNNPtr BoundCRS::_shallowClone() const { return shallowCloneAsBoundCRS(); }
5857
5858
// ---------------------------------------------------------------------------
5859
5860
/** \brief Return the base CRS.
5861
 *
5862
 * This is the CRS into which coordinates of the BoundCRS are expressed.
5863
 *
5864
 * @return the base CRS.
5865
 */
5866
387k
const CRSNNPtr &BoundCRS::baseCRS() PROJ_PURE_DEFN { return d->baseCRS_; }
5867
5868
// ---------------------------------------------------------------------------
5869
5870
// The only legit caller is BoundCRS::baseCRSWithCanonicalBoundCRS()
5871
0
void CRS::setCanonicalBoundCRS(const BoundCRSNNPtr &boundCRS) {
5872
5873
0
    d->canonicalBoundCRS_ = boundCRS;
5874
0
}
5875
5876
// ---------------------------------------------------------------------------
5877
5878
/** \brief Return a shallow clone of the base CRS that points to a
5879
 * shallow clone of this BoundCRS.
5880
 *
5881
 * The base CRS is the CRS into which coordinates of the BoundCRS are expressed.
5882
 *
5883
 * The returned CRS will actually be a shallow clone of the actual base CRS,
5884
 * with the extra property that CRS::canonicalBoundCRS() will point to a
5885
 * shallow clone of this BoundCRS. Use this only if you want to work with
5886
 * the base CRS object rather than the BoundCRS, but wanting to be able to
5887
 * retrieve the BoundCRS later.
5888
 *
5889
 * @return the base CRS.
5890
 */
5891
0
CRSNNPtr BoundCRS::baseCRSWithCanonicalBoundCRS() const {
5892
0
    auto baseCRSClone = baseCRS()->_shallowClone();
5893
0
    baseCRSClone->setCanonicalBoundCRS(shallowCloneAsBoundCRS());
5894
0
    return baseCRSClone;
5895
0
}
5896
5897
// ---------------------------------------------------------------------------
5898
5899
/** \brief Return the target / hub CRS.
5900
 *
5901
 * @return the hub CRS.
5902
 */
5903
141k
const CRSNNPtr &BoundCRS::hubCRS() PROJ_PURE_DEFN { return d->hubCRS_; }
5904
5905
// ---------------------------------------------------------------------------
5906
5907
/** \brief Return the transformation to the hub RS.
5908
 *
5909
 * @return transformation.
5910
 */
5911
const operation::TransformationNNPtr &
5912
198k
BoundCRS::transformation() PROJ_PURE_DEFN {
5913
198k
    return d->transformation_;
5914
198k
}
5915
5916
// ---------------------------------------------------------------------------
5917
5918
/** \brief Instantiate a BoundCRS from a base CRS, a hub CRS and a
5919
 * transformation.
5920
 *
5921
 * @param properties See \ref general_properties.
5922
 * @param baseCRSIn base CRS.
5923
 * @param hubCRSIn hub CRS.
5924
 * @param transformationIn transformation from base CRS to hub CRS.
5925
 * @return new BoundCRS.
5926
 * @since PROJ 8.2
5927
 */
5928
BoundCRSNNPtr
5929
BoundCRS::create(const util::PropertyMap &properties, const CRSNNPtr &baseCRSIn,
5930
                 const CRSNNPtr &hubCRSIn,
5931
210k
                 const operation::TransformationNNPtr &transformationIn) {
5932
210k
    auto crs = BoundCRS::nn_make_shared<BoundCRS>(baseCRSIn, hubCRSIn,
5933
210k
                                                  transformationIn);
5934
210k
    crs->assignSelf(crs);
5935
210k
    const auto &l_name = baseCRSIn->nameStr();
5936
210k
    if (properties.get(common::IdentifiedObject::NAME_KEY) == nullptr &&
5937
210k
        !l_name.empty()) {
5938
208k
        auto newProperties(properties);
5939
208k
        newProperties.set(common::IdentifiedObject::NAME_KEY, l_name);
5940
208k
        crs->setProperties(newProperties);
5941
208k
    } else {
5942
2.17k
        crs->setProperties(properties);
5943
2.17k
    }
5944
210k
    return crs;
5945
210k
}
5946
5947
// ---------------------------------------------------------------------------
5948
5949
/** \brief Instantiate a BoundCRS from a base CRS, a hub CRS and a
5950
 * transformation.
5951
 *
5952
 * @param baseCRSIn base CRS.
5953
 * @param hubCRSIn hub CRS.
5954
 * @param transformationIn transformation from base CRS to hub CRS.
5955
 * @return new BoundCRS.
5956
 */
5957
BoundCRSNNPtr
5958
BoundCRS::create(const CRSNNPtr &baseCRSIn, const CRSNNPtr &hubCRSIn,
5959
209k
                 const operation::TransformationNNPtr &transformationIn) {
5960
209k
    return create(util::PropertyMap(), baseCRSIn, hubCRSIn, transformationIn);
5961
209k
}
5962
5963
// ---------------------------------------------------------------------------
5964
5965
/** \brief Instantiate a BoundCRS from a base CRS and TOWGS84 parameters
5966
 *
5967
 * @param baseCRSIn base CRS.
5968
 * @param TOWGS84Parameters a vector of 3 or 7 double values representing WKT1
5969
 * TOWGS84 parameter.
5970
 * @return new BoundCRS.
5971
 */
5972
BoundCRSNNPtr
5973
BoundCRS::createFromTOWGS84(const CRSNNPtr &baseCRSIn,
5974
70.7k
                            const std::vector<double> &TOWGS84Parameters) {
5975
70.7k
    auto transf =
5976
70.7k
        operation::Transformation::createTOWGS84(baseCRSIn, TOWGS84Parameters);
5977
70.7k
    return create(baseCRSIn, transf->targetCRS(), transf);
5978
70.7k
}
5979
5980
// ---------------------------------------------------------------------------
5981
5982
/** \brief Instantiate a BoundCRS from a base CRS and nadgrids parameters
5983
 *
5984
 * @param baseCRSIn base CRS.
5985
 * @param filename Horizontal grid filename
5986
 * @return new BoundCRS.
5987
 */
5988
BoundCRSNNPtr BoundCRS::createFromNadgrids(const CRSNNPtr &baseCRSIn,
5989
40.0k
                                           const std::string &filename) {
5990
40.0k
    const auto sourceGeographicCRS = baseCRSIn->extractGeographicCRS();
5991
40.0k
    auto transformationSourceCRS =
5992
40.0k
        sourceGeographicCRS
5993
40.0k
            ? NN_NO_CHECK(std::static_pointer_cast<CRS>(sourceGeographicCRS))
5994
40.0k
            : baseCRSIn;
5995
40.0k
    if (sourceGeographicCRS != nullptr &&
5996
33.7k
        sourceGeographicCRS->primeMeridian()->longitude().getSIValue() != 0.0) {
5997
3.58k
        transformationSourceCRS = GeographicCRS::create(
5998
3.58k
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
5999
3.58k
                                    sourceGeographicCRS->nameStr() +
6000
3.58k
                                        " (with Greenwich prime meridian)"),
6001
3.58k
            datum::GeodeticReferenceFrame::create(
6002
3.58k
                util::PropertyMap().set(
6003
3.58k
                    common::IdentifiedObject::NAME_KEY,
6004
3.58k
                    sourceGeographicCRS->datumNonNull(nullptr)->nameStr() +
6005
3.58k
                        " (with Greenwich prime meridian)"),
6006
3.58k
                sourceGeographicCRS->datumNonNull(nullptr)->ellipsoid(),
6007
3.58k
                util::optional<std::string>(), datum::PrimeMeridian::GREENWICH),
6008
3.58k
            cs::EllipsoidalCS::createLatitudeLongitude(
6009
3.58k
                common::UnitOfMeasure::DEGREE));
6010
3.58k
    }
6011
40.0k
    std::string transformationName = transformationSourceCRS->nameStr();
6012
40.0k
    transformationName += " to WGS84";
6013
6014
40.0k
    return create(
6015
40.0k
        baseCRSIn, GeographicCRS::EPSG_4326,
6016
40.0k
        operation::Transformation::createNTv2(
6017
40.0k
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6018
40.0k
                                    transformationName),
6019
40.0k
            transformationSourceCRS, GeographicCRS::EPSG_4326, filename,
6020
40.0k
            std::vector<metadata::PositionalAccuracyNNPtr>()));
6021
40.0k
}
6022
6023
// ---------------------------------------------------------------------------
6024
6025
34.9k
bool BoundCRS::isTOWGS84Compatible() const {
6026
34.9k
    return dynamic_cast<GeodeticCRS *>(d->hubCRS().get()) != nullptr &&
6027
34.9k
           ci_equal(d->hubCRS()->nameStr(), "WGS 84");
6028
34.9k
}
6029
6030
// ---------------------------------------------------------------------------
6031
6032
std::string BoundCRS::getHDatumPROJ4GRIDS(
6033
45.4k
    const io::DatabaseContextPtr &databaseContext) const {
6034
45.4k
    if (ci_equal(d->hubCRS()->nameStr(), "WGS 84")) {
6035
45.3k
        if (databaseContext) {
6036
36.2k
            return d->transformation()
6037
36.2k
                ->substitutePROJAlternativeGridNames(
6038
36.2k
                    NN_NO_CHECK(databaseContext))
6039
36.2k
                ->getPROJ4NadgridsCompatibleFilename();
6040
36.2k
        }
6041
9.11k
        return d->transformation()->getPROJ4NadgridsCompatibleFilename();
6042
45.3k
    }
6043
98
    return std::string();
6044
45.4k
}
6045
6046
// ---------------------------------------------------------------------------
6047
6048
std::string
6049
BoundCRS::getVDatumPROJ4GRIDS(const crs::GeographicCRS *geogCRSOfCompoundCRS,
6050
51.9k
                              const char **outGeoidCRSValue) const {
6051
    // When importing from WKT1 PROJ4_GRIDS extension, we used to hardcode
6052
    // "WGS 84" as the hub CRS, so let's test that for backward compatibility.
6053
51.9k
    if (dynamic_cast<VerticalCRS *>(d->baseCRS().get()) &&
6054
6.59k
        ci_equal(d->hubCRS()->nameStr(), "WGS 84")) {
6055
5.43k
        if (outGeoidCRSValue)
6056
4.13k
            *outGeoidCRSValue = "WGS84";
6057
5.43k
        return d->transformation()->getHeightToGeographic3DFilename();
6058
46.5k
    } else if (geogCRSOfCompoundCRS &&
6059
8.69k
               dynamic_cast<VerticalCRS *>(d->baseCRS().get()) &&
6060
1.16k
               ci_equal(d->hubCRS()->nameStr(),
6061
1.16k
                        geogCRSOfCompoundCRS->nameStr())) {
6062
1.06k
        if (outGeoidCRSValue)
6063
611
            *outGeoidCRSValue = "horizontal_crs";
6064
1.06k
        return d->transformation()->getHeightToGeographic3DFilename();
6065
1.06k
    }
6066
45.4k
    return std::string();
6067
51.9k
}
6068
6069
// ---------------------------------------------------------------------------
6070
6071
//! @cond Doxygen_Suppress
6072
54.6k
void BoundCRS::_exportToWKT(io::WKTFormatter *formatter) const {
6073
54.6k
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
6074
54.6k
    if (isWKT2) {
6075
16.8k
        formatter->startNode(io::WKTConstants::BOUNDCRS, false);
6076
16.8k
        formatter->startNode(io::WKTConstants::SOURCECRS, false);
6077
16.8k
        d->baseCRS()->_exportToWKT(formatter);
6078
16.8k
        formatter->endNode();
6079
16.8k
        formatter->startNode(io::WKTConstants::TARGETCRS, false);
6080
16.8k
        d->hubCRS()->_exportToWKT(formatter);
6081
16.8k
        formatter->endNode();
6082
16.8k
        formatter->setAbridgedTransformation(true);
6083
16.8k
        d->transformation()->_exportToWKT(formatter);
6084
16.8k
        formatter->setAbridgedTransformation(false);
6085
16.8k
        ObjectUsage::baseExportToWKT(formatter);
6086
16.8k
        formatter->endNode();
6087
37.8k
    } else {
6088
6089
37.8k
        auto vdatumProj4GridName = getVDatumPROJ4GRIDS(
6090
37.8k
            formatter->getGeogCRSOfCompoundCRS().get(), nullptr);
6091
37.8k
        if (!vdatumProj4GridName.empty()) {
6092
1.75k
            formatter->setVDatumExtension(vdatumProj4GridName);
6093
1.75k
            d->baseCRS()->_exportToWKT(formatter);
6094
1.75k
            formatter->setVDatumExtension(std::string());
6095
1.75k
            return;
6096
1.75k
        }
6097
6098
36.0k
        auto hdatumProj4GridName =
6099
36.0k
            getHDatumPROJ4GRIDS(formatter->databaseContext());
6100
36.0k
        if (!hdatumProj4GridName.empty()) {
6101
10.0k
            formatter->setHDatumExtension(hdatumProj4GridName);
6102
10.0k
            d->baseCRS()->_exportToWKT(formatter);
6103
10.0k
            formatter->setHDatumExtension(std::string());
6104
10.0k
            return;
6105
10.0k
        }
6106
6107
26.0k
        if (!isTOWGS84Compatible()) {
6108
97
            io::FormattingException::Throw(
6109
97
                "Cannot export BoundCRS with non-WGS 84 hub CRS in WKT1");
6110
97
        }
6111
25.9k
        auto params = d->transformation()->getTOWGS84Parameters(true);
6112
25.9k
        if (!formatter->useESRIDialect()) {
6113
25.8k
            formatter->setTOWGS84Parameters(params);
6114
25.8k
        }
6115
25.9k
        d->baseCRS()->_exportToWKT(formatter);
6116
25.9k
        formatter->setTOWGS84Parameters(std::vector<double>());
6117
25.9k
    }
6118
54.6k
}
6119
//! @endcond
6120
6121
// ---------------------------------------------------------------------------
6122
6123
//! @cond Doxygen_Suppress
6124
void BoundCRS::_exportToJSON(
6125
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
6126
35
{
6127
35
    auto writer = formatter->writer();
6128
35
    const auto &l_name = nameStr();
6129
6130
35
    auto objectContext(formatter->MakeObjectContext("BoundCRS", false));
6131
6132
35
    const auto &l_sourceCRS = d->baseCRS();
6133
6134
35
    if (!l_name.empty() && l_name != l_sourceCRS->nameStr()) {
6135
0
        writer->AddObjKey("name");
6136
0
        writer->Add(l_name);
6137
0
    }
6138
6139
35
    writer->AddObjKey("source_crs");
6140
35
    l_sourceCRS->_exportToJSON(formatter);
6141
6142
35
    writer->AddObjKey("target_crs");
6143
35
    const auto &l_targetCRS = d->hubCRS();
6144
35
    l_targetCRS->_exportToJSON(formatter);
6145
6146
35
    writer->AddObjKey("transformation");
6147
35
    formatter->setOmitTypeInImmediateChild();
6148
35
    formatter->setAbridgedTransformation(true);
6149
    // Only write the source_crs of the transformation if it is different from
6150
    // the source_crs of the BoundCRS. But don't do it for projectedCRS if its
6151
    // base CRS matches the source_crs of the transformation and the targetCRS
6152
    // is geographic
6153
35
    const auto sourceCRSAsProjectedCRS =
6154
35
        dynamic_cast<const ProjectedCRS *>(l_sourceCRS.get());
6155
35
    if (!l_sourceCRS->_isEquivalentTo(
6156
35
            d->transformation()->sourceCRS().get(),
6157
35
            util::IComparable::Criterion::EQUIVALENT) &&
6158
19
        (sourceCRSAsProjectedCRS == nullptr ||
6159
19
         (dynamic_cast<GeographicCRS *>(l_targetCRS.get()) &&
6160
19
          !sourceCRSAsProjectedCRS->baseCRS()->_isEquivalentTo(
6161
19
              d->transformation()->sourceCRS().get(),
6162
19
              util::IComparable::Criterion::EQUIVALENT)))) {
6163
0
        formatter->setAbridgedTransformationWriteSourceCRS(true);
6164
0
    }
6165
35
    d->transformation()->_exportToJSON(formatter);
6166
35
    formatter->setAbridgedTransformation(false);
6167
35
    formatter->setAbridgedTransformationWriteSourceCRS(false);
6168
6169
35
    ObjectUsage::baseExportToJSON(formatter);
6170
35
}
6171
//! @endcond
6172
6173
// ---------------------------------------------------------------------------
6174
6175
void BoundCRS::_exportToPROJString(
6176
    io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
6177
14.1k
{
6178
14.1k
    auto crs_exportable =
6179
14.1k
        dynamic_cast<const io::IPROJStringExportable *>(d->baseCRS_.get());
6180
14.1k
    if (!crs_exportable) {
6181
0
        io::FormattingException::Throw(
6182
0
            "baseCRS of BoundCRS cannot be exported as a PROJ string");
6183
0
    }
6184
6185
14.1k
    const char *geoidCRSValue = "";
6186
14.1k
    auto vdatumProj4GridName = getVDatumPROJ4GRIDS(
6187
14.1k
        formatter->getGeogCRSOfCompoundCRS().get(), &geoidCRSValue);
6188
14.1k
    if (!vdatumProj4GridName.empty()) {
6189
4.74k
        formatter->setVDatumExtension(vdatumProj4GridName, geoidCRSValue);
6190
4.74k
        crs_exportable->_exportToPROJString(formatter);
6191
4.74k
        formatter->setVDatumExtension(std::string(), std::string());
6192
9.38k
    } else {
6193
9.38k
        auto hdatumProj4GridName =
6194
9.38k
            getHDatumPROJ4GRIDS(formatter->databaseContext());
6195
9.38k
        if (!hdatumProj4GridName.empty()) {
6196
3.28k
            formatter->setHDatumExtension(hdatumProj4GridName);
6197
3.28k
            crs_exportable->_exportToPROJString(formatter);
6198
3.28k
            formatter->setHDatumExtension(std::string());
6199
6.10k
        } else {
6200
6.10k
            if (isTOWGS84Compatible()) {
6201
6.10k
                auto params = transformation()->getTOWGS84Parameters(true);
6202
6.10k
                formatter->setTOWGS84Parameters(params);
6203
6.10k
            }
6204
6.10k
            crs_exportable->_exportToPROJString(formatter);
6205
6.10k
            formatter->setTOWGS84Parameters(std::vector<double>());
6206
6.10k
        }
6207
9.38k
    }
6208
14.1k
}
6209
6210
// ---------------------------------------------------------------------------
6211
6212
bool BoundCRS::_isEquivalentTo(const util::IComparable *other,
6213
                               util::IComparable::Criterion criterion,
6214
13.1k
                               const io::DatabaseContextPtr &dbContext) const {
6215
13.1k
    auto otherBoundCRS = dynamic_cast<const BoundCRS *>(other);
6216
13.1k
    if (otherBoundCRS == nullptr ||
6217
31
        (criterion == util::IComparable::Criterion::STRICT &&
6218
13.1k
         !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
6219
13.1k
        return false;
6220
13.1k
    }
6221
31
    const auto standardCriterion = getStandardCriterion(criterion);
6222
31
    return d->baseCRS_->_isEquivalentTo(otherBoundCRS->d->baseCRS_.get(),
6223
31
                                        criterion, dbContext) &&
6224
4
           d->hubCRS_->_isEquivalentTo(otherBoundCRS->d->hubCRS_.get(),
6225
4
                                       criterion, dbContext) &&
6226
4
           d->transformation_->_isEquivalentTo(
6227
4
               otherBoundCRS->d->transformation_.get(), standardCriterion,
6228
4
               dbContext);
6229
13.1k
}
6230
6231
// ---------------------------------------------------------------------------
6232
6233
//! @cond Doxygen_Suppress
6234
6235
std::list<std::pair<CRSNNPtr, int>>
6236
2.85k
BoundCRS::_identify(const io::AuthorityFactoryPtr &authorityFactory) const {
6237
2.85k
    typedef std::pair<CRSNNPtr, int> Pair;
6238
2.85k
    std::list<Pair> res;
6239
2.85k
    if (!authorityFactory)
6240
0
        return res;
6241
2.85k
    std::list<Pair> resMatchOfTransfToWGS84;
6242
2.85k
    const io::DatabaseContextNNPtr &dbContext =
6243
2.85k
        authorityFactory->databaseContext();
6244
2.85k
    if (d->hubCRS_->_isEquivalentTo(GeographicCRS::EPSG_4326.get(),
6245
2.85k
                                    util::IComparable::Criterion::EQUIVALENT,
6246
2.85k
                                    dbContext)) {
6247
2.85k
        auto resTemp = d->baseCRS_->identify(authorityFactory);
6248
6249
2.85k
        std::string refTransfPROJString;
6250
2.85k
        bool refTransfPROJStringValid = false;
6251
2.85k
        auto refTransf = d->transformation_->normalizeForVisualization();
6252
2.85k
        try {
6253
2.85k
            refTransfPROJString = refTransf->exportToPROJString(
6254
2.85k
                io::PROJStringFormatter::create().get());
6255
2.85k
            refTransfPROJString = replaceAll(
6256
2.85k
                refTransfPROJString,
6257
2.85k
                " +rx=0 +ry=0 +rz=0 +s=0 +convention=position_vector", "");
6258
2.85k
            refTransfPROJStringValid = true;
6259
2.85k
        } catch (const std::exception &) {
6260
0
        }
6261
2.85k
        bool refIsNullTransform = false;
6262
2.85k
        if (isTOWGS84Compatible()) {
6263
2.85k
            auto params = transformation()->getTOWGS84Parameters(true);
6264
2.85k
            if (params == std::vector<double>{0, 0, 0, 0, 0, 0, 0}) {
6265
863
                refIsNullTransform = true;
6266
863
            }
6267
2.85k
        }
6268
6269
53.9k
        for (const auto &pair : resTemp) {
6270
53.9k
            const auto &candidateBaseCRS = pair.first;
6271
53.9k
            auto projCRS =
6272
53.9k
                dynamic_cast<const ProjectedCRS *>(candidateBaseCRS.get());
6273
53.9k
            auto geodCRS = projCRS ? projCRS->baseCRS().as_nullable()
6274
53.9k
                                   : util::nn_dynamic_pointer_cast<GeodeticCRS>(
6275
12.0k
                                         candidateBaseCRS);
6276
53.9k
            if (geodCRS) {
6277
53.9k
                auto context = operation::CoordinateOperationContext::create(
6278
53.9k
                    authorityFactory, nullptr, 0.0);
6279
53.9k
                context->setSpatialCriterion(
6280
53.9k
                    operation::CoordinateOperationContext::SpatialCriterion::
6281
53.9k
                        PARTIAL_INTERSECTION);
6282
53.9k
                auto ops =
6283
53.9k
                    operation::CoordinateOperationFactory::create()
6284
53.9k
                        ->createOperations(NN_NO_CHECK(geodCRS),
6285
53.9k
                                           GeographicCRS::EPSG_4326, context);
6286
6287
53.9k
                bool foundOp = false;
6288
137k
                for (const auto &op : ops) {
6289
137k
                    auto opNormalized = op->normalizeForVisualization();
6290
137k
                    std::string opTransfPROJString;
6291
137k
                    bool opTransfPROJStringValid = false;
6292
137k
                    const auto &opName = op->nameStr();
6293
137k
                    if (starts_with(
6294
137k
                            opName,
6295
137k
                            operation::BALLPARK_GEOCENTRIC_TRANSLATION) ||
6296
137k
                        starts_with(opName,
6297
137k
                                    operation::NULL_GEOGRAPHIC_OFFSET)) {
6298
20.6k
                        if (refIsNullTransform) {
6299
1.88k
                            res.emplace_back(create(candidateBaseCRS,
6300
1.88k
                                                    d->hubCRS_,
6301
1.88k
                                                    transformation()),
6302
1.88k
                                             pair.second);
6303
1.88k
                            foundOp = true;
6304
1.88k
                            break;
6305
1.88k
                        }
6306
18.7k
                        continue;
6307
20.6k
                    }
6308
117k
                    try {
6309
117k
                        opTransfPROJString = opNormalized->exportToPROJString(
6310
117k
                            io::PROJStringFormatter::create().get());
6311
117k
                        opTransfPROJStringValid = true;
6312
117k
                        opTransfPROJString =
6313
117k
                            replaceAll(opTransfPROJString,
6314
117k
                                       " +rx=0 +ry=0 +rz=0 +s=0 "
6315
117k
                                       "+convention=position_vector",
6316
117k
                                       "");
6317
117k
                    } catch (const std::exception &) {
6318
347
                    }
6319
117k
                    if ((refTransfPROJStringValid && opTransfPROJStringValid &&
6320
116k
                         refTransfPROJString == opTransfPROJString) ||
6321
111k
                        opNormalized->_isEquivalentTo(
6322
111k
                            refTransf.get(),
6323
111k
                            util::IComparable::Criterion::EQUIVALENT,
6324
111k
                            dbContext)) {
6325
7.36k
                        auto transf = util::nn_dynamic_pointer_cast<
6326
7.36k
                            operation::Transformation>(op);
6327
7.36k
                        if (transf) {
6328
5.84k
                            resMatchOfTransfToWGS84.emplace_back(
6329
5.84k
                                create(candidateBaseCRS, d->hubCRS_,
6330
5.84k
                                       NN_NO_CHECK(transf)),
6331
5.84k
                                pair.second);
6332
5.84k
                            foundOp = true;
6333
5.84k
                            break;
6334
5.84k
                        } else {
6335
1.52k
                            auto concatenated = dynamic_cast<
6336
1.52k
                                const operation::ConcatenatedOperation *>(
6337
1.52k
                                op.get());
6338
1.52k
                            if (concatenated) {
6339
                                // Case for EPSG:4807 / "NTF (Paris)" that is
6340
                                // made of a longitude rotation followed by a
6341
                                // Helmert The prime meridian shift will be
6342
                                // accounted elsewhere
6343
501
                                const auto &subops = concatenated->operations();
6344
501
                                if (subops.size() == 2) {
6345
488
                                    auto firstOpIsTransformation = dynamic_cast<
6346
488
                                        const operation::Transformation *>(
6347
488
                                        subops[0].get());
6348
488
                                    auto firstOpIsConversion = dynamic_cast<
6349
488
                                        const operation::Conversion *>(
6350
488
                                        subops[0].get());
6351
488
                                    if ((firstOpIsTransformation &&
6352
431
                                         firstOpIsTransformation
6353
431
                                             ->isLongitudeRotation()) ||
6354
488
                                        (dynamic_cast<DerivedCRS *>(
6355
488
                                             candidateBaseCRS.get()) &&
6356
442
                                         firstOpIsConversion)) {
6357
11
                                        transf = util::nn_dynamic_pointer_cast<
6358
11
                                            operation::Transformation>(
6359
11
                                            subops[1]);
6360
11
                                        if (transf &&
6361
11
                                            !starts_with(transf->nameStr(),
6362
11
                                                         "Ballpark geo")) {
6363
11
                                            resMatchOfTransfToWGS84
6364
11
                                                .emplace_back(
6365
11
                                                    create(candidateBaseCRS,
6366
11
                                                           d->hubCRS_,
6367
11
                                                           NN_NO_CHECK(transf)),
6368
11
                                                    pair.second);
6369
11
                                            foundOp = true;
6370
11
                                            break;
6371
11
                                        }
6372
11
                                    }
6373
488
                                }
6374
501
                            }
6375
1.52k
                        }
6376
7.36k
                    }
6377
117k
                }
6378
53.9k
                if (!foundOp) {
6379
46.1k
                    res.emplace_back(
6380
46.1k
                        create(candidateBaseCRS, d->hubCRS_, transformation()),
6381
46.1k
                        std::min(70, pair.second));
6382
46.1k
                }
6383
53.9k
            }
6384
53.9k
        }
6385
2.85k
    }
6386
2.85k
    return !resMatchOfTransfToWGS84.empty() ? resMatchOfTransfToWGS84 : res;
6387
2.85k
}
6388
//! @endcond
6389
6390
// ---------------------------------------------------------------------------
6391
6392
//! @cond Doxygen_Suppress
6393
struct DerivedGeodeticCRS::Private {};
6394
//! @endcond
6395
6396
// ---------------------------------------------------------------------------
6397
6398
//! @cond Doxygen_Suppress
6399
0
DerivedGeodeticCRS::~DerivedGeodeticCRS() = default;
6400
//! @endcond
6401
6402
// ---------------------------------------------------------------------------
6403
6404
DerivedGeodeticCRS::DerivedGeodeticCRS(
6405
    const GeodeticCRSNNPtr &baseCRSIn,
6406
    const operation::ConversionNNPtr &derivingConversionIn,
6407
    const cs::CartesianCSNNPtr &csIn)
6408
0
    : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6409
0
      GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6410
0
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedGeodeticCRS::DerivedGeodeticCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::GeodeticCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CartesianCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedGeodeticCRS::DerivedGeodeticCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::GeodeticCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CartesianCS> > const&)
6411
6412
// ---------------------------------------------------------------------------
6413
6414
DerivedGeodeticCRS::DerivedGeodeticCRS(
6415
    const GeodeticCRSNNPtr &baseCRSIn,
6416
    const operation::ConversionNNPtr &derivingConversionIn,
6417
    const cs::SphericalCSNNPtr &csIn)
6418
0
    : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6419
0
      GeodeticCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6420
0
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedGeodeticCRS::DerivedGeodeticCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::GeodeticCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::SphericalCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedGeodeticCRS::DerivedGeodeticCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::GeodeticCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::SphericalCS> > const&)
6421
6422
// ---------------------------------------------------------------------------
6423
6424
DerivedGeodeticCRS::DerivedGeodeticCRS(const DerivedGeodeticCRS &other)
6425
0
    : SingleCRS(other), GeodeticCRS(other), DerivedCRS(other), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedGeodeticCRS::DerivedGeodeticCRS(osgeo::proj::crs::DerivedGeodeticCRS const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedGeodeticCRS::DerivedGeodeticCRS(osgeo::proj::crs::DerivedGeodeticCRS const&)
6426
6427
// ---------------------------------------------------------------------------
6428
6429
0
CRSNNPtr DerivedGeodeticCRS::_shallowClone() const {
6430
0
    auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>(*this));
6431
0
    crs->assignSelf(crs);
6432
0
    crs->setDerivingConversionCRS();
6433
0
    return crs;
6434
0
}
6435
6436
// ---------------------------------------------------------------------------
6437
6438
/** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeodeticCRS.
6439
 *
6440
 * @return the base CRS.
6441
 */
6442
0
const GeodeticCRSNNPtr DerivedGeodeticCRS::baseCRS() const {
6443
0
    return NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
6444
0
        DerivedCRS::getPrivate()->baseCRS_));
6445
0
}
6446
6447
// ---------------------------------------------------------------------------
6448
6449
/** \brief Instantiate a DerivedGeodeticCRS from a base CRS, a deriving
6450
 * conversion and a cs::CartesianCS.
6451
 *
6452
 * @param properties See \ref general_properties.
6453
 * At minimum the name should be defined.
6454
 * @param baseCRSIn base CRS.
6455
 * @param derivingConversionIn the deriving conversion from the base CRS to this
6456
 * CRS.
6457
 * @param csIn the coordinate system.
6458
 * @return new DerivedGeodeticCRS.
6459
 */
6460
DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create(
6461
    const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn,
6462
    const operation::ConversionNNPtr &derivingConversionIn,
6463
0
    const cs::CartesianCSNNPtr &csIn) {
6464
0
    auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>(
6465
0
        baseCRSIn, derivingConversionIn, csIn));
6466
0
    crs->assignSelf(crs);
6467
0
    crs->setProperties(properties);
6468
0
    crs->setDerivingConversionCRS();
6469
0
    return crs;
6470
0
}
6471
6472
// ---------------------------------------------------------------------------
6473
6474
/** \brief Instantiate a DerivedGeodeticCRS from a base CRS, a deriving
6475
 * conversion and a cs::SphericalCS.
6476
 *
6477
 * @param properties See \ref general_properties.
6478
 * At minimum the name should be defined.
6479
 * @param baseCRSIn base CRS.
6480
 * @param derivingConversionIn the deriving conversion from the base CRS to this
6481
 * CRS.
6482
 * @param csIn the coordinate system.
6483
 * @return new DerivedGeodeticCRS.
6484
 */
6485
DerivedGeodeticCRSNNPtr DerivedGeodeticCRS::create(
6486
    const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn,
6487
    const operation::ConversionNNPtr &derivingConversionIn,
6488
0
    const cs::SphericalCSNNPtr &csIn) {
6489
0
    auto crs(DerivedGeodeticCRS::nn_make_shared<DerivedGeodeticCRS>(
6490
0
        baseCRSIn, derivingConversionIn, csIn));
6491
0
    crs->assignSelf(crs);
6492
0
    crs->setProperties(properties);
6493
0
    crs->setDerivingConversionCRS();
6494
0
    return crs;
6495
0
}
6496
6497
// ---------------------------------------------------------------------------
6498
6499
//! @cond Doxygen_Suppress
6500
0
void DerivedGeodeticCRS::_exportToWKT(io::WKTFormatter *formatter) const {
6501
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
6502
0
    if (!isWKT2) {
6503
0
        io::FormattingException::Throw(
6504
0
            "DerivedGeodeticCRS can only be exported to WKT2");
6505
0
    }
6506
0
    formatter->startNode(io::WKTConstants::GEODCRS, !identifiers().empty());
6507
0
    formatter->addQuotedString(nameStr());
6508
6509
0
    auto l_baseCRS = baseCRS();
6510
0
    formatter->startNode((formatter->use2019Keywords() &&
6511
0
                          dynamic_cast<const GeographicCRS *>(l_baseCRS.get()))
6512
0
                             ? io::WKTConstants::BASEGEOGCRS
6513
0
                             : io::WKTConstants::BASEGEODCRS,
6514
0
                         !baseCRS()->identifiers().empty());
6515
0
    formatter->addQuotedString(l_baseCRS->nameStr());
6516
0
    auto l_datum = l_baseCRS->datum();
6517
0
    if (l_datum) {
6518
0
        l_datum->_exportToWKT(formatter);
6519
0
    } else {
6520
0
        auto l_datumEnsemble = datumEnsemble();
6521
0
        assert(l_datumEnsemble);
6522
0
        l_datumEnsemble->_exportToWKT(formatter);
6523
0
    }
6524
0
    l_baseCRS->primeMeridian()->_exportToWKT(formatter);
6525
0
    if (formatter->use2019Keywords() &&
6526
0
        !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())) {
6527
0
        l_baseCRS->formatID(formatter);
6528
0
    }
6529
0
    formatter->endNode();
6530
6531
0
    formatter->setUseDerivingConversion(true);
6532
0
    derivingConversionRef()->_exportToWKT(formatter);
6533
0
    formatter->setUseDerivingConversion(false);
6534
6535
0
    coordinateSystem()->_exportToWKT(formatter);
6536
0
    ObjectUsage::baseExportToWKT(formatter);
6537
0
    formatter->endNode();
6538
0
}
6539
//! @endcond
6540
6541
// ---------------------------------------------------------------------------
6542
6543
void DerivedGeodeticCRS::_exportToPROJString(
6544
    io::PROJStringFormatter *) const // throw(io::FormattingException)
6545
0
{
6546
0
    throw io::FormattingException(
6547
0
        "DerivedGeodeticCRS cannot be exported to PROJ string");
6548
0
}
6549
6550
// ---------------------------------------------------------------------------
6551
6552
bool DerivedGeodeticCRS::_isEquivalentTo(
6553
    const util::IComparable *other, util::IComparable::Criterion criterion,
6554
0
    const io::DatabaseContextPtr &dbContext) const {
6555
0
    auto otherDerivedCRS = dynamic_cast<const DerivedGeodeticCRS *>(other);
6556
0
    return otherDerivedCRS != nullptr &&
6557
0
           DerivedCRS::_isEquivalentTo(other, criterion, dbContext);
6558
0
}
6559
6560
// ---------------------------------------------------------------------------
6561
6562
//! @cond Doxygen_Suppress
6563
6564
std::list<std::pair<CRSNNPtr, int>>
6565
0
DerivedGeodeticCRS::_identify(const io::AuthorityFactoryPtr &factory) const {
6566
0
    return CRS::_identify(factory);
6567
0
}
6568
6569
//! @endcond
6570
6571
// ---------------------------------------------------------------------------
6572
6573
//! @cond Doxygen_Suppress
6574
struct DerivedGeographicCRS::Private {};
6575
//! @endcond
6576
6577
// ---------------------------------------------------------------------------
6578
6579
//! @cond Doxygen_Suppress
6580
29.7k
DerivedGeographicCRS::~DerivedGeographicCRS() = default;
6581
//! @endcond
6582
6583
// ---------------------------------------------------------------------------
6584
6585
DerivedGeographicCRS::DerivedGeographicCRS(
6586
    const GeodeticCRSNNPtr &baseCRSIn,
6587
    const operation::ConversionNNPtr &derivingConversionIn,
6588
    const cs::EllipsoidalCSNNPtr &csIn)
6589
29.6k
    : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6590
29.6k
      GeographicCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6591
29.6k
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedGeographicCRS::DerivedGeographicCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::GeodeticCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::EllipsoidalCS> > const&)
osgeo::proj::crs::DerivedGeographicCRS::DerivedGeographicCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::GeodeticCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::EllipsoidalCS> > const&)
Line
Count
Source
6589
29.6k
    : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6590
29.6k
      GeographicCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6591
29.6k
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {}
6592
6593
// ---------------------------------------------------------------------------
6594
6595
DerivedGeographicCRS::DerivedGeographicCRS(const DerivedGeographicCRS &other)
6596
97
    : SingleCRS(other), GeographicCRS(other), DerivedCRS(other), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedGeographicCRS::DerivedGeographicCRS(osgeo::proj::crs::DerivedGeographicCRS const&)
osgeo::proj::crs::DerivedGeographicCRS::DerivedGeographicCRS(osgeo::proj::crs::DerivedGeographicCRS const&)
Line
Count
Source
6596
97
    : SingleCRS(other), GeographicCRS(other), DerivedCRS(other), d(nullptr) {}
6597
6598
// ---------------------------------------------------------------------------
6599
6600
97
CRSNNPtr DerivedGeographicCRS::_shallowClone() const {
6601
97
    auto crs(DerivedGeographicCRS::nn_make_shared<DerivedGeographicCRS>(*this));
6602
97
    crs->assignSelf(crs);
6603
97
    crs->setDerivingConversionCRS();
6604
97
    return crs;
6605
97
}
6606
6607
// ---------------------------------------------------------------------------
6608
6609
/** \brief Return the base CRS (a GeodeticCRS) of a DerivedGeographicCRS.
6610
 *
6611
 * @return the base CRS.
6612
 */
6613
7.08k
const GeodeticCRSNNPtr DerivedGeographicCRS::baseCRS() const {
6614
7.08k
    return NN_NO_CHECK(util::nn_dynamic_pointer_cast<GeodeticCRS>(
6615
7.08k
        DerivedCRS::getPrivate()->baseCRS_));
6616
7.08k
}
6617
6618
// ---------------------------------------------------------------------------
6619
6620
/** \brief Instantiate a DerivedGeographicCRS from a base CRS, a deriving
6621
 * conversion and a cs::EllipsoidalCS.
6622
 *
6623
 * @param properties See \ref general_properties.
6624
 * At minimum the name should be defined.
6625
 * @param baseCRSIn base CRS.
6626
 * @param derivingConversionIn the deriving conversion from the base CRS to this
6627
 * CRS.
6628
 * @param csIn the coordinate system.
6629
 * @return new DerivedGeographicCRS.
6630
 */
6631
DerivedGeographicCRSNNPtr DerivedGeographicCRS::create(
6632
    const util::PropertyMap &properties, const GeodeticCRSNNPtr &baseCRSIn,
6633
    const operation::ConversionNNPtr &derivingConversionIn,
6634
29.6k
    const cs::EllipsoidalCSNNPtr &csIn) {
6635
29.6k
    auto crs(DerivedGeographicCRS::nn_make_shared<DerivedGeographicCRS>(
6636
29.6k
        baseCRSIn, derivingConversionIn, csIn));
6637
29.6k
    crs->assignSelf(crs);
6638
29.6k
    crs->setProperties(properties);
6639
29.6k
    crs->setDerivingConversionCRS();
6640
29.6k
    return crs;
6641
29.6k
}
6642
6643
// ---------------------------------------------------------------------------
6644
6645
//! @cond Doxygen_Suppress
6646
7.13k
void DerivedGeographicCRS::_exportToWKT(io::WKTFormatter *formatter) const {
6647
7.13k
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
6648
7.13k
    if (!isWKT2) {
6649
3.17k
        io::FormattingException::Throw(
6650
3.17k
            "DerivedGeographicCRS can only be exported to WKT2");
6651
3.17k
    }
6652
3.96k
    formatter->startNode(formatter->use2019Keywords()
6653
3.96k
                             ? io::WKTConstants::GEOGCRS
6654
3.96k
                             : io::WKTConstants::GEODCRS,
6655
3.96k
                         !identifiers().empty());
6656
3.96k
    formatter->addQuotedString(nameStr());
6657
6658
3.96k
    auto l_baseCRS = baseCRS();
6659
3.96k
    formatter->startNode((formatter->use2019Keywords() &&
6660
3.96k
                          dynamic_cast<const GeographicCRS *>(l_baseCRS.get()))
6661
3.96k
                             ? io::WKTConstants::BASEGEOGCRS
6662
3.96k
                             : io::WKTConstants::BASEGEODCRS,
6663
3.96k
                         !l_baseCRS->identifiers().empty());
6664
3.96k
    formatter->addQuotedString(l_baseCRS->nameStr());
6665
3.96k
    l_baseCRS->exportDatumOrDatumEnsembleToWkt(formatter);
6666
3.96k
    l_baseCRS->primeMeridian()->_exportToWKT(formatter);
6667
3.96k
    if (formatter->use2019Keywords() &&
6668
3.96k
        !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())) {
6669
3.96k
        l_baseCRS->formatID(formatter);
6670
3.96k
    }
6671
3.96k
    formatter->endNode();
6672
6673
3.96k
    formatter->setUseDerivingConversion(true);
6674
3.96k
    derivingConversionRef()->_exportToWKT(formatter);
6675
3.96k
    formatter->setUseDerivingConversion(false);
6676
6677
3.96k
    coordinateSystem()->_exportToWKT(formatter);
6678
3.96k
    ObjectUsage::baseExportToWKT(formatter);
6679
3.96k
    formatter->endNode();
6680
3.96k
}
6681
//! @endcond
6682
6683
// ---------------------------------------------------------------------------
6684
6685
void DerivedGeographicCRS::_exportToPROJString(
6686
    io::PROJStringFormatter *formatter) const // throw(io::FormattingException)
6687
3.69k
{
6688
3.69k
    const auto &l_conv = derivingConversionRef();
6689
3.69k
    const auto &methodName = l_conv->method()->nameStr();
6690
6691
3.69k
    for (const char *substr :
6692
3.69k
         {"PROJ ob_tran o_proj=longlat", "PROJ ob_tran o_proj=lonlat",
6693
10.7k
          "PROJ ob_tran o_proj=latlon", "PROJ ob_tran o_proj=latlong"}) {
6694
10.7k
        if (starts_with(methodName, substr)) {
6695
3.69k
            l_conv->_exportToPROJString(formatter);
6696
3.69k
            return;
6697
3.69k
        }
6698
10.7k
    }
6699
6700
0
    if (ci_equal(methodName,
6701
0
                 PROJ_WKT2_NAME_METHOD_POLE_ROTATION_GRIB_CONVENTION) ||
6702
0
        ci_equal(methodName,
6703
0
                 PROJ_WKT2_NAME_METHOD_POLE_ROTATION_NETCDF_CF_CONVENTION)) {
6704
0
        l_conv->_exportToPROJString(formatter);
6705
0
        return;
6706
0
    }
6707
6708
0
    throw io::FormattingException(
6709
0
        "DerivedGeographicCRS cannot be exported to PROJ string");
6710
0
}
6711
6712
// ---------------------------------------------------------------------------
6713
6714
bool DerivedGeographicCRS::_isEquivalentTo(
6715
    const util::IComparable *other, util::IComparable::Criterion criterion,
6716
36
    const io::DatabaseContextPtr &dbContext) const {
6717
36
    auto otherDerivedCRS = dynamic_cast<const DerivedGeographicCRS *>(other);
6718
36
    return otherDerivedCRS != nullptr &&
6719
30
           DerivedCRS::_isEquivalentTo(other, criterion, dbContext);
6720
36
}
6721
6722
// ---------------------------------------------------------------------------
6723
6724
/** \brief Return a variant of this CRS "demoted" to a 2D one, if not already
6725
 * the case.
6726
 *
6727
 *
6728
 * @param newName Name of the new CRS. If empty, nameStr() will be used.
6729
 * @param dbContext Database context to look for potentially already registered
6730
 *                  2D CRS. May be nullptr.
6731
 * @return a new CRS demoted to 2D, or the current one if already 2D or not
6732
 * applicable.
6733
 * @since 8.1.1
6734
 */
6735
DerivedGeographicCRSNNPtr DerivedGeographicCRS::demoteTo2D(
6736
0
    const std::string &newName, const io::DatabaseContextPtr &dbContext) const {
6737
6738
0
    const auto &axisList = coordinateSystem()->axisList();
6739
0
    if (axisList.size() == 3) {
6740
0
        auto cs = cs::EllipsoidalCS::create(util::PropertyMap(), axisList[0],
6741
0
                                            axisList[1]);
6742
0
        auto baseGeog2DCRS = util::nn_dynamic_pointer_cast<GeodeticCRS>(
6743
0
            baseCRS()->demoteTo2D(std::string(), dbContext));
6744
0
        return DerivedGeographicCRS::create(
6745
0
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6746
0
                                    !newName.empty() ? newName : nameStr()),
6747
0
            NN_CHECK_THROW(std::move(baseGeog2DCRS)), derivingConversion(),
6748
0
            std::move(cs));
6749
0
    }
6750
6751
0
    return NN_NO_CHECK(std::dynamic_pointer_cast<DerivedGeographicCRS>(
6752
0
        shared_from_this().as_nullable()));
6753
0
}
6754
6755
// ---------------------------------------------------------------------------
6756
6757
//! @cond Doxygen_Suppress
6758
6759
std::list<std::pair<CRSNNPtr, int>>
6760
1
DerivedGeographicCRS::_identify(const io::AuthorityFactoryPtr &factory) const {
6761
1
    return CRS::_identify(factory);
6762
1
}
6763
6764
//! @endcond
6765
6766
// ---------------------------------------------------------------------------
6767
6768
//! @cond Doxygen_Suppress
6769
struct DerivedProjectedCRS::Private {};
6770
//! @endcond
6771
6772
// ---------------------------------------------------------------------------
6773
6774
//! @cond Doxygen_Suppress
6775
0
DerivedProjectedCRS::~DerivedProjectedCRS() = default;
6776
//! @endcond
6777
6778
// ---------------------------------------------------------------------------
6779
6780
DerivedProjectedCRS::DerivedProjectedCRS(
6781
    const ProjectedCRSNNPtr &baseCRSIn,
6782
    const operation::ConversionNNPtr &derivingConversionIn,
6783
    const cs::CoordinateSystemNNPtr &csIn)
6784
0
    : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
6785
0
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedProjectedCRS::DerivedProjectedCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::ProjectedCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CoordinateSystem> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedProjectedCRS::DerivedProjectedCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::ProjectedCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CoordinateSystem> > const&)
6786
6787
// ---------------------------------------------------------------------------
6788
6789
DerivedProjectedCRS::DerivedProjectedCRS(const DerivedProjectedCRS &other)
6790
0
    : SingleCRS(other), DerivedCRS(other), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedProjectedCRS::DerivedProjectedCRS(osgeo::proj::crs::DerivedProjectedCRS const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedProjectedCRS::DerivedProjectedCRS(osgeo::proj::crs::DerivedProjectedCRS const&)
6791
6792
// ---------------------------------------------------------------------------
6793
6794
0
CRSNNPtr DerivedProjectedCRS::_shallowClone() const {
6795
0
    auto crs(DerivedProjectedCRS::nn_make_shared<DerivedProjectedCRS>(*this));
6796
0
    crs->assignSelf(crs);
6797
0
    crs->setDerivingConversionCRS();
6798
0
    return crs;
6799
0
}
6800
6801
// ---------------------------------------------------------------------------
6802
6803
/** \brief Return the base CRS (a ProjectedCRS) of a DerivedProjectedCRS.
6804
 *
6805
 * @return the base CRS.
6806
 */
6807
0
const ProjectedCRSNNPtr DerivedProjectedCRS::baseCRS() const {
6808
0
    return NN_NO_CHECK(util::nn_dynamic_pointer_cast<ProjectedCRS>(
6809
0
        DerivedCRS::getPrivate()->baseCRS_));
6810
0
}
6811
6812
// ---------------------------------------------------------------------------
6813
6814
/** \brief Instantiate a DerivedProjectedCRS from a base CRS, a deriving
6815
 * conversion and a cs::CS.
6816
 *
6817
 * @param properties See \ref general_properties.
6818
 * At minimum the name should be defined.
6819
 * @param baseCRSIn base CRS.
6820
 * @param derivingConversionIn the deriving conversion from the base CRS to this
6821
 * CRS.
6822
 * @param csIn the coordinate system.
6823
 * @return new DerivedProjectedCRS.
6824
 */
6825
DerivedProjectedCRSNNPtr DerivedProjectedCRS::create(
6826
    const util::PropertyMap &properties, const ProjectedCRSNNPtr &baseCRSIn,
6827
    const operation::ConversionNNPtr &derivingConversionIn,
6828
0
    const cs::CoordinateSystemNNPtr &csIn) {
6829
0
    auto crs(DerivedProjectedCRS::nn_make_shared<DerivedProjectedCRS>(
6830
0
        baseCRSIn, derivingConversionIn, csIn));
6831
0
    crs->assignSelf(crs);
6832
0
    crs->setProperties(properties);
6833
0
    crs->setDerivingConversionCRS();
6834
0
    return crs;
6835
0
}
6836
6837
// ---------------------------------------------------------------------------
6838
6839
/** \brief Return a variant of this CRS "demoted" to a 2D one, if not already
6840
 * the case.
6841
 *
6842
 *
6843
 * @param newName Name of the new CRS. If empty, nameStr() will be used.
6844
 * @param dbContext Database context to look for potentially already registered
6845
 *                  2D CRS. May be nullptr.
6846
 * @return a new CRS demoted to 2D, or the current one if already 2D or not
6847
 * applicable.
6848
 * @since 9.1.1
6849
 */
6850
DerivedProjectedCRSNNPtr
6851
DerivedProjectedCRS::demoteTo2D(const std::string &newName,
6852
0
                                const io::DatabaseContextPtr &dbContext) const {
6853
6854
0
    const auto &axisList = coordinateSystem()->axisList();
6855
0
    if (axisList.size() == 3) {
6856
0
        auto cs = cs::CartesianCS::create(util::PropertyMap(), axisList[0],
6857
0
                                          axisList[1]);
6858
0
        auto baseProj2DCRS = util::nn_dynamic_pointer_cast<ProjectedCRS>(
6859
0
            baseCRS()->demoteTo2D(std::string(), dbContext));
6860
0
        return DerivedProjectedCRS::create(
6861
0
            util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
6862
0
                                    !newName.empty() ? newName : nameStr()),
6863
0
            NN_CHECK_THROW(std::move(baseProj2DCRS)), derivingConversion(),
6864
0
            std::move(cs));
6865
0
    }
6866
6867
0
    return NN_NO_CHECK(std::dynamic_pointer_cast<DerivedProjectedCRS>(
6868
0
        shared_from_this().as_nullable()));
6869
0
}
6870
6871
// ---------------------------------------------------------------------------
6872
6873
//! @cond Doxygen_Suppress
6874
0
void DerivedProjectedCRS::_exportToWKT(io::WKTFormatter *formatter) const {
6875
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
6876
0
    if (!isWKT2 || !formatter->use2019Keywords()) {
6877
0
        io::FormattingException::Throw(
6878
0
            "DerivedProjectedCRS can only be exported to WKT2:2019");
6879
0
    }
6880
0
    formatter->startNode(io::WKTConstants::DERIVEDPROJCRS,
6881
0
                         !identifiers().empty());
6882
0
    formatter->addQuotedString(nameStr());
6883
6884
0
    {
6885
0
        auto l_baseProjCRS = baseCRS();
6886
0
        formatter->startNode(io::WKTConstants::BASEPROJCRS,
6887
0
                             !l_baseProjCRS->identifiers().empty());
6888
0
        formatter->addQuotedString(l_baseProjCRS->nameStr());
6889
6890
0
        auto l_baseGeodCRS = l_baseProjCRS->baseCRS();
6891
0
        auto &geodeticCRSAxisList =
6892
0
            l_baseGeodCRS->coordinateSystem()->axisList();
6893
6894
0
        formatter->startNode(
6895
0
            dynamic_cast<const GeographicCRS *>(l_baseGeodCRS.get())
6896
0
                ? io::WKTConstants::BASEGEOGCRS
6897
0
                : io::WKTConstants::BASEGEODCRS,
6898
0
            !l_baseGeodCRS->identifiers().empty());
6899
0
        formatter->addQuotedString(l_baseGeodCRS->nameStr());
6900
0
        l_baseGeodCRS->exportDatumOrDatumEnsembleToWkt(formatter);
6901
        // insert ellipsoidal cs unit when the units of the map
6902
        // projection angular parameters are not explicitly given within those
6903
        // parameters. See
6904
        // http://docs.opengeospatial.org/is/12-063r5/12-063r5.html#61
6905
0
        if (formatter->primeMeridianOrParameterUnitOmittedIfSameAsAxis() &&
6906
0
            !geodeticCRSAxisList.empty()) {
6907
0
            geodeticCRSAxisList[0]->unit()._exportToWKT(formatter);
6908
0
        }
6909
0
        l_baseGeodCRS->primeMeridian()->_exportToWKT(formatter);
6910
0
        formatter->endNode();
6911
6912
0
        l_baseProjCRS->derivingConversionRef()->_exportToWKT(formatter);
6913
6914
0
        const auto &baseCSAxisList =
6915
0
            l_baseProjCRS->coordinateSystem()->axisList();
6916
        // Current WKT grammar (as of WKT2 18-010r11) does not allow a
6917
        // BASEPROJCRS.CS node, but in situations where this is ambiguous, emit
6918
        // one. Cf WKTParser::Private::buildProjectedCRS() for more details
6919
0
        if (!baseCSAxisList.empty() &&
6920
0
            baseCSAxisList[0]->unit() != common::UnitOfMeasure::METRE &&
6921
0
            l_baseProjCRS->identifiers().empty()) {
6922
0
            bool knownBaseCRS = false;
6923
0
            auto &dbContext = formatter->databaseContext();
6924
0
            if (dbContext) {
6925
0
                auto authFactory = io::AuthorityFactory::create(
6926
0
                    NN_NO_CHECK(dbContext), std::string());
6927
0
                auto res = authFactory->createObjectsFromName(
6928
0
                    l_baseProjCRS->nameStr(),
6929
0
                    {io::AuthorityFactory::ObjectType::PROJECTED_CRS}, false,
6930
0
                    2);
6931
0
                if (res.size() == 1) {
6932
0
                    knownBaseCRS = true;
6933
0
                }
6934
0
            }
6935
0
            if (!knownBaseCRS) {
6936
0
                l_baseProjCRS->coordinateSystem()->_exportToWKT(formatter);
6937
0
            }
6938
0
        }
6939
6940
0
        if (identifiers().empty() && !l_baseProjCRS->identifiers().empty()) {
6941
0
            l_baseProjCRS->formatID(formatter);
6942
0
        }
6943
6944
0
        formatter->endNode();
6945
0
    }
6946
6947
0
    formatter->setUseDerivingConversion(true);
6948
0
    derivingConversionRef()->_exportToWKT(formatter);
6949
0
    formatter->setUseDerivingConversion(false);
6950
6951
0
    coordinateSystem()->_exportToWKT(formatter);
6952
0
    ObjectUsage::baseExportToWKT(formatter);
6953
0
    formatter->endNode();
6954
0
}
6955
//! @endcond
6956
6957
// ---------------------------------------------------------------------------
6958
6959
bool DerivedProjectedCRS::_isEquivalentTo(
6960
    const util::IComparable *other, util::IComparable::Criterion criterion,
6961
0
    const io::DatabaseContextPtr &dbContext) const {
6962
0
    auto otherDerivedCRS = dynamic_cast<const DerivedProjectedCRS *>(other);
6963
0
    return otherDerivedCRS != nullptr &&
6964
0
           DerivedCRS::_isEquivalentTo(other, criterion, dbContext);
6965
0
}
6966
6967
// ---------------------------------------------------------------------------
6968
6969
//! @cond Doxygen_Suppress
6970
void DerivedProjectedCRS::addUnitConvertAndAxisSwap(
6971
0
    io::PROJStringFormatter *formatter) const {
6972
0
    ProjectedCRS::addUnitConvertAndAxisSwap(coordinateSystem()->axisList(),
6973
0
                                            formatter, false);
6974
0
}
6975
//! @endcond
6976
6977
// ---------------------------------------------------------------------------
6978
6979
//! @cond Doxygen_Suppress
6980
struct TemporalCRS::Private {};
6981
//! @endcond
6982
6983
// ---------------------------------------------------------------------------
6984
6985
//! @cond Doxygen_Suppress
6986
0
TemporalCRS::~TemporalCRS() = default;
6987
//! @endcond
6988
6989
// ---------------------------------------------------------------------------
6990
6991
TemporalCRS::TemporalCRS(const datum::TemporalDatumNNPtr &datumIn,
6992
                         const cs::TemporalCSNNPtr &csIn)
6993
0
    : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::TemporalCRS::TemporalCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::TemporalDatum> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::TemporalCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::TemporalCRS::TemporalCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::TemporalDatum> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::TemporalCS> > const&)
6994
6995
// ---------------------------------------------------------------------------
6996
6997
TemporalCRS::TemporalCRS(const TemporalCRS &other)
6998
0
    : SingleCRS(other), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::TemporalCRS::TemporalCRS(osgeo::proj::crs::TemporalCRS const&)
Unexecuted instantiation: osgeo::proj::crs::TemporalCRS::TemporalCRS(osgeo::proj::crs::TemporalCRS const&)
6999
7000
// ---------------------------------------------------------------------------
7001
7002
0
CRSNNPtr TemporalCRS::_shallowClone() const {
7003
0
    auto crs(TemporalCRS::nn_make_shared<TemporalCRS>(*this));
7004
0
    crs->assignSelf(crs);
7005
0
    return crs;
7006
0
}
7007
7008
// ---------------------------------------------------------------------------
7009
7010
/** \brief Return the datum::TemporalDatum associated with the CRS.
7011
 *
7012
 * @return a TemporalDatum
7013
 */
7014
0
const datum::TemporalDatumNNPtr TemporalCRS::datum() const {
7015
0
    return NN_NO_CHECK(std::static_pointer_cast<datum::TemporalDatum>(
7016
0
        SingleCRS::getPrivate()->datum));
7017
0
}
7018
7019
// ---------------------------------------------------------------------------
7020
7021
/** \brief Return the cs::TemporalCS associated with the CRS.
7022
 *
7023
 * @return a TemporalCS
7024
 */
7025
0
const cs::TemporalCSNNPtr TemporalCRS::coordinateSystem() const {
7026
0
    return util::nn_static_pointer_cast<cs::TemporalCS>(
7027
0
        SingleCRS::getPrivate()->coordinateSystem);
7028
0
}
7029
7030
// ---------------------------------------------------------------------------
7031
7032
/** \brief Instantiate a TemporalCRS from a datum and a coordinate system.
7033
 *
7034
 * @param properties See \ref general_properties.
7035
 * At minimum the name should be defined.
7036
 * @param datumIn the datum.
7037
 * @param csIn the coordinate system.
7038
 * @return new TemporalCRS.
7039
 */
7040
TemporalCRSNNPtr TemporalCRS::create(const util::PropertyMap &properties,
7041
                                     const datum::TemporalDatumNNPtr &datumIn,
7042
0
                                     const cs::TemporalCSNNPtr &csIn) {
7043
0
    auto crs(TemporalCRS::nn_make_shared<TemporalCRS>(datumIn, csIn));
7044
0
    crs->assignSelf(crs);
7045
0
    crs->setProperties(properties);
7046
0
    return crs;
7047
0
}
7048
7049
// ---------------------------------------------------------------------------
7050
7051
//! @cond Doxygen_Suppress
7052
0
void TemporalCRS::_exportToWKT(io::WKTFormatter *formatter) const {
7053
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
7054
0
    if (!isWKT2) {
7055
0
        io::FormattingException::Throw(
7056
0
            "TemporalCRS can only be exported to WKT2");
7057
0
    }
7058
0
    formatter->startNode(io::WKTConstants::TIMECRS, !identifiers().empty());
7059
0
    formatter->addQuotedString(nameStr());
7060
0
    datum()->_exportToWKT(formatter);
7061
0
    coordinateSystem()->_exportToWKT(formatter);
7062
0
    ObjectUsage::baseExportToWKT(formatter);
7063
0
    formatter->endNode();
7064
0
}
7065
//! @endcond
7066
7067
// ---------------------------------------------------------------------------
7068
7069
//! @cond Doxygen_Suppress
7070
void TemporalCRS::_exportToJSON(
7071
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
7072
0
{
7073
0
    auto writer = formatter->writer();
7074
0
    auto objectContext(
7075
0
        formatter->MakeObjectContext("TemporalCRS", !identifiers().empty()));
7076
7077
0
    writer->AddObjKey("name");
7078
0
    const auto &l_name = nameStr();
7079
0
    if (l_name.empty()) {
7080
0
        writer->Add("unnamed");
7081
0
    } else {
7082
0
        writer->Add(l_name);
7083
0
    }
7084
7085
0
    writer->AddObjKey("datum");
7086
0
    formatter->setOmitTypeInImmediateChild();
7087
0
    datum()->_exportToJSON(formatter);
7088
7089
0
    writer->AddObjKey("coordinate_system");
7090
0
    formatter->setOmitTypeInImmediateChild();
7091
0
    coordinateSystem()->_exportToJSON(formatter);
7092
7093
0
    ObjectUsage::baseExportToJSON(formatter);
7094
0
}
7095
//! @endcond
7096
7097
// ---------------------------------------------------------------------------
7098
7099
bool TemporalCRS::_isEquivalentTo(
7100
    const util::IComparable *other, util::IComparable::Criterion criterion,
7101
0
    const io::DatabaseContextPtr &dbContext) const {
7102
0
    auto otherTemporalCRS = dynamic_cast<const TemporalCRS *>(other);
7103
0
    return otherTemporalCRS != nullptr &&
7104
0
           SingleCRS::baseIsEquivalentTo(other, criterion, dbContext);
7105
0
}
7106
7107
// ---------------------------------------------------------------------------
7108
7109
//! @cond Doxygen_Suppress
7110
struct EngineeringCRS::Private {};
7111
//! @endcond
7112
7113
// ---------------------------------------------------------------------------
7114
7115
//! @cond Doxygen_Suppress
7116
251k
EngineeringCRS::~EngineeringCRS() = default;
7117
//! @endcond
7118
7119
// ---------------------------------------------------------------------------
7120
7121
EngineeringCRS::EngineeringCRS(const datum::EngineeringDatumNNPtr &datumIn,
7122
                               const cs::CoordinateSystemNNPtr &csIn)
7123
249k
    : SingleCRS(datumIn.as_nullable(), nullptr, csIn),
7124
249k
      d(std::make_unique<Private>()) {}
Unexecuted instantiation: osgeo::proj::crs::EngineeringCRS::EngineeringCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::EngineeringDatum> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CoordinateSystem> > const&)
osgeo::proj::crs::EngineeringCRS::EngineeringCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::EngineeringDatum> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CoordinateSystem> > const&)
Line
Count
Source
7123
249k
    : SingleCRS(datumIn.as_nullable(), nullptr, csIn),
7124
249k
      d(std::make_unique<Private>()) {}
7125
7126
// ---------------------------------------------------------------------------
7127
7128
EngineeringCRS::EngineeringCRS(const EngineeringCRS &other)
7129
1.55k
    : SingleCRS(other), d(std::make_unique<Private>(*(other.d))) {}
Unexecuted instantiation: osgeo::proj::crs::EngineeringCRS::EngineeringCRS(osgeo::proj::crs::EngineeringCRS const&)
osgeo::proj::crs::EngineeringCRS::EngineeringCRS(osgeo::proj::crs::EngineeringCRS const&)
Line
Count
Source
7129
1.55k
    : SingleCRS(other), d(std::make_unique<Private>(*(other.d))) {}
7130
7131
// ---------------------------------------------------------------------------
7132
7133
1.55k
CRSNNPtr EngineeringCRS::_shallowClone() const {
7134
1.55k
    auto crs(EngineeringCRS::nn_make_shared<EngineeringCRS>(*this));
7135
1.55k
    crs->assignSelf(crs);
7136
1.55k
    return crs;
7137
1.55k
}
7138
7139
// ---------------------------------------------------------------------------
7140
7141
/** \brief Return the datum::EngineeringDatum associated with the CRS.
7142
 *
7143
 * @return a EngineeringDatum
7144
 */
7145
154k
const datum::EngineeringDatumNNPtr EngineeringCRS::datum() const {
7146
154k
    return NN_NO_CHECK(std::static_pointer_cast<datum::EngineeringDatum>(
7147
154k
        SingleCRS::getPrivate()->datum));
7148
154k
}
7149
7150
// ---------------------------------------------------------------------------
7151
7152
/** \brief Instantiate a EngineeringCRS from a datum and a coordinate system.
7153
 *
7154
 * @param properties See \ref general_properties.
7155
 * At minimum the name should be defined.
7156
 * @param datumIn the datum.
7157
 * @param csIn the coordinate system.
7158
 * @return new EngineeringCRS.
7159
 */
7160
EngineeringCRSNNPtr
7161
EngineeringCRS::create(const util::PropertyMap &properties,
7162
                       const datum::EngineeringDatumNNPtr &datumIn,
7163
249k
                       const cs::CoordinateSystemNNPtr &csIn) {
7164
249k
    auto crs(EngineeringCRS::nn_make_shared<EngineeringCRS>(datumIn, csIn));
7165
249k
    crs->assignSelf(crs);
7166
249k
    crs->setProperties(properties);
7167
7168
249k
    return crs;
7169
249k
}
7170
7171
// ---------------------------------------------------------------------------
7172
7173
//! @cond Doxygen_Suppress
7174
109k
void EngineeringCRS::_exportToWKT(io::WKTFormatter *formatter) const {
7175
109k
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
7176
109k
    formatter->startNode(isWKT2 ? io::WKTConstants::ENGCRS
7177
109k
                                : io::WKTConstants::LOCAL_CS,
7178
109k
                         !identifiers().empty());
7179
109k
    formatter->addQuotedString(nameStr());
7180
109k
    const auto &datumName = datum()->nameStr();
7181
109k
    if (isWKT2 ||
7182
88.7k
        (!datumName.empty() && datumName != UNKNOWN_ENGINEERING_DATUM)) {
7183
22.5k
        datum()->_exportToWKT(formatter);
7184
22.5k
    }
7185
109k
    if (!isWKT2) {
7186
88.7k
        coordinateSystem()->axisList()[0]->unit()._exportToWKT(formatter);
7187
88.7k
    }
7188
7189
109k
    const auto oldAxisOutputRule = formatter->outputAxis();
7190
109k
    formatter->setOutputAxis(io::WKTFormatter::OutputAxisRule::YES);
7191
109k
    coordinateSystem()->_exportToWKT(formatter);
7192
109k
    formatter->setOutputAxis(oldAxisOutputRule);
7193
7194
109k
    ObjectUsage::baseExportToWKT(formatter);
7195
109k
    formatter->endNode();
7196
109k
}
7197
//! @endcond
7198
7199
// ---------------------------------------------------------------------------
7200
7201
//! @cond Doxygen_Suppress
7202
void EngineeringCRS::_exportToJSON(
7203
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
7204
0
{
7205
0
    auto writer = formatter->writer();
7206
0
    auto objectContext(
7207
0
        formatter->MakeObjectContext("EngineeringCRS", !identifiers().empty()));
7208
7209
0
    writer->AddObjKey("name");
7210
0
    const auto &l_name = nameStr();
7211
0
    if (l_name.empty()) {
7212
0
        writer->Add("unnamed");
7213
0
    } else {
7214
0
        writer->Add(l_name);
7215
0
    }
7216
7217
0
    writer->AddObjKey("datum");
7218
0
    formatter->setOmitTypeInImmediateChild();
7219
0
    datum()->_exportToJSON(formatter);
7220
7221
0
    writer->AddObjKey("coordinate_system");
7222
0
    formatter->setOmitTypeInImmediateChild();
7223
0
    coordinateSystem()->_exportToJSON(formatter);
7224
7225
0
    ObjectUsage::baseExportToJSON(formatter);
7226
0
}
7227
//! @endcond
7228
7229
// ---------------------------------------------------------------------------
7230
7231
bool EngineeringCRS::_isEquivalentTo(
7232
    const util::IComparable *other, util::IComparable::Criterion criterion,
7233
8.60k
    const io::DatabaseContextPtr &dbContext) const {
7234
8.60k
    auto otherEngineeringCRS = dynamic_cast<const EngineeringCRS *>(other);
7235
8.60k
    if (otherEngineeringCRS == nullptr ||
7236
1
        (criterion == util::IComparable::Criterion::STRICT &&
7237
8.60k
         !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
7238
8.60k
        return false;
7239
8.60k
    }
7240
7241
    // Check datum
7242
1
    const auto &thisDatum = datum();
7243
1
    const auto &otherDatum = otherEngineeringCRS->datum();
7244
1
    if (!thisDatum->_isEquivalentTo(otherDatum.get(), criterion, dbContext)) {
7245
1
        return false;
7246
1
    }
7247
7248
    // Check coordinate system
7249
0
    const auto &thisCS = coordinateSystem();
7250
0
    const auto &otherCS = otherEngineeringCRS->coordinateSystem();
7251
0
    if (!(thisCS->_isEquivalentTo(otherCS.get(), criterion, dbContext))) {
7252
0
        const auto thisCartCS = dynamic_cast<cs::CartesianCS *>(thisCS.get());
7253
0
        const auto otherCartCS = dynamic_cast<cs::CartesianCS *>(otherCS.get());
7254
0
        const auto &thisAxisList = thisCS->axisList();
7255
0
        const auto &otherAxisList = otherCS->axisList();
7256
        // Check particular case of
7257
        // https://github.com/r-spatial/sf/issues/2049#issuecomment-1486600723
7258
0
        if (criterion != util::IComparable::Criterion::STRICT && thisCartCS &&
7259
0
            otherCartCS && thisAxisList.size() == 2 &&
7260
0
            otherAxisList.size() == 2 &&
7261
0
            ((&thisAxisList[0]->direction() ==
7262
0
                  &cs::AxisDirection::UNSPECIFIED &&
7263
0
              &thisAxisList[1]->direction() ==
7264
0
                  &cs::AxisDirection::UNSPECIFIED) ||
7265
0
             (&otherAxisList[0]->direction() ==
7266
0
                  &cs::AxisDirection::UNSPECIFIED &&
7267
0
              &otherAxisList[1]->direction() ==
7268
0
                  &cs::AxisDirection::UNSPECIFIED)) &&
7269
0
            ((thisAxisList[0]->nameStr() == "X" &&
7270
0
              otherAxisList[0]->nameStr() == "Easting" &&
7271
0
              thisAxisList[1]->nameStr() == "Y" &&
7272
0
              otherAxisList[1]->nameStr() == "Northing") ||
7273
0
             (otherAxisList[0]->nameStr() == "X" &&
7274
0
              thisAxisList[0]->nameStr() == "Easting" &&
7275
0
              otherAxisList[1]->nameStr() == "Y" &&
7276
0
              thisAxisList[1]->nameStr() == "Northing"))) {
7277
0
            return true;
7278
0
        }
7279
0
        return false;
7280
0
    }
7281
7282
0
    return true;
7283
0
}
7284
7285
// ---------------------------------------------------------------------------
7286
7287
//! @cond Doxygen_Suppress
7288
struct ParametricCRS::Private {};
7289
//! @endcond
7290
7291
// ---------------------------------------------------------------------------
7292
7293
//! @cond Doxygen_Suppress
7294
0
ParametricCRS::~ParametricCRS() = default;
7295
//! @endcond
7296
7297
// ---------------------------------------------------------------------------
7298
7299
ParametricCRS::ParametricCRS(const datum::ParametricDatumNNPtr &datumIn,
7300
                             const cs::ParametricCSNNPtr &csIn)
7301
0
    : SingleCRS(datumIn.as_nullable(), nullptr, csIn), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::ParametricCRS::ParametricCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::ParametricDatum> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::ParametricCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::ParametricCRS::ParametricCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::datum::ParametricDatum> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::ParametricCS> > const&)
7302
7303
// ---------------------------------------------------------------------------
7304
7305
ParametricCRS::ParametricCRS(const ParametricCRS &other)
7306
0
    : SingleCRS(other), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::ParametricCRS::ParametricCRS(osgeo::proj::crs::ParametricCRS const&)
Unexecuted instantiation: osgeo::proj::crs::ParametricCRS::ParametricCRS(osgeo::proj::crs::ParametricCRS const&)
7307
7308
// ---------------------------------------------------------------------------
7309
7310
0
CRSNNPtr ParametricCRS::_shallowClone() const {
7311
0
    auto crs(ParametricCRS::nn_make_shared<ParametricCRS>(*this));
7312
0
    crs->assignSelf(crs);
7313
0
    return crs;
7314
0
}
7315
7316
// ---------------------------------------------------------------------------
7317
7318
/** \brief Return the datum::ParametricDatum associated with the CRS.
7319
 *
7320
 * @return a ParametricDatum
7321
 */
7322
0
const datum::ParametricDatumNNPtr ParametricCRS::datum() const {
7323
0
    return NN_NO_CHECK(std::static_pointer_cast<datum::ParametricDatum>(
7324
0
        SingleCRS::getPrivate()->datum));
7325
0
}
7326
7327
// ---------------------------------------------------------------------------
7328
7329
/** \brief Return the cs::TemporalCS associated with the CRS.
7330
 *
7331
 * @return a TemporalCS
7332
 */
7333
0
const cs::ParametricCSNNPtr ParametricCRS::coordinateSystem() const {
7334
0
    return util::nn_static_pointer_cast<cs::ParametricCS>(
7335
0
        SingleCRS::getPrivate()->coordinateSystem);
7336
0
}
7337
7338
// ---------------------------------------------------------------------------
7339
7340
/** \brief Instantiate a ParametricCRS from a datum and a coordinate system.
7341
 *
7342
 * @param properties See \ref general_properties.
7343
 * At minimum the name should be defined.
7344
 * @param datumIn the datum.
7345
 * @param csIn the coordinate system.
7346
 * @return new ParametricCRS.
7347
 */
7348
ParametricCRSNNPtr
7349
ParametricCRS::create(const util::PropertyMap &properties,
7350
                      const datum::ParametricDatumNNPtr &datumIn,
7351
0
                      const cs::ParametricCSNNPtr &csIn) {
7352
0
    auto crs(ParametricCRS::nn_make_shared<ParametricCRS>(datumIn, csIn));
7353
0
    crs->assignSelf(crs);
7354
0
    crs->setProperties(properties);
7355
0
    return crs;
7356
0
}
7357
7358
// ---------------------------------------------------------------------------
7359
7360
//! @cond Doxygen_Suppress
7361
0
void ParametricCRS::_exportToWKT(io::WKTFormatter *formatter) const {
7362
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
7363
0
    if (!isWKT2) {
7364
0
        io::FormattingException::Throw(
7365
0
            "ParametricCRS can only be exported to WKT2");
7366
0
    }
7367
0
    formatter->startNode(io::WKTConstants::PARAMETRICCRS,
7368
0
                         !identifiers().empty());
7369
0
    formatter->addQuotedString(nameStr());
7370
0
    datum()->_exportToWKT(formatter);
7371
0
    coordinateSystem()->_exportToWKT(formatter);
7372
0
    ObjectUsage::baseExportToWKT(formatter);
7373
0
    formatter->endNode();
7374
0
}
7375
//! @endcond
7376
7377
// ---------------------------------------------------------------------------
7378
7379
//! @cond Doxygen_Suppress
7380
void ParametricCRS::_exportToJSON(
7381
    io::JSONFormatter *formatter) const // throw(io::FormattingException)
7382
0
{
7383
0
    auto writer = formatter->writer();
7384
0
    auto objectContext(
7385
0
        formatter->MakeObjectContext("ParametricCRS", !identifiers().empty()));
7386
7387
0
    writer->AddObjKey("name");
7388
0
    const auto &l_name = nameStr();
7389
0
    if (l_name.empty()) {
7390
0
        writer->Add("unnamed");
7391
0
    } else {
7392
0
        writer->Add(l_name);
7393
0
    }
7394
7395
0
    writer->AddObjKey("datum");
7396
0
    formatter->setOmitTypeInImmediateChild();
7397
0
    datum()->_exportToJSON(formatter);
7398
7399
0
    writer->AddObjKey("coordinate_system");
7400
0
    formatter->setOmitTypeInImmediateChild();
7401
0
    coordinateSystem()->_exportToJSON(formatter);
7402
7403
0
    ObjectUsage::baseExportToJSON(formatter);
7404
0
}
7405
//! @endcond
7406
7407
// ---------------------------------------------------------------------------
7408
7409
bool ParametricCRS::_isEquivalentTo(
7410
    const util::IComparable *other, util::IComparable::Criterion criterion,
7411
0
    const io::DatabaseContextPtr &dbContext) const {
7412
0
    auto otherParametricCRS = dynamic_cast<const ParametricCRS *>(other);
7413
0
    return otherParametricCRS != nullptr &&
7414
0
           SingleCRS::baseIsEquivalentTo(other, criterion, dbContext);
7415
0
}
7416
7417
// ---------------------------------------------------------------------------
7418
7419
//! @cond Doxygen_Suppress
7420
struct DerivedVerticalCRS::Private {};
7421
//! @endcond
7422
7423
// ---------------------------------------------------------------------------
7424
7425
//! @cond Doxygen_Suppress
7426
0
DerivedVerticalCRS::~DerivedVerticalCRS() = default;
7427
//! @endcond
7428
7429
// ---------------------------------------------------------------------------
7430
7431
DerivedVerticalCRS::DerivedVerticalCRS(
7432
    const VerticalCRSNNPtr &baseCRSIn,
7433
    const operation::ConversionNNPtr &derivingConversionIn,
7434
    const cs::VerticalCSNNPtr &csIn)
7435
0
    : SingleCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
7436
0
      VerticalCRS(baseCRSIn->datum(), baseCRSIn->datumEnsemble(), csIn),
7437
0
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedVerticalCRS::DerivedVerticalCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::VerticalCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::VerticalCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedVerticalCRS::DerivedVerticalCRS(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::VerticalCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::VerticalCS> > const&)
7438
7439
// ---------------------------------------------------------------------------
7440
7441
DerivedVerticalCRS::DerivedVerticalCRS(const DerivedVerticalCRS &other)
7442
0
    : SingleCRS(other), VerticalCRS(other), DerivedCRS(other), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedVerticalCRS::DerivedVerticalCRS(osgeo::proj::crs::DerivedVerticalCRS const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedVerticalCRS::DerivedVerticalCRS(osgeo::proj::crs::DerivedVerticalCRS const&)
7443
7444
// ---------------------------------------------------------------------------
7445
7446
0
CRSNNPtr DerivedVerticalCRS::_shallowClone() const {
7447
0
    auto crs(DerivedVerticalCRS::nn_make_shared<DerivedVerticalCRS>(*this));
7448
0
    crs->assignSelf(crs);
7449
0
    crs->setDerivingConversionCRS();
7450
0
    return crs;
7451
0
}
7452
7453
// ---------------------------------------------------------------------------
7454
7455
/** \brief Return the base CRS (a VerticalCRS) of a DerivedVerticalCRS.
7456
 *
7457
 * @return the base CRS.
7458
 */
7459
0
const VerticalCRSNNPtr DerivedVerticalCRS::baseCRS() const {
7460
0
    return NN_NO_CHECK(util::nn_dynamic_pointer_cast<VerticalCRS>(
7461
0
        DerivedCRS::getPrivate()->baseCRS_));
7462
0
}
7463
7464
// ---------------------------------------------------------------------------
7465
7466
/** \brief Instantiate a DerivedVerticalCRS from a base CRS, a deriving
7467
 * conversion and a cs::VerticalCS.
7468
 *
7469
 * @param properties See \ref general_properties.
7470
 * At minimum the name should be defined.
7471
 * @param baseCRSIn base CRS.
7472
 * @param derivingConversionIn the deriving conversion from the base CRS to this
7473
 * CRS.
7474
 * @param csIn the coordinate system.
7475
 * @return new DerivedVerticalCRS.
7476
 */
7477
DerivedVerticalCRSNNPtr DerivedVerticalCRS::create(
7478
    const util::PropertyMap &properties, const VerticalCRSNNPtr &baseCRSIn,
7479
    const operation::ConversionNNPtr &derivingConversionIn,
7480
0
    const cs::VerticalCSNNPtr &csIn) {
7481
0
    auto crs(DerivedVerticalCRS::nn_make_shared<DerivedVerticalCRS>(
7482
0
        baseCRSIn, derivingConversionIn, csIn));
7483
0
    crs->assignSelf(crs);
7484
0
    crs->setProperties(properties);
7485
0
    crs->setDerivingConversionCRS();
7486
0
    return crs;
7487
0
}
7488
7489
// ---------------------------------------------------------------------------
7490
7491
//! @cond Doxygen_Suppress
7492
0
void DerivedVerticalCRS::_exportToWKT(io::WKTFormatter *formatter) const {
7493
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
7494
0
    if (!isWKT2) {
7495
7496
0
        bool useBaseMethod = true;
7497
0
        const DerivedVerticalCRS *dvcrs = this;
7498
0
        while (true) {
7499
            // If the derived vertical CRS is obtained through simple conversion
7500
            // methods that just do unit change or height/depth reversal, export
7501
            // it as a regular VerticalCRS
7502
0
            const int methodCode =
7503
0
                dvcrs->derivingConversionRef()->method()->getEPSGCode();
7504
0
            if (methodCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT ||
7505
0
                methodCode ==
7506
0
                    EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT_NO_CONV_FACTOR ||
7507
0
                methodCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
7508
0
                dvcrs = dynamic_cast<DerivedVerticalCRS *>(baseCRS().get());
7509
0
                if (dvcrs == nullptr) {
7510
0
                    break;
7511
0
                }
7512
0
            } else {
7513
0
                useBaseMethod = false;
7514
0
                break;
7515
0
            }
7516
0
        }
7517
0
        if (useBaseMethod) {
7518
0
            VerticalCRS::_exportToWKT(formatter);
7519
0
            return;
7520
0
        }
7521
7522
0
        io::FormattingException::Throw(
7523
0
            "DerivedVerticalCRS can only be exported to WKT2");
7524
0
    }
7525
0
    baseExportToWKT(formatter, io::WKTConstants::VERTCRS,
7526
0
                    io::WKTConstants::BASEVERTCRS);
7527
0
}
7528
//! @endcond
7529
7530
// ---------------------------------------------------------------------------
7531
7532
void DerivedVerticalCRS::_exportToPROJString(
7533
    io::PROJStringFormatter *) const // throw(io::FormattingException)
7534
0
{
7535
0
    throw io::FormattingException(
7536
0
        "DerivedVerticalCRS cannot be exported to PROJ string");
7537
0
}
7538
7539
// ---------------------------------------------------------------------------
7540
7541
bool DerivedVerticalCRS::_isEquivalentTo(
7542
    const util::IComparable *other, util::IComparable::Criterion criterion,
7543
0
    const io::DatabaseContextPtr &dbContext) const {
7544
0
    auto otherDerivedCRS = dynamic_cast<const DerivedVerticalCRS *>(other);
7545
0
    return otherDerivedCRS != nullptr &&
7546
0
           DerivedCRS::_isEquivalentTo(other, criterion, dbContext);
7547
0
}
7548
7549
// ---------------------------------------------------------------------------
7550
7551
//! @cond Doxygen_Suppress
7552
7553
std::list<std::pair<CRSNNPtr, int>>
7554
0
DerivedVerticalCRS::_identify(const io::AuthorityFactoryPtr &factory) const {
7555
0
    return CRS::_identify(factory);
7556
0
}
7557
7558
//! @endcond
7559
7560
// ---------------------------------------------------------------------------
7561
7562
//! @cond Doxygen_Suppress
7563
template <class DerivedCRSTraits>
7564
struct DerivedCRSTemplate<DerivedCRSTraits>::Private {};
7565
//! @endcond
7566
7567
// ---------------------------------------------------------------------------
7568
7569
//! @cond Doxygen_Suppress
7570
template <class DerivedCRSTraits>
7571
0
DerivedCRSTemplate<DerivedCRSTraits>::~DerivedCRSTemplate() = default;
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::~DerivedCRSTemplate()
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::~DerivedCRSTemplate()
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::~DerivedCRSTemplate()
7572
//! @endcond
7573
7574
// ---------------------------------------------------------------------------
7575
7576
template <class DerivedCRSTraits>
7577
DerivedCRSTemplate<DerivedCRSTraits>::DerivedCRSTemplate(
7578
    const BaseNNPtr &baseCRSIn,
7579
    const operation::ConversionNNPtr &derivingConversionIn, const CSNNPtr &csIn)
7580
0
    : SingleCRS(baseCRSIn->datum().as_nullable(), nullptr, csIn),
7581
0
      BaseType(baseCRSIn->datum(), csIn),
7582
0
      DerivedCRS(baseCRSIn, derivingConversionIn, csIn), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::DerivedCRSTemplate(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::EngineeringCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CoordinateSystem> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::DerivedCRSTemplate(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::EngineeringCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CoordinateSystem> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::DerivedCRSTemplate(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::ParametricCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::ParametricCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::DerivedCRSTemplate(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::ParametricCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::ParametricCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::DerivedCRSTemplate(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::TemporalCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::TemporalCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::DerivedCRSTemplate(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::TemporalCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::TemporalCS> > const&)
7583
7584
// ---------------------------------------------------------------------------
7585
7586
template <class DerivedCRSTraits>
7587
DerivedCRSTemplate<DerivedCRSTraits>::DerivedCRSTemplate(
7588
    const DerivedCRSTemplate &other)
7589
0
    : SingleCRS(other), BaseType(other), DerivedCRS(other), d(nullptr) {}
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::DerivedCRSTemplate(osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits> const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::DerivedCRSTemplate(osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits> const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::DerivedCRSTemplate(osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits> const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::DerivedCRSTemplate(osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits> const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::DerivedCRSTemplate(osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits> const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::DerivedCRSTemplate(osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits> const&)
7590
7591
// ---------------------------------------------------------------------------
7592
7593
template <class DerivedCRSTraits>
7594
const typename DerivedCRSTemplate<DerivedCRSTraits>::BaseNNPtr
7595
0
DerivedCRSTemplate<DerivedCRSTraits>::baseCRS() const {
7596
0
    auto l_baseCRS = DerivedCRS::getPrivate()->baseCRS_;
7597
0
    return NN_NO_CHECK(util::nn_dynamic_pointer_cast<BaseType>(l_baseCRS));
7598
0
}
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::baseCRS() const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::baseCRS() const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::baseCRS() const
7599
7600
// ---------------------------------------------------------------------------
7601
7602
//! @cond Doxygen_Suppress
7603
7604
template <class DerivedCRSTraits>
7605
0
CRSNNPtr DerivedCRSTemplate<DerivedCRSTraits>::_shallowClone() const {
7606
0
    auto crs(DerivedCRSTemplate::nn_make_shared<DerivedCRSTemplate>(*this));
7607
0
    crs->assignSelf(crs);
7608
0
    crs->setDerivingConversionCRS();
7609
0
    return crs;
7610
0
}
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::_shallowClone() const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::_shallowClone() const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::_shallowClone() const
7611
7612
// ---------------------------------------------------------------------------
7613
7614
template <class DerivedCRSTraits>
7615
typename DerivedCRSTemplate<DerivedCRSTraits>::NNPtr
7616
DerivedCRSTemplate<DerivedCRSTraits>::create(
7617
    const util::PropertyMap &properties, const BaseNNPtr &baseCRSIn,
7618
    const operation::ConversionNNPtr &derivingConversionIn,
7619
0
    const CSNNPtr &csIn) {
7620
0
    auto crs(DerivedCRSTemplate::nn_make_shared<DerivedCRSTemplate>(
7621
0
        baseCRSIn, derivingConversionIn, csIn));
7622
0
    crs->assignSelf(crs);
7623
0
    crs->setProperties(properties);
7624
0
    crs->setDerivingConversionCRS();
7625
0
    return crs;
7626
0
}
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::EngineeringCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::CoordinateSystem> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::ParametricCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::ParametricCS> > const&)
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::create(osgeo::proj::util::PropertyMap const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::TemporalCRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Conversion> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::cs::TemporalCS> > const&)
7627
7628
// ---------------------------------------------------------------------------
7629
7630
template <class DerivedCRSTraits>
7631
0
const char *DerivedCRSTemplate<DerivedCRSTraits>::className() const {
7632
0
    return DerivedCRSTraits::CRSName().c_str();
7633
0
}
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::className() const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::className() const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::className() const
7634
7635
// ---------------------------------------------------------------------------
7636
7637
static void DerivedCRSTemplateCheckExportToWKT(io::WKTFormatter *formatter,
7638
                                               const std::string &crsName,
7639
0
                                               bool wkt2_2019_only) {
7640
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
7641
0
    if (!isWKT2 || (wkt2_2019_only && !formatter->use2019Keywords())) {
7642
0
        io::FormattingException::Throw(crsName +
7643
0
                                       " can only be exported to WKT2" +
7644
0
                                       (wkt2_2019_only ? ":2019" : ""));
7645
0
    }
7646
0
}
7647
7648
// ---------------------------------------------------------------------------
7649
7650
template <class DerivedCRSTraits>
7651
void DerivedCRSTemplate<DerivedCRSTraits>::_exportToWKT(
7652
0
    io::WKTFormatter *formatter) const {
7653
0
    DerivedCRSTemplateCheckExportToWKT(formatter, DerivedCRSTraits::CRSName(),
7654
0
                                       DerivedCRSTraits::wkt2_2019_only);
7655
0
    baseExportToWKT(formatter, DerivedCRSTraits::WKTKeyword(),
7656
0
                    DerivedCRSTraits::WKTBaseKeyword());
7657
0
}
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::_exportToWKT(osgeo::proj::io::WKTFormatter*) const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::_exportToWKT(osgeo::proj::io::WKTFormatter*) const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::_exportToWKT(osgeo::proj::io::WKTFormatter*) const
7658
7659
// ---------------------------------------------------------------------------
7660
7661
template <class DerivedCRSTraits>
7662
bool DerivedCRSTemplate<DerivedCRSTraits>::_isEquivalentTo(
7663
    const util::IComparable *other, util::IComparable::Criterion criterion,
7664
0
    const io::DatabaseContextPtr &dbContext) const {
7665
0
    auto otherDerivedCRS = dynamic_cast<const DerivedCRSTemplate *>(other);
7666
0
    return otherDerivedCRS != nullptr &&
7667
0
           DerivedCRS::_isEquivalentTo(other, criterion, dbContext);
7668
0
}
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedEngineeringCRSTraits>::_isEquivalentTo(osgeo::proj::util::IComparable const*, osgeo::proj::util::IComparable::Criterion, std::__1::shared_ptr<osgeo::proj::io::DatabaseContext> const&) const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedParametricCRSTraits>::_isEquivalentTo(osgeo::proj::util::IComparable const*, osgeo::proj::util::IComparable::Criterion, std::__1::shared_ptr<osgeo::proj::io::DatabaseContext> const&) const
Unexecuted instantiation: osgeo::proj::crs::DerivedCRSTemplate<osgeo::proj::crs::DerivedTemporalCRSTraits>::_isEquivalentTo(osgeo::proj::util::IComparable const*, osgeo::proj::util::IComparable::Criterion, std::__1::shared_ptr<osgeo::proj::io::DatabaseContext> const&) const
7669
7670
//! @endcond
7671
7672
// ---------------------------------------------------------------------------
7673
7674
//! @cond Doxygen_Suppress
7675
static const std::string STRING_DerivedEngineeringCRS("DerivedEngineeringCRS");
7676
0
const std::string &DerivedEngineeringCRSTraits::CRSName() {
7677
0
    return STRING_DerivedEngineeringCRS;
7678
0
}
7679
0
const std::string &DerivedEngineeringCRSTraits::WKTKeyword() {
7680
0
    return io::WKTConstants::ENGCRS;
7681
0
}
7682
0
const std::string &DerivedEngineeringCRSTraits::WKTBaseKeyword() {
7683
0
    return io::WKTConstants::BASEENGCRS;
7684
0
}
7685
7686
template class DerivedCRSTemplate<DerivedEngineeringCRSTraits>;
7687
//! @endcond
7688
7689
// ---------------------------------------------------------------------------
7690
7691
//! @cond Doxygen_Suppress
7692
static const std::string STRING_DerivedParametricCRS("DerivedParametricCRS");
7693
0
const std::string &DerivedParametricCRSTraits::CRSName() {
7694
0
    return STRING_DerivedParametricCRS;
7695
0
}
7696
0
const std::string &DerivedParametricCRSTraits::WKTKeyword() {
7697
0
    return io::WKTConstants::PARAMETRICCRS;
7698
0
}
7699
0
const std::string &DerivedParametricCRSTraits::WKTBaseKeyword() {
7700
0
    return io::WKTConstants::BASEPARAMCRS;
7701
0
}
7702
7703
template class DerivedCRSTemplate<DerivedParametricCRSTraits>;
7704
//! @endcond
7705
7706
// ---------------------------------------------------------------------------
7707
7708
//! @cond Doxygen_Suppress
7709
static const std::string STRING_DerivedTemporalCRS("DerivedTemporalCRS");
7710
0
const std::string &DerivedTemporalCRSTraits::CRSName() {
7711
0
    return STRING_DerivedTemporalCRS;
7712
0
}
7713
0
const std::string &DerivedTemporalCRSTraits::WKTKeyword() {
7714
0
    return io::WKTConstants::TIMECRS;
7715
0
}
7716
0
const std::string &DerivedTemporalCRSTraits::WKTBaseKeyword() {
7717
0
    return io::WKTConstants::BASETIMECRS;
7718
0
}
7719
7720
template class DerivedCRSTemplate<DerivedTemporalCRSTraits>;
7721
//! @endcond
7722
7723
// ---------------------------------------------------------------------------
7724
7725
} // namespace crs
7726
NS_PROJ_END