Coverage Report

Created: 2024-02-25 06:14

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