Coverage Report

Created: 2025-07-11 06:33

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