Coverage Report

Created: 2026-08-31 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/iso19111/datum.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#include "proj/datum.hpp"
34
#include "proj/common.hpp"
35
#include "proj/io.hpp"
36
#include "proj/metadata.hpp"
37
#include "proj/util.hpp"
38
39
#include "proj/internal/datum_internal.hpp"
40
#include "proj/internal/internal.hpp"
41
#include "proj/internal/io_internal.hpp"
42
43
// PROJ include order is sensitive
44
// clang-format off
45
#include "proj.h"
46
#include "proj_internal.h"
47
// clang-format on
48
49
#include "proj_json_streaming_writer.hpp"
50
51
#include <cmath>
52
#include <cstdlib>
53
#include <memory>
54
#include <string>
55
56
using namespace NS_PROJ::internal;
57
58
#if 0
59
namespace dropbox{ namespace oxygen {
60
template<> nn<NS_PROJ::datum::DatumPtr>::~nn() = default;
61
template<> nn<NS_PROJ::datum::DatumEnsemblePtr>::~nn() = default;
62
template<> nn<NS_PROJ::datum::PrimeMeridianPtr>::~nn() = default;
63
template<> nn<NS_PROJ::datum::EllipsoidPtr>::~nn() = default;
64
template<> nn<NS_PROJ::datum::GeodeticReferenceFramePtr>::~nn() = default;
65
template<> nn<NS_PROJ::datum::DynamicGeodeticReferenceFramePtr>::~nn() = default;
66
template<> nn<NS_PROJ::datum::VerticalReferenceFramePtr>::~nn() = default;
67
template<> nn<NS_PROJ::datum::DynamicVerticalReferenceFramePtr>::~nn() = default;
68
template<> nn<NS_PROJ::datum::EngineeringDatumPtr>::~nn() = default;
69
template<> nn<NS_PROJ::datum::TemporalDatumPtr>::~nn() = default;
70
template<> nn<NS_PROJ::datum::ParametricDatumPtr>::~nn() = default;
71
}}
72
#endif
73
74
NS_PROJ_START
75
namespace datum {
76
77
// ---------------------------------------------------------------------------
78
79
//! @cond Doxygen_Suppress
80
16
static util::PropertyMap createMapNameEPSGCode(const char *name, int code) {
81
16
    return util::PropertyMap()
82
16
        .set(common::IdentifiedObject::NAME_KEY, name)
83
16
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
84
16
        .set(metadata::Identifier::CODE_KEY, code);
85
16
}
86
//! @endcond
87
88
// ---------------------------------------------------------------------------
89
90
//! @cond Doxygen_Suppress
91
struct Datum::Private {
92
    util::optional<std::string> anchorDefinition{};
93
    std::shared_ptr<util::optional<common::Measure>> anchorEpoch =
94
        std::make_shared<util::optional<common::Measure>>();
95
    util::optional<common::DateTime> publicationDate{};
96
    common::IdentifiedObjectPtr conventionalRS{};
97
98
    // cppcheck-suppress functionStatic
99
    void exportAnchorDefinition(io::WKTFormatter *formatter) const;
100
101
    // cppcheck-suppress functionStatic
102
    void exportAnchorEpoch(io::WKTFormatter *formatter) const;
103
104
    // cppcheck-suppress functionStatic
105
    void exportAnchorDefinition(io::JSONFormatter *formatter) const;
106
107
    // cppcheck-suppress functionStatic
108
    void exportAnchorEpoch(io::JSONFormatter *formatter) const;
109
};
110
111
// ---------------------------------------------------------------------------
112
113
0
void Datum::Private::exportAnchorDefinition(io::WKTFormatter *formatter) const {
114
0
    if (anchorDefinition) {
115
0
        formatter->startNode(io::WKTConstants::ANCHOR, false);
116
0
        formatter->addQuotedString(*anchorDefinition);
117
0
        formatter->endNode();
118
0
    }
119
0
}
120
121
// ---------------------------------------------------------------------------
122
123
0
void Datum::Private::exportAnchorEpoch(io::WKTFormatter *formatter) const {
124
0
    if (anchorEpoch->has_value()) {
125
0
        formatter->startNode(io::WKTConstants::ANCHOREPOCH, false);
126
0
        const double year =
127
0
            (*anchorEpoch)->convertToUnit(common::UnitOfMeasure::YEAR);
128
0
        formatter->add(getRoundedEpochInDecimalYear(year));
129
0
        formatter->endNode();
130
0
    }
131
0
}
132
133
// ---------------------------------------------------------------------------
134
135
void Datum::Private::exportAnchorDefinition(
136
0
    io::JSONFormatter *formatter) const {
137
0
    if (anchorDefinition) {
138
0
        auto writer = formatter->writer();
139
0
        writer->AddObjKey("anchor");
140
0
        writer->Add(*anchorDefinition);
141
0
    }
142
0
}
143
144
// ---------------------------------------------------------------------------
145
146
0
void Datum::Private::exportAnchorEpoch(io::JSONFormatter *formatter) const {
147
0
    if (anchorEpoch->has_value()) {
148
0
        auto writer = formatter->writer();
149
0
        writer->AddObjKey("anchor_epoch");
150
0
        const double year =
151
0
            (*anchorEpoch)->convertToUnit(common::UnitOfMeasure::YEAR);
152
0
        writer->Add(getRoundedEpochInDecimalYear(year));
153
0
    }
154
0
}
155
156
//! @endcond
157
158
// ---------------------------------------------------------------------------
159
160
460k
Datum::Datum() : d(std::make_unique<Private>()) {}
161
162
// ---------------------------------------------------------------------------
163
164
#ifdef notdef
165
Datum::Datum(const Datum &other)
166
    : ObjectUsage(other), d(std::make_unique<Private>(*other.d)) {}
167
#endif
168
169
// ---------------------------------------------------------------------------
170
171
//! @cond Doxygen_Suppress
172
460k
Datum::~Datum() = default;
173
//! @endcond
174
175
// ---------------------------------------------------------------------------
176
177
/** \brief Return the anchor definition.
178
 *
179
 * A description - possibly including coordinates of an identified point or
180
 * points - of the relationship used to anchor a coordinate system to the
181
 * Earth or alternate object.
182
 * <ul>
183
 * <li>For modern geodetic reference frames the anchor may be a set of station
184
 * coordinates; if the reference frame is dynamic it will also include
185
 * coordinate velocities. For a traditional geodetic datum, this anchor may be
186
 * a point known as the fundamental point, which is traditionally the point
187
 * where the relationship between geoid and ellipsoid is defined, together
188
 * with a direction from that point.</li>
189
 * <li>For a vertical reference frame the anchor may be the zero level at one
190
 * or more defined locations or a conventionally defined surface.</li>
191
 * <li>For an engineering datum, the anchor may be an identified physical point
192
 * with the orientation defined relative to the object.</li>
193
 * </ul>
194
 *
195
 * @return the anchor definition, or empty.
196
 */
197
1.11k
const util::optional<std::string> &Datum::anchorDefinition() const {
198
1.11k
    return d->anchorDefinition;
199
1.11k
}
200
201
// ---------------------------------------------------------------------------
202
203
/** \brief Return the anchor epoch.
204
 *
205
 * Epoch at which a static reference frame matches a dynamic reference frame
206
 * from which it has been derived.
207
 *
208
 * Note: Not to be confused with the frame reference epoch of dynamic geodetic
209
 * and dynamic vertical reference frames. Nor with the epoch at which a
210
 * reference frame is defined to be aligned with another reference frame;
211
 * this information should be included in the datum anchor definition.
212
 *
213
 * @return the anchor epoch, or empty.
214
 * @since 9.2
215
 */
216
0
const util::optional<common::Measure> &Datum::anchorEpoch() const {
217
0
    return *(d->anchorEpoch);
218
0
}
219
220
// ---------------------------------------------------------------------------
221
222
/** \brief Return the date on which the datum definition was published.
223
 *
224
 * \note Departure from \ref ISO_19111_2019 : we return a DateTime instead of
225
 * a Citation::Date.
226
 *
227
 * @return the publication date, or empty.
228
 */
229
35.6k
const util::optional<common::DateTime> &Datum::publicationDate() const {
230
35.6k
    return d->publicationDate;
231
35.6k
}
232
233
// ---------------------------------------------------------------------------
234
235
/** \brief Return the conventional reference system.
236
 *
237
 * This is the name, identifier, alias and remarks for the terrestrial
238
 * reference system or vertical reference system realized by this reference
239
 * frame, for example "ITRS" for ITRF88 through ITRF2008 and ITRF2014, or
240
 * "EVRS" for EVRF2000 and EVRF2007.
241
 *
242
 * @return the conventional reference system, or nullptr.
243
 */
244
39
const common::IdentifiedObjectPtr &Datum::conventionalRS() const {
245
39
    return d->conventionalRS;
246
39
}
247
248
// ---------------------------------------------------------------------------
249
250
460k
void Datum::setAnchor(const util::optional<std::string> &anchor) {
251
460k
    d->anchorDefinition = anchor;
252
460k
}
253
254
// ---------------------------------------------------------------------------
255
256
11.4k
void Datum::setAnchorEpoch(const util::optional<common::Measure> &anchorEpoch) {
257
11.4k
    d->anchorEpoch =
258
11.4k
        std::make_shared<util::optional<common::Measure>>(anchorEpoch);
259
11.4k
}
260
261
// ---------------------------------------------------------------------------
262
263
void Datum::setProperties(
264
    const util::PropertyMap &properties) // throw(InvalidValueTypeException)
265
460k
{
266
460k
    std::string publicationDateResult;
267
460k
    properties.getStringValue("PUBLICATION_DATE", publicationDateResult);
268
460k
    if (!publicationDateResult.empty()) {
269
37.6k
        d->publicationDate = common::DateTime::create(publicationDateResult);
270
37.6k
    }
271
460k
    std::string anchorEpoch;
272
460k
    properties.getStringValue("ANCHOR_EPOCH", anchorEpoch);
273
460k
    if (!anchorEpoch.empty()) {
274
9.54k
        bool success = false;
275
9.54k
        const double anchorEpochYear = c_locale_stod(anchorEpoch, success);
276
9.54k
        if (success) {
277
9.54k
            setAnchorEpoch(util::optional<common::Measure>(
278
9.54k
                common::Measure(anchorEpochYear, common::UnitOfMeasure::YEAR)));
279
9.54k
        }
280
9.54k
    }
281
460k
    ObjectUsage::setProperties(properties);
282
460k
}
283
284
// ---------------------------------------------------------------------------
285
286
//! @cond Doxygen_Suppress
287
bool Datum::_isEquivalentTo(const util::IComparable *other,
288
                            util::IComparable::Criterion criterion,
289
1.23M
                            const io::DatabaseContextPtr &dbContext) const {
290
1.23M
    auto otherDatum = dynamic_cast<const Datum *>(other);
291
1.23M
    if (otherDatum == nullptr ||
292
1.23M
        !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) {
293
128k
        return false;
294
128k
    }
295
1.10M
    if (criterion == util::IComparable::Criterion::STRICT) {
296
13
        if ((anchorDefinition().has_value() ^
297
13
             otherDatum->anchorDefinition().has_value())) {
298
0
            return false;
299
0
        }
300
13
        if (anchorDefinition().has_value() &&
301
0
            otherDatum->anchorDefinition().has_value() &&
302
0
            *anchorDefinition() != *otherDatum->anchorDefinition()) {
303
0
            return false;
304
0
        }
305
306
13
        if ((publicationDate().has_value() ^
307
13
             otherDatum->publicationDate().has_value())) {
308
0
            return false;
309
0
        }
310
13
        if (publicationDate().has_value() &&
311
0
            otherDatum->publicationDate().has_value() &&
312
0
            publicationDate()->toString() !=
313
0
                otherDatum->publicationDate()->toString()) {
314
0
            return false;
315
0
        }
316
317
13
        if (((conventionalRS() != nullptr) ^
318
13
             (otherDatum->conventionalRS() != nullptr))) {
319
0
            return false;
320
0
        }
321
13
        if (conventionalRS() && otherDatum->conventionalRS() &&
322
0
            conventionalRS()->_isEquivalentTo(
323
0
                otherDatum->conventionalRS().get(), criterion, dbContext)) {
324
0
            return false;
325
0
        }
326
13
    }
327
1.10M
    return true;
328
1.10M
}
329
//! @endcond
330
331
// ---------------------------------------------------------------------------
332
333
//! @cond Doxygen_Suppress
334
struct PrimeMeridian::Private {
335
    common::Angle longitude_{};
336
337
19.5k
    explicit Private(const common::Angle &longitude) : longitude_(longitude) {}
338
};
339
//! @endcond
340
341
// ---------------------------------------------------------------------------
342
343
PrimeMeridian::PrimeMeridian(const common::Angle &longitudeIn)
344
19.5k
    : d(std::make_unique<Private>(longitudeIn)) {}
345
346
// ---------------------------------------------------------------------------
347
348
#ifdef notdef
349
PrimeMeridian::PrimeMeridian(const PrimeMeridian &other)
350
    : common::IdentifiedObject(other), d(std::make_unique<Private>(*other.d)) {}
351
#endif
352
353
// ---------------------------------------------------------------------------
354
355
//! @cond Doxygen_Suppress
356
19.5k
PrimeMeridian::~PrimeMeridian() = default;
357
//! @endcond
358
359
// ---------------------------------------------------------------------------
360
361
/** \brief Return the longitude of the prime meridian.
362
 *
363
 * It is measured from the internationally-recognised reference meridian
364
 * ('Greenwich meridian'), positive eastward.
365
 * The default value is 0 degrees.
366
 *
367
 * @return the longitude of the prime meridian.
368
 */
369
2.92M
const common::Angle &PrimeMeridian::longitude() PROJ_PURE_DEFN {
370
2.92M
    return d->longitude_;
371
2.92M
}
372
373
// ---------------------------------------------------------------------------
374
375
/** \brief Instantiate a PrimeMeridian.
376
 *
377
 * @param properties See \ref general_properties.
378
 * At minimum the name should be defined.
379
 * @param longitudeIn the longitude of the prime meridian.
380
 * @return new PrimeMeridian.
381
 */
382
PrimeMeridianNNPtr PrimeMeridian::create(const util::PropertyMap &properties,
383
19.5k
                                         const common::Angle &longitudeIn) {
384
19.5k
    auto pm(PrimeMeridian::nn_make_shared<PrimeMeridian>(longitudeIn));
385
19.5k
    pm->setProperties(properties);
386
19.5k
    return pm;
387
19.5k
}
388
389
// ---------------------------------------------------------------------------
390
391
2
const PrimeMeridianNNPtr PrimeMeridian::createGREENWICH() {
392
2
    return create(createMapNameEPSGCode("Greenwich", 8901), common::Angle(0));
393
2
}
394
395
// ---------------------------------------------------------------------------
396
397
2
const PrimeMeridianNNPtr PrimeMeridian::createREFERENCE_MERIDIAN() {
398
2
    return create(util::PropertyMap().set(IdentifiedObject::NAME_KEY,
399
2
                                          "Reference meridian"),
400
2
                  common::Angle(0));
401
2
}
402
403
// ---------------------------------------------------------------------------
404
405
2
const PrimeMeridianNNPtr PrimeMeridian::createPARIS() {
406
2
    return create(createMapNameEPSGCode("Paris", 8903),
407
2
                  common::Angle(2.5969213, common::UnitOfMeasure::GRAD));
408
2
}
409
410
// ---------------------------------------------------------------------------
411
412
//! @cond Doxygen_Suppress
413
void PrimeMeridian::_exportToWKT(
414
    io::WKTFormatter *formatter) const // throw(FormattingException)
415
0
{
416
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
417
0
    std::string l_name(name()->description().has_value() ? nameStr()
418
0
                                                         : "Greenwich");
419
0
    if (!(isWKT2 && formatter->primeMeridianOmittedIfGreenwich() &&
420
0
          l_name == "Greenwich")) {
421
0
        formatter->startNode(io::WKTConstants::PRIMEM, !identifiers().empty());
422
423
0
        if (formatter->useESRIDialect()) {
424
0
            bool aliasFound = false;
425
0
            const auto &dbContext = formatter->databaseContext();
426
0
            if (dbContext) {
427
0
                auto l_alias = dbContext->getAliasFromOfficialName(
428
0
                    l_name, "prime_meridian", "ESRI");
429
0
                if (!l_alias.empty()) {
430
0
                    l_name = std::move(l_alias);
431
0
                    aliasFound = true;
432
0
                }
433
0
            }
434
0
            if (!aliasFound && dbContext) {
435
0
                auto authFactory = io::AuthorityFactory::create(
436
0
                    NN_NO_CHECK(dbContext), "ESRI");
437
0
                aliasFound =
438
0
                    authFactory
439
0
                        ->createObjectsFromName(
440
0
                            l_name,
441
0
                            {io::AuthorityFactory::ObjectType::PRIME_MERIDIAN},
442
0
                            false // approximateMatch
443
0
                            )
444
0
                        .size() == 1;
445
0
            }
446
0
            if (!aliasFound) {
447
0
                l_name = io::WKTFormatter::morphNameToESRI(l_name);
448
0
            }
449
0
        }
450
451
0
        formatter->addQuotedString(l_name);
452
0
        const auto &l_long = longitude();
453
0
        if (formatter->primeMeridianInDegree()) {
454
0
            formatter->add(l_long.convertToUnit(common::UnitOfMeasure::DEGREE));
455
0
        } else {
456
0
            formatter->add(l_long.value());
457
0
        }
458
0
        const auto &unit = l_long.unit();
459
0
        if (isWKT2) {
460
0
            if (!(formatter
461
0
                      ->primeMeridianOrParameterUnitOmittedIfSameAsAxis() &&
462
0
                  unit == *(formatter->axisAngularUnit()))) {
463
0
                unit._exportToWKT(formatter, io::WKTConstants::ANGLEUNIT);
464
0
            }
465
0
        } else if (!formatter->primeMeridianInDegree()) {
466
0
            unit._exportToWKT(formatter);
467
0
        }
468
0
        if (formatter->outputId()) {
469
0
            formatID(formatter);
470
0
        }
471
0
        formatter->endNode();
472
0
    }
473
0
}
474
//! @endcond
475
476
// ---------------------------------------------------------------------------
477
478
//! @cond Doxygen_Suppress
479
void PrimeMeridian::_exportToJSON(
480
    io::JSONFormatter *formatter) const // throw(FormattingException)
481
0
{
482
0
    auto writer = formatter->writer();
483
0
    auto objectContext(
484
0
        formatter->MakeObjectContext("PrimeMeridian", !identifiers().empty()));
485
486
0
    writer->AddObjKey("name");
487
0
    std::string l_name =
488
0
        name()->description().has_value() ? nameStr() : "Greenwich";
489
0
    writer->Add(l_name);
490
491
0
    const auto &l_long = longitude();
492
0
    writer->AddObjKey("longitude");
493
0
    const auto &unit = l_long.unit();
494
0
    if (unit == common::UnitOfMeasure::DEGREE) {
495
0
        writer->Add(l_long.value(), 15);
496
0
    } else {
497
0
        auto longitudeContext(formatter->MakeObjectContext(nullptr, false));
498
0
        writer->AddObjKey("value");
499
0
        writer->Add(l_long.value(), 15);
500
0
        writer->AddObjKey("unit");
501
0
        unit._exportToJSON(formatter);
502
0
    }
503
504
0
    if (formatter->outputId()) {
505
0
        formatID(formatter);
506
0
    }
507
0
}
508
//! @endcond
509
510
// ---------------------------------------------------------------------------
511
512
//! @cond Doxygen_Suppress
513
std::string
514
39.3k
PrimeMeridian::getPROJStringWellKnownName(const common::Angle &angle) {
515
39.3k
    const double valRad = angle.getSIValue();
516
39.3k
    std::string projPMName;
517
39.3k
    PJ_CONTEXT *ctxt = proj_context_create();
518
39.3k
    auto proj_pm = proj_list_prime_meridians();
519
588k
    for (int i = 0; proj_pm[i].id != nullptr; ++i) {
520
549k
        double valRefRad = dmstor_ctx(ctxt, proj_pm[i].defn, nullptr);
521
549k
        if (::fabs(valRad - valRefRad) < 1e-10) {
522
992
            projPMName = proj_pm[i].id;
523
992
            break;
524
992
        }
525
549k
    }
526
39.3k
    proj_context_destroy(ctxt);
527
39.3k
    return projPMName;
528
39.3k
}
529
//! @endcond
530
531
// ---------------------------------------------------------------------------
532
533
//! @cond Doxygen_Suppress
534
void PrimeMeridian::_exportToPROJString(
535
    io::PROJStringFormatter *formatter) const // throw(FormattingException)
536
489k
{
537
489k
    if (longitude().getSIValue() != 0) {
538
28.8k
        std::string projPMName(getPROJStringWellKnownName(longitude()));
539
28.8k
        if (!projPMName.empty()) {
540
698
            formatter->addParam("pm", projPMName);
541
28.1k
        } else {
542
28.1k
            const double valDeg =
543
28.1k
                longitude().convertToUnit(common::UnitOfMeasure::DEGREE);
544
28.1k
            formatter->addParam("pm", valDeg);
545
28.1k
        }
546
28.8k
    }
547
489k
}
548
//! @endcond
549
550
// ---------------------------------------------------------------------------
551
552
//! @cond Doxygen_Suppress
553
bool PrimeMeridian::_isEquivalentTo(
554
    const util::IComparable *other, util::IComparable::Criterion criterion,
555
1.08M
    const io::DatabaseContextPtr &dbContext) const {
556
1.08M
    auto otherPM = dynamic_cast<const PrimeMeridian *>(other);
557
1.08M
    if (otherPM == nullptr ||
558
1.08M
        !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
559
4.07k
        return false;
560
4.07k
    }
561
    // In MapInfo, the Paris prime meridian is returned as 2.3372291666667
562
    // instead of the official value of 2.33722917, which is a relative
563
    // error in the 1e-9 range.
564
1.08M
    return longitude()._isEquivalentTo(otherPM->longitude(), criterion, 1e-8);
565
1.08M
}
566
//! @endcond
567
568
// ---------------------------------------------------------------------------
569
570
//! @cond Doxygen_Suppress
571
struct Ellipsoid::Private {
572
    common::Length semiMajorAxis_{};
573
    util::optional<common::Scale> inverseFlattening_{};
574
    util::optional<common::Length> semiMinorAxis_{};
575
    util::optional<common::Length> semiMedianAxis_{};
576
    std::string celestialBody_{};
577
578
    explicit Private(const common::Length &radius,
579
                     const std::string &celestialBody)
580
1.61k
        : semiMajorAxis_(radius), celestialBody_(celestialBody) {}
581
582
    Private(const common::Length &semiMajorAxisIn,
583
            const common::Scale &invFlattening,
584
            const std::string &celestialBody)
585
12.1k
        : semiMajorAxis_(semiMajorAxisIn), inverseFlattening_(invFlattening),
586
12.1k
          celestialBody_(celestialBody) {}
587
588
    Private(const common::Length &semiMajorAxisIn,
589
            const common::Length &semiMinorAxisIn,
590
            const std::string &celestialBody)
591
957
        : semiMajorAxis_(semiMajorAxisIn), semiMinorAxis_(semiMinorAxisIn),
592
957
          celestialBody_(celestialBody) {}
593
};
594
//! @endcond
595
596
// ---------------------------------------------------------------------------
597
598
Ellipsoid::Ellipsoid(const common::Length &radius,
599
                     const std::string &celestialBodyIn)
600
1.61k
    : d(std::make_unique<Private>(radius, celestialBodyIn)) {}
601
602
// ---------------------------------------------------------------------------
603
604
Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn,
605
                     const common::Scale &invFlattening,
606
                     const std::string &celestialBodyIn)
607
12.1k
    : d(std::make_unique<Private>(semiMajorAxisIn, invFlattening,
608
12.1k
                                  celestialBodyIn)) {}
609
610
// ---------------------------------------------------------------------------
611
612
Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn,
613
                     const common::Length &semiMinorAxisIn,
614
                     const std::string &celestialBodyIn)
615
957
    : d(std::make_unique<Private>(semiMajorAxisIn, semiMinorAxisIn,
616
957
                                  celestialBodyIn)) {}
617
618
// ---------------------------------------------------------------------------
619
620
#ifdef notdef
621
Ellipsoid::Ellipsoid(const Ellipsoid &other)
622
    : common::IdentifiedObject(other), d(std::make_unique<Private>(*other.d)) {}
623
#endif
624
625
// ---------------------------------------------------------------------------
626
627
//! @cond Doxygen_Suppress
628
15.2k
Ellipsoid::~Ellipsoid() = default;
629
630
Ellipsoid::Ellipsoid(const Ellipsoid &other)
631
578
    : IdentifiedObject(other), d(std::make_unique<Private>(*(other.d))) {}
632
633
//! @endcond
634
635
// ---------------------------------------------------------------------------
636
637
/** \brief Return the length of the semi-major axis of the ellipsoid.
638
 *
639
 * @return the semi-major axis.
640
 */
641
3.88M
const common::Length &Ellipsoid::semiMajorAxis() PROJ_PURE_DEFN {
642
3.88M
    return d->semiMajorAxis_;
643
3.88M
}
644
645
// ---------------------------------------------------------------------------
646
647
/** \brief Return the inverse flattening value of the ellipsoid, if the
648
 * ellipsoid
649
 * has been defined with this value.
650
 *
651
 * @see computeInverseFlattening() that will always return a valid value of the
652
 * inverse flattening, whether the ellipsoid has been defined through inverse
653
 * flattening or semi-minor axis.
654
 *
655
 * @return the inverse flattening value of the ellipsoid, or empty.
656
 */
657
const util::optional<common::Scale> &
658
4.70M
Ellipsoid::inverseFlattening() PROJ_PURE_DEFN {
659
4.70M
    return d->inverseFlattening_;
660
4.70M
}
661
662
// ---------------------------------------------------------------------------
663
664
/** \brief Return the length of the semi-minor axis of the ellipsoid, if the
665
 * ellipsoid
666
 * has been defined with this value.
667
 *
668
 * @see computeSemiMinorAxis() that will always return a valid value of the
669
 * semi-minor axis, whether the ellipsoid has been defined through inverse
670
 * flattening or semi-minor axis.
671
 *
672
 * @return the semi-minor axis of the ellipsoid, or empty.
673
 */
674
const util::optional<common::Length> &
675
2.24M
Ellipsoid::semiMinorAxis() PROJ_PURE_DEFN {
676
2.24M
    return d->semiMinorAxis_;
677
2.24M
}
678
679
// ---------------------------------------------------------------------------
680
681
/** \brief Return whether the ellipsoid is spherical.
682
 *
683
 * That is to say is semiMajorAxis() == computeSemiMinorAxis().
684
 *
685
 * A sphere is completely defined by the semi-major axis, which is the radius
686
 * of the sphere.
687
 *
688
 * @return true if the ellipsoid is spherical.
689
 */
690
20.8k
bool Ellipsoid::isSphere() PROJ_PURE_DEFN {
691
20.8k
    if (d->inverseFlattening_.has_value()) {
692
16.3k
        return d->inverseFlattening_->value() == 0;
693
16.3k
    }
694
695
4.55k
    if (semiMinorAxis().has_value()) {
696
3.08k
        return semiMajorAxis() == *semiMinorAxis();
697
3.08k
    }
698
699
1.46k
    return true;
700
4.55k
}
701
702
// ---------------------------------------------------------------------------
703
704
/** \brief Return the length of the semi-median axis of a triaxial ellipsoid
705
 *
706
 * This parameter is not required for a biaxial ellipsoid.
707
 *
708
 * @return the semi-median axis of the ellipsoid, or empty.
709
 */
710
const util::optional<common::Length> &
711
2.22M
Ellipsoid::semiMedianAxis() PROJ_PURE_DEFN {
712
2.22M
    return d->semiMedianAxis_;
713
2.22M
}
714
715
// ---------------------------------------------------------------------------
716
717
/** \brief Return or compute the inverse flattening value of the ellipsoid.
718
 *
719
 * If computed, the inverse flattening is the result of a / (a - b),
720
 * where a is the semi-major axis and b the semi-minor axis.
721
 *
722
 * @return the inverse flattening value of the ellipsoid, or 0 for a sphere.
723
 */
724
857k
double Ellipsoid::computedInverseFlattening() PROJ_PURE_DEFN {
725
857k
    if (d->inverseFlattening_.has_value()) {
726
572k
        return d->inverseFlattening_->getSIValue();
727
572k
    }
728
729
285k
    if (d->semiMinorAxis_.has_value()) {
730
283k
        const double a = d->semiMajorAxis_.getSIValue();
731
283k
        const double b = d->semiMinorAxis_->getSIValue();
732
283k
        return (a == b) ? 0.0 : a / (a - b);
733
283k
    }
734
735
2.04k
    return 0.0;
736
285k
}
737
738
// ---------------------------------------------------------------------------
739
740
/** \brief Return the squared eccentricity of the ellipsoid.
741
 *
742
 * @return the squared eccentricity, or a negative value if invalid.
743
 */
744
54.0k
double Ellipsoid::squaredEccentricity() PROJ_PURE_DEFN {
745
54.0k
    const double rf = computedInverseFlattening();
746
    // coverity[divide_by_zero]
747
54.0k
    const double f = rf != 0.0 ? 1. / rf : 0.0;
748
54.0k
    const double e2 = f * (2 - f);
749
54.0k
    return e2;
750
54.0k
}
751
752
// ---------------------------------------------------------------------------
753
754
/** \brief Return or compute the length of the semi-minor axis of the ellipsoid.
755
 *
756
 * If computed, the semi-minor axis is the result of a * (1 - 1 / rf)
757
 * where a is the semi-major axis and rf the reverse/inverse flattening.
758
759
 * @return the semi-minor axis of the ellipsoid.
760
 */
761
2.95M
common::Length Ellipsoid::computeSemiMinorAxis() const {
762
2.95M
    if (d->semiMinorAxis_.has_value()) {
763
503k
        return *d->semiMinorAxis_;
764
503k
    }
765
766
2.44M
    if (inverseFlattening().has_value()) {
767
2.44M
        return common::Length(
768
2.44M
            (1.0 - 1.0 / d->inverseFlattening_->getSIValue()) *
769
2.44M
                d->semiMajorAxis_.value(),
770
2.44M
            d->semiMajorAxis_.unit());
771
2.44M
    }
772
773
8.88k
    return d->semiMajorAxis_;
774
2.44M
}
775
776
// ---------------------------------------------------------------------------
777
778
/** \brief Return the name of the celestial body on which the ellipsoid refers
779
 * to.
780
 */
781
373k
const std::string &Ellipsoid::celestialBody() PROJ_PURE_DEFN {
782
373k
    return d->celestialBody_;
783
373k
}
784
785
// ---------------------------------------------------------------------------
786
787
/** \brief Instantiate a Ellipsoid as a sphere.
788
 *
789
 * @param properties See \ref general_properties.
790
 * At minimum the name should be defined.
791
 * @param radius the sphere radius (semi-major axis).
792
 * @param celestialBody Name of the celestial body on which the ellipsoid refers
793
 * to.
794
 * @return new Ellipsoid.
795
 */
796
EllipsoidNNPtr Ellipsoid::createSphere(const util::PropertyMap &properties,
797
                                       const common::Length &radius,
798
1.34k
                                       const std::string &celestialBody) {
799
1.34k
    auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(radius, celestialBody));
800
1.34k
    ellipsoid->setProperties(properties);
801
1.34k
    return ellipsoid;
802
1.34k
}
803
804
// ---------------------------------------------------------------------------
805
806
/** \brief Instantiate a Ellipsoid from its inverse/reverse flattening.
807
 *
808
 * @param properties See \ref general_properties.
809
 * At minimum the name should be defined.
810
 * @param semiMajorAxisIn the semi-major axis.
811
 * @param invFlattening the inverse/reverse flattening. If set to 0, this will
812
 * be considered as a sphere.
813
 * @param celestialBody Name of the celestial body on which the ellipsoid refers
814
 * to.
815
 * @return new Ellipsoid.
816
 */
817
EllipsoidNNPtr Ellipsoid::createFlattenedSphere(
818
    const util::PropertyMap &properties, const common::Length &semiMajorAxisIn,
819
12.4k
    const common::Scale &invFlattening, const std::string &celestialBody) {
820
12.4k
    if (invFlattening.value() == 0) {
821
269
        auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(semiMajorAxisIn,
822
269
                                                            celestialBody));
823
269
        ellipsoid->setProperties(properties);
824
269
        return ellipsoid;
825
12.1k
    } else {
826
12.1k
        auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(
827
12.1k
            semiMajorAxisIn, invFlattening, celestialBody));
828
12.1k
        ellipsoid->setProperties(properties);
829
12.1k
        return ellipsoid;
830
12.1k
    }
831
12.4k
}
832
833
// ---------------------------------------------------------------------------
834
835
/** \brief Instantiate a Ellipsoid from the value of its two semi axis.
836
 *
837
 * @param properties See \ref general_properties.
838
 * At minimum the name should be defined.
839
 * @param semiMajorAxisIn the semi-major axis.
840
 * @param semiMinorAxisIn the semi-minor axis.
841
 * @param celestialBody Name of the celestial body on which the ellipsoid refers
842
 * to.
843
 * @return new Ellipsoid.
844
 */
845
EllipsoidNNPtr Ellipsoid::createTwoAxis(const util::PropertyMap &properties,
846
                                        const common::Length &semiMajorAxisIn,
847
                                        const common::Length &semiMinorAxisIn,
848
957
                                        const std::string &celestialBody) {
849
957
    auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(
850
957
        semiMajorAxisIn, semiMinorAxisIn, celestialBody));
851
957
    ellipsoid->setProperties(properties);
852
957
    return ellipsoid;
853
957
}
854
855
// ---------------------------------------------------------------------------
856
857
2
const EllipsoidNNPtr Ellipsoid::createCLARKE_1866() {
858
2
    return createTwoAxis(createMapNameEPSGCode("Clarke 1866", 7008),
859
2
                         common::Length(6378206.4), common::Length(6356583.8));
860
2
}
861
862
// ---------------------------------------------------------------------------
863
864
2
const EllipsoidNNPtr Ellipsoid::createWGS84() {
865
2
    return createFlattenedSphere(createMapNameEPSGCode("WGS 84", 7030),
866
2
                                 common::Length(6378137),
867
2
                                 common::Scale(298.257223563));
868
2
}
869
870
// ---------------------------------------------------------------------------
871
872
2
const EllipsoidNNPtr Ellipsoid::createGRS1980() {
873
2
    return createFlattenedSphere(createMapNameEPSGCode("GRS 1980", 7019),
874
2
                                 common::Length(6378137),
875
2
                                 common::Scale(298.257222101));
876
2
}
877
878
// ---------------------------------------------------------------------------
879
880
//! @cond Doxygen_Suppress
881
void Ellipsoid::_exportToWKT(
882
    io::WKTFormatter *formatter) const // throw(FormattingException)
883
0
{
884
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
885
0
    formatter->startNode(isWKT2 ? io::WKTConstants::ELLIPSOID
886
0
                                : io::WKTConstants::SPHEROID,
887
0
                         !identifiers().empty());
888
0
    {
889
0
        std::string l_name(nameStr());
890
0
        if (l_name.empty()) {
891
0
            formatter->addQuotedString("unnamed");
892
0
        } else {
893
0
            if (formatter->useESRIDialect()) {
894
0
                if (l_name == "WGS 84") {
895
0
                    l_name = "WGS_1984";
896
0
                } else {
897
0
                    bool aliasFound = false;
898
0
                    const auto &dbContext = formatter->databaseContext();
899
0
                    if (dbContext) {
900
0
                        auto l_alias = dbContext->getAliasFromOfficialName(
901
0
                            l_name, "ellipsoid", "ESRI");
902
0
                        if (!l_alias.empty()) {
903
0
                            l_name = std::move(l_alias);
904
0
                            aliasFound = true;
905
0
                        }
906
0
                    }
907
0
                    if (!aliasFound && dbContext) {
908
0
                        auto authFactory = io::AuthorityFactory::create(
909
0
                            NN_NO_CHECK(dbContext), "ESRI");
910
0
                        aliasFound = authFactory
911
0
                                         ->createObjectsFromName(
912
0
                                             l_name,
913
0
                                             {io::AuthorityFactory::ObjectType::
914
0
                                                  ELLIPSOID},
915
0
                                             false // approximateMatch
916
0
                                             )
917
0
                                         .size() == 1;
918
0
                    }
919
0
                    if (!aliasFound) {
920
0
                        l_name = io::WKTFormatter::morphNameToESRI(l_name);
921
0
                    }
922
0
                }
923
0
            }
924
0
            formatter->addQuotedString(l_name);
925
0
        }
926
0
        const auto &semiMajor = semiMajorAxis();
927
0
        if (isWKT2) {
928
0
            formatter->add(semiMajor.value());
929
0
        } else {
930
0
            formatter->add(semiMajor.getSIValue());
931
0
        }
932
0
        formatter->add(computedInverseFlattening());
933
0
        const auto &unit = semiMajor.unit();
934
0
        if (isWKT2 && !(formatter->ellipsoidUnitOmittedIfMetre() &&
935
0
                        unit == common::UnitOfMeasure::METRE)) {
936
0
            unit._exportToWKT(formatter, io::WKTConstants::LENGTHUNIT);
937
0
        }
938
0
        if (formatter->outputId()) {
939
0
            formatID(formatter);
940
0
        }
941
0
    }
942
0
    formatter->endNode();
943
0
}
944
//! @endcond
945
946
// ---------------------------------------------------------------------------
947
948
//! @cond Doxygen_Suppress
949
void Ellipsoid::_exportToJSON(
950
    io::JSONFormatter *formatter) const // throw(FormattingException)
951
0
{
952
0
    auto writer = formatter->writer();
953
0
    auto objectContext(
954
0
        formatter->MakeObjectContext("Ellipsoid", !identifiers().empty()));
955
956
0
    writer->AddObjKey("name");
957
0
    const auto &l_name = nameStr();
958
0
    if (l_name.empty()) {
959
0
        writer->Add("unnamed");
960
0
    } else {
961
0
        writer->Add(l_name);
962
0
    }
963
964
0
    const auto &semiMajor = semiMajorAxis();
965
0
    const auto &semiMajorUnit = semiMajor.unit();
966
0
    writer->AddObjKey(isSphere() ? "radius" : "semi_major_axis");
967
0
    if (semiMajorUnit == common::UnitOfMeasure::METRE) {
968
0
        writer->Add(semiMajor.value(), 15);
969
0
    } else {
970
0
        auto objContext(formatter->MakeObjectContext(nullptr, false));
971
0
        writer->AddObjKey("value");
972
0
        writer->Add(semiMajor.value(), 15);
973
974
0
        writer->AddObjKey("unit");
975
0
        semiMajorUnit._exportToJSON(formatter);
976
0
    }
977
978
0
    if (!isSphere()) {
979
0
        const auto &l_inverseFlattening = inverseFlattening();
980
0
        if (l_inverseFlattening.has_value()) {
981
0
            writer->AddObjKey("inverse_flattening");
982
0
            writer->Add(l_inverseFlattening->getSIValue(), 15);
983
0
        } else {
984
0
            writer->AddObjKey("semi_minor_axis");
985
0
            const auto &l_semiMinorAxis(semiMinorAxis());
986
0
            const auto &semiMinorAxisUnit(l_semiMinorAxis->unit());
987
0
            if (semiMinorAxisUnit == common::UnitOfMeasure::METRE) {
988
0
                writer->Add(l_semiMinorAxis->value(), 15);
989
0
            } else {
990
0
                auto objContext(formatter->MakeObjectContext(nullptr, false));
991
0
                writer->AddObjKey("value");
992
0
                writer->Add(l_semiMinorAxis->value(), 15);
993
994
0
                writer->AddObjKey("unit");
995
0
                semiMinorAxisUnit._exportToJSON(formatter);
996
0
            }
997
0
        }
998
0
    }
999
1000
0
    if (formatter->outputId()) {
1001
0
        formatID(formatter);
1002
0
    }
1003
0
}
1004
//! @endcond
1005
1006
// ---------------------------------------------------------------------------
1007
1008
bool Ellipsoid::lookForProjWellKnownEllps(std::string &projEllpsName,
1009
784k
                                          std::string &ellpsName) const {
1010
784k
    const double a = semiMajorAxis().getSIValue();
1011
784k
    const double b = computeSemiMinorAxis().getSIValue();
1012
784k
    const double rf = computedInverseFlattening();
1013
784k
    auto proj_ellps = proj_list_ellps();
1014
22.1M
    for (int i = 0; proj_ellps[i].id != nullptr; i++) {
1015
22.1M
        assert(strncmp(proj_ellps[i].major, "a=", 2) == 0);
1016
22.1M
        const double a_iter = c_locale_stod(proj_ellps[i].major + 2);
1017
22.1M
        if (::fabs(a - a_iter) < 1e-10 * a_iter) {
1018
1.94M
            if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) {
1019
273k
                const double b_iter = c_locale_stod(proj_ellps[i].ell + 2);
1020
273k
                if (::fabs(b - b_iter) < 1e-10 * b_iter) {
1021
266k
                    projEllpsName = proj_ellps[i].id;
1022
266k
                    ellpsName = proj_ellps[i].name;
1023
266k
                    if (starts_with(ellpsName, "GRS 1980")) {
1024
0
                        ellpsName = "GRS 1980";
1025
0
                    }
1026
266k
                    return true;
1027
266k
                }
1028
1.67M
            } else {
1029
1.67M
                assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0);
1030
1.67M
                const double rf_iter = c_locale_stod(proj_ellps[i].ell + 3);
1031
1.67M
                if (::fabs(rf - rf_iter) < 1e-10 * rf_iter) {
1032
499k
                    projEllpsName = proj_ellps[i].id;
1033
499k
                    ellpsName = proj_ellps[i].name;
1034
499k
                    if (starts_with(ellpsName, "GRS 1980")) {
1035
84.0k
                        ellpsName = "GRS 1980";
1036
84.0k
                    }
1037
499k
                    return true;
1038
499k
                }
1039
1.67M
            }
1040
1.94M
        }
1041
22.1M
    }
1042
18.9k
    return false;
1043
784k
}
1044
1045
// ---------------------------------------------------------------------------
1046
1047
//! @cond Doxygen_Suppress
1048
void Ellipsoid::_exportToPROJString(
1049
    io::PROJStringFormatter *formatter) const // throw(FormattingException)
1050
784k
{
1051
784k
    const double a = semiMajorAxis().getSIValue();
1052
1053
784k
    std::string projEllpsName;
1054
784k
    std::string ellpsName;
1055
784k
    if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) {
1056
765k
        formatter->addParam("ellps", projEllpsName);
1057
765k
        return;
1058
765k
    }
1059
1060
18.5k
    if (isSphere()) {
1061
1.40k
        formatter->addParam("R", a);
1062
17.1k
    } else {
1063
17.1k
        formatter->addParam("a", a);
1064
17.1k
        if (inverseFlattening().has_value()) {
1065
14.9k
            const double rf = computedInverseFlattening();
1066
14.9k
            formatter->addParam("rf", rf);
1067
14.9k
        } else {
1068
2.15k
            const double b = computeSemiMinorAxis().getSIValue();
1069
2.15k
            formatter->addParam("b", b);
1070
2.15k
        }
1071
17.1k
    }
1072
18.5k
}
1073
//! @endcond
1074
1075
// ---------------------------------------------------------------------------
1076
1077
/** \brief Return a Ellipsoid object where some parameters are better
1078
 * identified.
1079
 *
1080
 * @return a new Ellipsoid.
1081
 */
1082
578
EllipsoidNNPtr Ellipsoid::identify() const {
1083
578
    auto newEllipsoid = Ellipsoid::nn_make_shared<Ellipsoid>(*this);
1084
578
    newEllipsoid->assignSelf(
1085
578
        util::nn_static_pointer_cast<util::BaseObject>(newEllipsoid));
1086
1087
578
    if (name()->description()->empty() || nameStr() == "unknown") {
1088
578
        std::string projEllpsName;
1089
578
        std::string ellpsName;
1090
578
        if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) {
1091
115
            newEllipsoid->setProperties(
1092
115
                util::PropertyMap().set(IdentifiedObject::NAME_KEY, ellpsName));
1093
115
        }
1094
578
    }
1095
1096
578
    return newEllipsoid;
1097
578
}
1098
1099
// ---------------------------------------------------------------------------
1100
1101
//! @cond Doxygen_Suppress
1102
bool Ellipsoid::_isEquivalentTo(const util::IComparable *other,
1103
                                util::IComparable::Criterion criterion,
1104
1.12M
                                const io::DatabaseContextPtr &dbContext) const {
1105
1.12M
    auto otherEllipsoid = dynamic_cast<const Ellipsoid *>(other);
1106
1.12M
    if (otherEllipsoid == nullptr ||
1107
1.12M
        (criterion == util::IComparable::Criterion::STRICT &&
1108
30.6k
         !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext))) {
1109
3
        return false;
1110
3
    }
1111
1112
    // PROJ "clrk80" name is "Clarke 1880 mod." and GDAL tends to
1113
    // export to it a number of Clarke 1880 variants, so be lax
1114
1.12M
    if (criterion != util::IComparable::Criterion::STRICT &&
1115
1.09M
        (nameStr() == "Clarke 1880 mod." ||
1116
1.09M
         otherEllipsoid->nameStr() == "Clarke 1880 mod.")) {
1117
523
        return std::fabs(semiMajorAxis().getSIValue() -
1118
523
                         otherEllipsoid->semiMajorAxis().getSIValue()) <
1119
523
                   1e-8 * semiMajorAxis().getSIValue() &&
1120
514
               std::fabs(computedInverseFlattening() -
1121
514
                         otherEllipsoid->computedInverseFlattening()) <
1122
514
                   1e-5 * computedInverseFlattening();
1123
523
    }
1124
1125
1.12M
    if (!semiMajorAxis()._isEquivalentTo(otherEllipsoid->semiMajorAxis(),
1126
1.12M
                                         criterion)) {
1127
8.03k
        return false;
1128
8.03k
    }
1129
1130
1.11M
    const auto &l_semiMinorAxis = semiMinorAxis();
1131
1.11M
    const auto &l_other_semiMinorAxis = otherEllipsoid->semiMinorAxis();
1132
1.11M
    if (l_semiMinorAxis.has_value() && l_other_semiMinorAxis.has_value()) {
1133
115k
        if (!l_semiMinorAxis->_isEquivalentTo(*l_other_semiMinorAxis,
1134
115k
                                              criterion)) {
1135
16
            return false;
1136
16
        }
1137
115k
    }
1138
1139
1.11M
    const auto &l_inverseFlattening = inverseFlattening();
1140
1.11M
    const auto &l_other_sinverseFlattening =
1141
1.11M
        otherEllipsoid->inverseFlattening();
1142
1.11M
    if (l_inverseFlattening.has_value() &&
1143
999k
        l_other_sinverseFlattening.has_value()) {
1144
999k
        if (!l_inverseFlattening->_isEquivalentTo(*l_other_sinverseFlattening,
1145
999k
                                                  criterion)) {
1146
5.72k
            return false;
1147
5.72k
        }
1148
999k
    }
1149
1150
1.11M
    if (criterion == util::IComparable::Criterion::STRICT) {
1151
30.6k
        if ((l_semiMinorAxis.has_value() ^ l_other_semiMinorAxis.has_value())) {
1152
0
            return false;
1153
0
        }
1154
1155
30.6k
        if ((l_inverseFlattening.has_value() ^
1156
30.6k
             l_other_sinverseFlattening.has_value())) {
1157
0
            return false;
1158
0
        }
1159
1160
1.08M
    } else {
1161
1.08M
        if (!computeSemiMinorAxis()._isEquivalentTo(
1162
1.08M
                otherEllipsoid->computeSemiMinorAxis(), criterion)) {
1163
310
            return false;
1164
310
        }
1165
1.08M
    }
1166
1167
1.11M
    const auto &l_semiMedianAxis = semiMedianAxis();
1168
1.11M
    const auto &l_other_semiMedianAxis = otherEllipsoid->semiMedianAxis();
1169
1.11M
    if ((l_semiMedianAxis.has_value() ^ l_other_semiMedianAxis.has_value())) {
1170
0
        return false;
1171
0
    }
1172
1.11M
    if (l_semiMedianAxis.has_value() && l_other_semiMedianAxis.has_value()) {
1173
0
        if (!l_semiMedianAxis->_isEquivalentTo(*l_other_semiMedianAxis,
1174
0
                                               criterion)) {
1175
0
            return false;
1176
0
        }
1177
0
    }
1178
1.11M
    return true;
1179
1.11M
}
1180
//! @endcond
1181
1182
// ---------------------------------------------------------------------------
1183
1184
std::string Ellipsoid::guessBodyName(const io::DatabaseContextPtr &dbContext,
1185
5.28k
                                     double a, const std::string &ellpsName) {
1186
5.28k
    constexpr double earthMeanRadius = 6375000.0;
1187
5.28k
    if (std::fabs(a - earthMeanRadius) <
1188
5.28k
        REL_ERROR_FOR_SAME_CELESTIAL_BODY * earthMeanRadius) {
1189
3.26k
        return Ellipsoid::EARTH;
1190
3.26k
    }
1191
2.01k
    if (dbContext) {
1192
1.60k
        try {
1193
1.60k
            auto factory = io::AuthorityFactory::create(NN_NO_CHECK(dbContext),
1194
1.60k
                                                        std::string());
1195
1.60k
            if (!ellpsName.empty()) {
1196
1.01k
                auto matches = factory->createObjectsFromName(
1197
1.01k
                    ellpsName, {io::AuthorityFactory::ObjectType::ELLIPSOID},
1198
1.01k
                    true, 1);
1199
1.01k
                if (!matches.empty()) {
1200
120
                    auto ellps =
1201
120
                        static_cast<const Ellipsoid *>(matches.front().get());
1202
120
                    if (std::fabs(a - ellps->semiMajorAxis().getSIValue()) <
1203
120
                        REL_ERROR_FOR_SAME_CELESTIAL_BODY * a) {
1204
29
                        return ellps->celestialBody();
1205
29
                    }
1206
120
                }
1207
1.01k
            }
1208
1.57k
            return factory->identifyBodyFromSemiMajorAxis(
1209
1.57k
                a, REL_ERROR_FOR_SAME_CELESTIAL_BODY);
1210
1.60k
        } catch (const std::exception &) {
1211
1.49k
        }
1212
1.60k
    }
1213
1.90k
    return NON_EARTH_BODY;
1214
2.01k
}
1215
1216
// ---------------------------------------------------------------------------
1217
1218
//! @cond Doxygen_Suppress
1219
struct GeodeticReferenceFrame::Private {
1220
    PrimeMeridianNNPtr primeMeridian_;
1221
    EllipsoidNNPtr ellipsoid_;
1222
1223
    Private(const EllipsoidNNPtr &ellipsoidIn,
1224
            const PrimeMeridianNNPtr &primeMeridianIn)
1225
451k
        : primeMeridian_(primeMeridianIn), ellipsoid_(ellipsoidIn) {}
1226
};
1227
//! @endcond
1228
1229
// ---------------------------------------------------------------------------
1230
1231
GeodeticReferenceFrame::GeodeticReferenceFrame(
1232
    const EllipsoidNNPtr &ellipsoidIn,
1233
    const PrimeMeridianNNPtr &primeMeridianIn)
1234
451k
    : d(std::make_unique<Private>(ellipsoidIn, primeMeridianIn)) {}
1235
1236
// ---------------------------------------------------------------------------
1237
1238
#ifdef notdef
1239
GeodeticReferenceFrame::GeodeticReferenceFrame(
1240
    const GeodeticReferenceFrame &other)
1241
    : Datum(other), d(std::make_unique<Private>(*other.d)) {}
1242
#endif
1243
1244
// ---------------------------------------------------------------------------
1245
1246
//! @cond Doxygen_Suppress
1247
451k
GeodeticReferenceFrame::~GeodeticReferenceFrame() = default;
1248
//! @endcond
1249
1250
// ---------------------------------------------------------------------------
1251
1252
/** \brief Return the PrimeMeridian associated with a GeodeticReferenceFrame.
1253
 *
1254
 * @return the PrimeMeridian.
1255
 */
1256
const PrimeMeridianNNPtr &
1257
3.22M
GeodeticReferenceFrame::primeMeridian() PROJ_PURE_DEFN {
1258
3.22M
    return d->primeMeridian_;
1259
3.22M
}
1260
1261
// ---------------------------------------------------------------------------
1262
1263
/** \brief Return the Ellipsoid associated with a GeodeticReferenceFrame.
1264
 *
1265
 * \note The \ref ISO_19111_2019 modelling allows (but discourages) a
1266
 * GeodeticReferenceFrame
1267
 * to not be associated with a Ellipsoid in the case where it is used by a
1268
 * geocentric crs::GeodeticCRS. We have made the choice of making the ellipsoid
1269
 * specification compulsory.
1270
 *
1271
 * @return the Ellipsoid.
1272
 */
1273
3.74M
const EllipsoidNNPtr &GeodeticReferenceFrame::ellipsoid() PROJ_PURE_DEFN {
1274
3.74M
    return d->ellipsoid_;
1275
3.74M
}
1276
1277
// ---------------------------------------------------------------------------
1278
1279
/** \brief Instantiate a GeodeticReferenceFrame
1280
 *
1281
 * @param properties See \ref general_properties.
1282
 * At minimum the name should be defined.
1283
 * @param ellipsoid the Ellipsoid.
1284
 * @param anchor the anchor definition, or empty.
1285
 * @param primeMeridian the PrimeMeridian.
1286
 * @return new GeodeticReferenceFrame.
1287
 */
1288
GeodeticReferenceFrameNNPtr
1289
GeodeticReferenceFrame::create(const util::PropertyMap &properties,
1290
                               const EllipsoidNNPtr &ellipsoid,
1291
                               const util::optional<std::string> &anchor,
1292
424k
                               const PrimeMeridianNNPtr &primeMeridian) {
1293
424k
    GeodeticReferenceFrameNNPtr grf(
1294
424k
        GeodeticReferenceFrame::nn_make_shared<GeodeticReferenceFrame>(
1295
424k
            ellipsoid, primeMeridian));
1296
424k
    grf->setAnchor(anchor);
1297
424k
    grf->setProperties(properties);
1298
424k
    return grf;
1299
424k
}
1300
1301
// ---------------------------------------------------------------------------
1302
1303
/** \brief Instantiate a GeodeticReferenceFrame
1304
 *
1305
 * @param properties See \ref general_properties.
1306
 * At minimum the name should be defined.
1307
 * @param ellipsoid the Ellipsoid.
1308
 * @param anchor the anchor definition, or empty.
1309
 * @param anchorEpoch the anchor epoch, or empty.
1310
 * @param primeMeridian the PrimeMeridian.
1311
 * @return new GeodeticReferenceFrame.
1312
 * @since 9.2
1313
 */
1314
GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::create(
1315
    const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid,
1316
    const util::optional<std::string> &anchor,
1317
    const util::optional<common::Measure> &anchorEpoch,
1318
1.10k
    const PrimeMeridianNNPtr &primeMeridian) {
1319
1.10k
    GeodeticReferenceFrameNNPtr grf(
1320
1.10k
        GeodeticReferenceFrame::nn_make_shared<GeodeticReferenceFrame>(
1321
1.10k
            ellipsoid, primeMeridian));
1322
1.10k
    grf->setAnchor(anchor);
1323
1.10k
    grf->setAnchorEpoch(anchorEpoch);
1324
1.10k
    grf->setProperties(properties);
1325
1.10k
    return grf;
1326
1.10k
}
1327
1328
// ---------------------------------------------------------------------------
1329
1330
2
const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6267() {
1331
2
    return create(createMapNameEPSGCode("North American Datum 1927", 6267),
1332
2
                  Ellipsoid::CLARKE_1866, util::optional<std::string>(),
1333
2
                  PrimeMeridian::GREENWICH);
1334
2
}
1335
1336
// ---------------------------------------------------------------------------
1337
1338
2
const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6269() {
1339
2
    return create(createMapNameEPSGCode("North American Datum 1983", 6269),
1340
2
                  Ellipsoid::GRS1980, util::optional<std::string>(),
1341
2
                  PrimeMeridian::GREENWICH);
1342
2
}
1343
1344
// ---------------------------------------------------------------------------
1345
1346
2
const GeodeticReferenceFrameNNPtr GeodeticReferenceFrame::createEPSG_6326() {
1347
2
    return create(createMapNameEPSGCode("World Geodetic System 1984", 6326),
1348
2
                  Ellipsoid::WGS84, util::optional<std::string>(),
1349
2
                  PrimeMeridian::GREENWICH);
1350
2
}
1351
1352
// ---------------------------------------------------------------------------
1353
1354
//! @cond Doxygen_Suppress
1355
void GeodeticReferenceFrame::_exportToWKT(
1356
    io::WKTFormatter *formatter) const // throw(FormattingException)
1357
0
{
1358
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
1359
0
    const auto &ids = identifiers();
1360
0
    formatter->startNode(io::WKTConstants::DATUM, !ids.empty());
1361
0
    std::string l_name(nameStr());
1362
0
    if (l_name.empty()) {
1363
0
        l_name = "unnamed";
1364
0
    }
1365
0
    if (!isWKT2) {
1366
0
        if (formatter->useESRIDialect()) {
1367
0
            if (l_name == "World Geodetic System 1984") {
1368
0
                l_name = "D_WGS_1984";
1369
0
            } else {
1370
0
                bool aliasFound = false;
1371
0
                const auto &dbContext = formatter->databaseContext();
1372
0
                if (dbContext) {
1373
0
                    auto l_alias = dbContext->getAliasFromOfficialName(
1374
0
                        l_name, "geodetic_datum", "ESRI");
1375
0
                    size_t pos;
1376
0
                    if (!l_alias.empty()) {
1377
0
                        l_name = std::move(l_alias);
1378
0
                        aliasFound = true;
1379
0
                    } else if ((pos = l_name.find(" (")) != std::string::npos) {
1380
0
                        l_alias = dbContext->getAliasFromOfficialName(
1381
0
                            l_name.substr(0, pos), "geodetic_datum", "ESRI");
1382
0
                        if (!l_alias.empty()) {
1383
0
                            l_name = std::move(l_alias);
1384
0
                            aliasFound = true;
1385
0
                        }
1386
0
                    }
1387
0
                }
1388
0
                if (!aliasFound && dbContext) {
1389
0
                    auto authFactory = io::AuthorityFactory::create(
1390
0
                        NN_NO_CHECK(dbContext), "ESRI");
1391
0
                    aliasFound = authFactory
1392
0
                                     ->createObjectsFromName(
1393
0
                                         l_name,
1394
0
                                         {io::AuthorityFactory::ObjectType::
1395
0
                                              GEODETIC_REFERENCE_FRAME},
1396
0
                                         false // approximateMatch
1397
0
                                         )
1398
0
                                     .size() == 1;
1399
0
                }
1400
0
                if (!aliasFound && dbContext && !ids.empty()) {
1401
                    // Case for example for ETRS89-NOR [EUREF89] that has no
1402
                    // ESRI alias. Fallback to ETRS89
1403
0
                    const auto EPSGOldAliases = dbContext->getAliases(
1404
0
                        *(ids[0]->codeSpace()), ids[0]->code(),
1405
0
                        std::string(), // officialName,
1406
0
                        "geodetic_datum", "EPSG_OLD");
1407
0
                    if (EPSGOldAliases.size() == 1) {
1408
0
                        std::string EPSGName = EPSGOldAliases.front();
1409
0
                        if (EPSGName ==
1410
0
                            "European Terrestrial Reference System 1989") {
1411
0
                            EPSGName += " ensemble";
1412
0
                        }
1413
0
                        auto authFactoryEPSG = io::AuthorityFactory::create(
1414
0
                            NN_NO_CHECK(dbContext), "EPSG");
1415
0
                        auto objCandidates =
1416
0
                            authFactoryEPSG->createObjectsFromNameEx(
1417
0
                                EPSGName,
1418
0
                                {io::AuthorityFactory::ObjectType::
1419
0
                                     GEODETIC_REFERENCE_FRAME},
1420
0
                                false, // approximateMatch
1421
0
                                0,     // limitResultCount
1422
0
                                false  // useAliases
1423
0
                            );
1424
0
                        for (const auto &[obj, name] : objCandidates) {
1425
0
                            (void)name;
1426
0
                            const auto &objIdentifiers = obj->identifiers();
1427
0
                            if (!objIdentifiers.empty()) {
1428
0
                                const auto ESRIAliases = dbContext->getAliases(
1429
0
                                    *(objIdentifiers[0]->codeSpace()),
1430
0
                                    objIdentifiers[0]->code(),
1431
0
                                    std::string(), // officialName,
1432
0
                                    "geodetic_datum", "ESRI");
1433
0
                                if (ESRIAliases.size() == 1) {
1434
0
                                    l_name = ESRIAliases.front();
1435
0
                                    aliasFound = true;
1436
0
                                    break;
1437
0
                                }
1438
0
                            }
1439
0
                        }
1440
0
                    }
1441
0
                }
1442
0
                if (!aliasFound) {
1443
0
                    l_name = io::WKTFormatter::morphNameToESRI(l_name);
1444
0
                    if (!starts_with(l_name, "D_")) {
1445
0
                        l_name = "D_" + l_name;
1446
0
                    }
1447
0
                }
1448
0
            }
1449
0
        } else {
1450
            // Replace spaces by underscore for datum names coming from EPSG
1451
            // so as to emulate GDAL < 3 importFromEPSG()
1452
0
            if (ids.size() == 1 && *(ids.front()->codeSpace()) == "EPSG") {
1453
0
                l_name = io::WKTFormatter::morphNameToESRI(l_name);
1454
0
            } else if (ids.empty()) {
1455
0
                const auto &dbContext = formatter->databaseContext();
1456
0
                if (dbContext) {
1457
0
                    auto factory = io::AuthorityFactory::create(
1458
0
                        NN_NO_CHECK(dbContext), std::string());
1459
                    // We use anonymous authority and approximate matching, so
1460
                    // as to trigger the caching done in createObjectsFromName()
1461
                    // in that case.
1462
0
                    auto matches = factory->createObjectsFromName(
1463
0
                        l_name,
1464
0
                        {io::AuthorityFactory::ObjectType::
1465
0
                             GEODETIC_REFERENCE_FRAME},
1466
0
                        true, 2);
1467
0
                    if (matches.size() == 1) {
1468
0
                        const auto &match = matches.front();
1469
0
                        const auto &matchId = match->identifiers();
1470
0
                        if (matchId.size() == 1 &&
1471
0
                            *(matchId.front()->codeSpace()) == "EPSG" &&
1472
0
                            metadata::Identifier::isEquivalentName(
1473
0
                                l_name.c_str(), match->nameStr().c_str())) {
1474
0
                            l_name = io::WKTFormatter::morphNameToESRI(l_name);
1475
0
                        }
1476
0
                    }
1477
0
                }
1478
0
            }
1479
0
            if (l_name == "World_Geodetic_System_1984") {
1480
0
                l_name = "WGS_1984";
1481
0
            }
1482
0
        }
1483
0
    }
1484
0
    formatter->addQuotedString(l_name);
1485
1486
0
    ellipsoid()->_exportToWKT(formatter);
1487
0
    if (isWKT2) {
1488
0
        Datum::getPrivate()->exportAnchorDefinition(formatter);
1489
0
        if (formatter->use2019Keywords()) {
1490
0
            Datum::getPrivate()->exportAnchorEpoch(formatter);
1491
0
        }
1492
0
    } else {
1493
0
        const auto &TOWGS84Params = formatter->getTOWGS84Parameters();
1494
0
        if (TOWGS84Params.size() == 7) {
1495
0
            formatter->startNode(io::WKTConstants::TOWGS84, false);
1496
0
            for (const auto &val : TOWGS84Params) {
1497
0
                formatter->add(val, 12);
1498
0
            }
1499
0
            formatter->endNode();
1500
0
        }
1501
0
        std::string extension = formatter->getHDatumExtension();
1502
0
        if (!extension.empty()) {
1503
0
            formatter->startNode(io::WKTConstants::EXTENSION, false);
1504
0
            formatter->addQuotedString("PROJ4_GRIDS");
1505
0
            formatter->addQuotedString(extension);
1506
0
            formatter->endNode();
1507
0
        }
1508
0
    }
1509
0
    if (formatter->outputId()) {
1510
0
        formatID(formatter);
1511
0
    }
1512
    // the PRIMEM is exported as a child of the CRS
1513
0
    formatter->endNode();
1514
1515
0
    if (formatter->isAtTopLevel()) {
1516
0
        const auto &l_primeMeridian(primeMeridian());
1517
0
        if (l_primeMeridian->nameStr() != "Greenwich") {
1518
0
            l_primeMeridian->_exportToWKT(formatter);
1519
0
        }
1520
0
    }
1521
0
}
1522
//! @endcond
1523
1524
// ---------------------------------------------------------------------------
1525
1526
//! @cond Doxygen_Suppress
1527
void GeodeticReferenceFrame::_exportToJSON(
1528
    io::JSONFormatter *formatter) const // throw(FormattingException)
1529
0
{
1530
0
    auto dynamicGRF = dynamic_cast<const DynamicGeodeticReferenceFrame *>(this);
1531
1532
0
    auto objectContext(formatter->MakeObjectContext(
1533
0
        dynamicGRF ? "DynamicGeodeticReferenceFrame" : "GeodeticReferenceFrame",
1534
0
        !identifiers().empty()));
1535
0
    auto writer = formatter->writer();
1536
1537
0
    writer->AddObjKey("name");
1538
0
    const auto &l_name = nameStr();
1539
0
    if (l_name.empty()) {
1540
0
        writer->Add("unnamed");
1541
0
    } else {
1542
0
        writer->Add(l_name);
1543
0
    }
1544
1545
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
1546
0
    Datum::getPrivate()->exportAnchorEpoch(formatter);
1547
1548
0
    if (dynamicGRF) {
1549
0
        writer->AddObjKey("frame_reference_epoch");
1550
0
        writer->Add(dynamicGRF->frameReferenceEpoch().value());
1551
0
    }
1552
1553
0
    writer->AddObjKey("ellipsoid");
1554
0
    formatter->setOmitTypeInImmediateChild();
1555
0
    ellipsoid()->_exportToJSON(formatter);
1556
1557
0
    const auto &l_primeMeridian(primeMeridian());
1558
0
    if (l_primeMeridian->nameStr() != "Greenwich") {
1559
0
        writer->AddObjKey("prime_meridian");
1560
0
        formatter->setOmitTypeInImmediateChild();
1561
0
        primeMeridian()->_exportToJSON(formatter);
1562
0
    }
1563
1564
0
    ObjectUsage::baseExportToJSON(formatter);
1565
0
}
1566
//! @endcond
1567
1568
// ---------------------------------------------------------------------------
1569
1570
//! @cond Doxygen_Suppress
1571
1572
bool GeodeticReferenceFrame::isEquivalentToNoExactTypeCheck(
1573
    const util::IComparable *other, util::IComparable::Criterion criterion,
1574
1.16M
    const io::DatabaseContextPtr &dbContext) const {
1575
1.16M
    auto otherGRF = dynamic_cast<const GeodeticReferenceFrame *>(other);
1576
1.16M
    if (otherGRF == nullptr ||
1577
1.16M
        !Datum::_isEquivalentTo(other, criterion, dbContext)) {
1578
127k
        return false;
1579
127k
    }
1580
1.04M
    return primeMeridian()->_isEquivalentTo(otherGRF->primeMeridian().get(),
1581
1.04M
                                            criterion, dbContext) &&
1582
1.03M
           ellipsoid()->_isEquivalentTo(otherGRF->ellipsoid().get(), criterion,
1583
1.03M
                                        dbContext);
1584
1.16M
}
1585
1586
// ---------------------------------------------------------------------------
1587
1588
bool GeodeticReferenceFrame::_isEquivalentTo(
1589
    const util::IComparable *other, util::IComparable::Criterion criterion,
1590
906k
    const io::DatabaseContextPtr &dbContext) const {
1591
906k
    if (criterion == Criterion::STRICT &&
1592
15
        !util::isOfExactType<GeodeticReferenceFrame>(*other)) {
1593
1
        return false;
1594
1
    }
1595
906k
    return isEquivalentToNoExactTypeCheck(other, criterion, dbContext);
1596
906k
}
1597
1598
//! @endcond
1599
1600
// ---------------------------------------------------------------------------
1601
1602
bool GeodeticReferenceFrame::hasEquivalentNameToUsingAlias(
1603
    const IdentifiedObject *other,
1604
131k
    const io::DatabaseContextPtr &dbContext) const {
1605
1606
131k
    const auto compareFromThisId =
1607
131k
        [&dbContext](const GeodeticReferenceFrame &self,
1608
131k
                     const std::string &thisName,
1609
151k
                     const std::string &otherName) {
1610
151k
            const auto &id = self.identifiers().front();
1611
1612
151k
            const std::string officialNameFromId = dbContext->getName(
1613
151k
                "geodetic_datum", *(id->codeSpace()), id->code());
1614
151k
            const auto aliasesResult =
1615
151k
                dbContext->getAliases(*(id->codeSpace()), id->code(), thisName,
1616
151k
                                      "geodetic_datum", std::string());
1617
1618
151k
            const auto isNameMatching =
1619
298k
                [&aliasesResult, &officialNameFromId](const std::string &name) {
1620
298k
                    const char *nameCstr = name.c_str();
1621
298k
                    if (metadata::Identifier::isEquivalentName(
1622
298k
                            nameCstr, officialNameFromId.c_str())) {
1623
79.7k
                        return true;
1624
218k
                    } else {
1625
670k
                        for (const auto &aliasResult : aliasesResult) {
1626
670k
                            if (metadata::Identifier::isEquivalentName(
1627
670k
                                    nameCstr, aliasResult.c_str())) {
1628
69.5k
                                return true;
1629
69.5k
                            }
1630
670k
                        }
1631
218k
                    }
1632
149k
                    return false;
1633
298k
                };
1634
1635
151k
            return isNameMatching(thisName) && isNameMatching(otherName);
1636
151k
        };
1637
1638
131k
    const auto compareFromThisName = [&dbContext](
1639
131k
                                         const std::string &thisName,
1640
165k
                                         const std::string &otherName) {
1641
165k
        auto aliasesResult =
1642
165k
            dbContext->getAliases(std::string(), std::string(), thisName,
1643
165k
                                  "geodetic_datum", std::string());
1644
165k
        const char *otherNamePtr = otherName.c_str();
1645
530k
        for (const auto &aliasResult : aliasesResult) {
1646
530k
            if (metadata::Identifier::isEquivalentName(otherNamePtr,
1647
530k
                                                       aliasResult.c_str())) {
1648
0
                return true;
1649
0
            }
1650
530k
        }
1651
165k
        return false;
1652
165k
    };
1653
1654
131k
    const auto compare = [this, other, &dbContext, &compareFromThisId,
1655
131k
                          &compareFromThisName](const std::string &thisName,
1656
131k
                                                const std::string &otherName) {
1657
131k
        if (thisName == otherName || thisName == "unknown" ||
1658
130k
            otherName == "unknown") {
1659
1.82k
            return true;
1660
1.82k
        }
1661
1662
129k
        if (ci_starts_with(thisName, UNKNOWN_BASED_ON) ||
1663
119k
            ci_starts_with(otherName, UNKNOWN_BASED_ON)) {
1664
            // Note: they cannot be equal based on initial test.
1665
12.0k
            return false;
1666
12.0k
        }
1667
1668
117k
        if (dbContext) {
1669
84.4k
            if (!identifiers().empty()) {
1670
81.8k
                if (compareFromThisId(*this, thisName, otherName)) {
1671
1.81k
                    return true;
1672
1.81k
                }
1673
81.8k
            }
1674
82.6k
            if (!other->identifiers().empty()) {
1675
69.1k
                auto otherGRF =
1676
69.1k
                    dynamic_cast<const GeodeticReferenceFrame *>(other);
1677
69.1k
                if (otherGRF) {
1678
69.1k
                    if (compareFromThisId(*otherGRF, otherName, thisName)) {
1679
15
                        return true;
1680
15
                    }
1681
69.1k
                }
1682
69.1k
            }
1683
1684
82.6k
            if (compareFromThisName(thisName, otherName) ||
1685
82.6k
                compareFromThisName(otherName, thisName)) {
1686
0
                return true;
1687
0
            }
1688
82.6k
        }
1689
115k
        return false;
1690
117k
    };
1691
1692
    // Try to work around issues with Esri style "D_" name prefixing
1693
    // Cf https://github.com/OSGeo/PROJ/issues/4514
1694
131k
    const bool thisStartsWithDUnderscore = ci_starts_with(nameStr(), "D_");
1695
131k
    const bool otherStartsWithDUnderscore =
1696
131k
        ci_starts_with(other->nameStr(), "D_");
1697
131k
    if (thisStartsWithDUnderscore && !otherStartsWithDUnderscore) {
1698
458
        const std::string thisNameMod = nameStr().substr(2);
1699
458
        return metadata::Identifier::isEquivalentName(
1700
458
                   thisNameMod.c_str(), other->nameStr().c_str()) ||
1701
458
               compare(thisNameMod, other->nameStr());
1702
130k
    } else if (!thisStartsWithDUnderscore && otherStartsWithDUnderscore) {
1703
1.46k
        const std::string otherNameMod = other->nameStr().substr(2);
1704
1.46k
        return metadata::Identifier::isEquivalentName(nameStr().c_str(),
1705
1.46k
                                                      otherNameMod.c_str()) ||
1706
1.46k
               compare(nameStr(), otherNameMod);
1707
129k
    } else {
1708
129k
        return compare(nameStr(), other->nameStr());
1709
129k
    }
1710
131k
}
1711
1712
// ---------------------------------------------------------------------------
1713
1714
//! @cond Doxygen_Suppress
1715
struct DynamicGeodeticReferenceFrame::Private {
1716
    common::Measure frameReferenceEpoch{};
1717
    util::optional<std::string> deformationModelName{};
1718
1719
    explicit Private(const common::Measure &frameReferenceEpochIn)
1720
25.6k
        : frameReferenceEpoch(frameReferenceEpochIn) {}
1721
};
1722
//! @endcond
1723
1724
// ---------------------------------------------------------------------------
1725
1726
DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame(
1727
    const EllipsoidNNPtr &ellipsoidIn,
1728
    const PrimeMeridianNNPtr &primeMeridianIn,
1729
    const common::Measure &frameReferenceEpochIn,
1730
    const util::optional<std::string> &deformationModelNameIn)
1731
25.6k
    : GeodeticReferenceFrame(ellipsoidIn, primeMeridianIn),
1732
25.6k
      d(std::make_unique<Private>(frameReferenceEpochIn)) {
1733
25.6k
    d->deformationModelName = deformationModelNameIn;
1734
25.6k
}
1735
1736
// ---------------------------------------------------------------------------
1737
1738
#ifdef notdef
1739
DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame(
1740
    const DynamicGeodeticReferenceFrame &other)
1741
    : GeodeticReferenceFrame(other), d(std::make_unique<Private>(*other.d)) {}
1742
#endif
1743
1744
// ---------------------------------------------------------------------------
1745
1746
//! @cond Doxygen_Suppress
1747
25.6k
DynamicGeodeticReferenceFrame::~DynamicGeodeticReferenceFrame() = default;
1748
//! @endcond
1749
1750
// ---------------------------------------------------------------------------
1751
1752
/** \brief Return the epoch to which the coordinates of stations defining the
1753
 * dynamic geodetic reference frame are referenced.
1754
 *
1755
 * Usually given as a decimal year e.g. 2016.47.
1756
 *
1757
 * @return the frame reference epoch.
1758
 */
1759
const common::Measure &
1760
468k
DynamicGeodeticReferenceFrame::frameReferenceEpoch() const {
1761
468k
    return d->frameReferenceEpoch;
1762
468k
}
1763
1764
// ---------------------------------------------------------------------------
1765
1766
/** \brief Return the name of the deformation model.
1767
 *
1768
 * @note This is an extension to the \ref ISO_19111_2019 modeling, to
1769
 * hold the content of the DYNAMIC.MODEL WKT2 node.
1770
 *
1771
 * @return the name of the deformation model.
1772
 */
1773
const util::optional<std::string> &
1774
468k
DynamicGeodeticReferenceFrame::deformationModelName() const {
1775
468k
    return d->deformationModelName;
1776
468k
}
1777
1778
// ---------------------------------------------------------------------------
1779
1780
//! @cond Doxygen_Suppress
1781
bool DynamicGeodeticReferenceFrame::_isEquivalentTo(
1782
    const util::IComparable *other, util::IComparable::Criterion criterion,
1783
261k
    const io::DatabaseContextPtr &dbContext) const {
1784
261k
    if (criterion == Criterion::STRICT &&
1785
4
        !util::isOfExactType<DynamicGeodeticReferenceFrame>(*other)) {
1786
3
        return false;
1787
3
    }
1788
261k
    if (!GeodeticReferenceFrame::isEquivalentToNoExactTypeCheck(
1789
261k
            other, criterion, dbContext)) {
1790
27.3k
        return false;
1791
27.3k
    }
1792
234k
    auto otherDGRF = dynamic_cast<const DynamicGeodeticReferenceFrame *>(other);
1793
234k
    if (otherDGRF == nullptr) {
1794
        // we can go here only if criterion != Criterion::STRICT, and thus
1795
        // given the above check we can consider the objects equivalent.
1796
0
        return true;
1797
0
    }
1798
234k
    return frameReferenceEpoch()._isEquivalentTo(
1799
234k
               otherDGRF->frameReferenceEpoch(), criterion) &&
1800
234k
           metadata::Identifier::isEquivalentName(
1801
234k
               deformationModelName()->c_str(),
1802
234k
               otherDGRF->deformationModelName()->c_str());
1803
234k
}
1804
//! @endcond
1805
1806
// ---------------------------------------------------------------------------
1807
1808
//! @cond Doxygen_Suppress
1809
void DynamicGeodeticReferenceFrame::_exportToWKT(
1810
    io::WKTFormatter *formatter) const // throw(FormattingException)
1811
0
{
1812
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
1813
0
    if (isWKT2 && formatter->use2019Keywords()) {
1814
0
        formatter->startNode(io::WKTConstants::DYNAMIC, false);
1815
0
        formatter->startNode(io::WKTConstants::FRAMEEPOCH, false);
1816
0
        formatter->add(
1817
0
            frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR));
1818
0
        formatter->endNode();
1819
0
        if (deformationModelName().has_value() &&
1820
0
            !deformationModelName()->empty()) {
1821
0
            formatter->startNode(io::WKTConstants::MODEL, false);
1822
0
            formatter->addQuotedString(*deformationModelName());
1823
0
            formatter->endNode();
1824
0
        }
1825
0
        formatter->endNode();
1826
0
    }
1827
0
    GeodeticReferenceFrame::_exportToWKT(formatter);
1828
0
}
1829
//! @endcond
1830
1831
// ---------------------------------------------------------------------------
1832
1833
/** \brief Instantiate a DynamicGeodeticReferenceFrame
1834
 *
1835
 * @param properties See \ref general_properties.
1836
 * At minimum the name should be defined.
1837
 * @param ellipsoid the Ellipsoid.
1838
 * @param anchor the anchor definition, or empty.
1839
 * @param primeMeridian the PrimeMeridian.
1840
 * @param frameReferenceEpochIn the frame reference epoch.
1841
 * @param deformationModelNameIn deformation model name, or empty
1842
 * @return new DynamicGeodeticReferenceFrame.
1843
 */
1844
DynamicGeodeticReferenceFrameNNPtr DynamicGeodeticReferenceFrame::create(
1845
    const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid,
1846
    const util::optional<std::string> &anchor,
1847
    const PrimeMeridianNNPtr &primeMeridian,
1848
    const common::Measure &frameReferenceEpochIn,
1849
25.6k
    const util::optional<std::string> &deformationModelNameIn) {
1850
25.6k
    DynamicGeodeticReferenceFrameNNPtr grf(
1851
25.6k
        DynamicGeodeticReferenceFrame::nn_make_shared<
1852
25.6k
            DynamicGeodeticReferenceFrame>(ellipsoid, primeMeridian,
1853
25.6k
                                           frameReferenceEpochIn,
1854
25.6k
                                           deformationModelNameIn));
1855
25.6k
    grf->setAnchor(anchor);
1856
25.6k
    grf->setProperties(properties);
1857
25.6k
    return grf;
1858
25.6k
}
1859
1860
// ---------------------------------------------------------------------------
1861
1862
//! @cond Doxygen_Suppress
1863
struct DatumEnsemble::Private {
1864
    std::vector<DatumNNPtr> datums{};
1865
    metadata::PositionalAccuracyNNPtr positionalAccuracy;
1866
1867
    Private(const std::vector<DatumNNPtr> &datumsIn,
1868
            const metadata::PositionalAccuracyNNPtr &accuracy)
1869
3.08k
        : datums(datumsIn), positionalAccuracy(accuracy) {}
1870
};
1871
//! @endcond
1872
1873
// ---------------------------------------------------------------------------
1874
1875
DatumEnsemble::DatumEnsemble(const std::vector<DatumNNPtr> &datumsIn,
1876
                             const metadata::PositionalAccuracyNNPtr &accuracy)
1877
3.08k
    : d(std::make_unique<Private>(datumsIn, accuracy)) {}
1878
1879
// ---------------------------------------------------------------------------
1880
1881
#ifdef notdef
1882
DatumEnsemble::DatumEnsemble(const DatumEnsemble &other)
1883
    : common::ObjectUsage(other), d(std::make_unique<Private>(*other.d)) {}
1884
#endif
1885
1886
// ---------------------------------------------------------------------------
1887
1888
//! @cond Doxygen_Suppress
1889
3.08k
DatumEnsemble::~DatumEnsemble() = default;
1890
//! @endcond
1891
1892
// ---------------------------------------------------------------------------
1893
1894
/** \brief Return the set of datums which may be considered to be
1895
 * insignificantly different from each other.
1896
 *
1897
 * @return the set of datums of the DatumEnsemble.
1898
 */
1899
1.27M
const std::vector<DatumNNPtr> &DatumEnsemble::datums() const {
1900
1.27M
    return d->datums;
1901
1.27M
}
1902
1903
// ---------------------------------------------------------------------------
1904
1905
/** \brief Return the inaccuracy introduced through use of this collection of
1906
 * datums.
1907
 *
1908
 * It is an indication of the differences in coordinate values at all points
1909
 * between the various realizations that have been grouped into this datum
1910
 * ensemble.
1911
 *
1912
 * @return the accuracy.
1913
 */
1914
const metadata::PositionalAccuracyNNPtr &
1915
0
DatumEnsemble::positionalAccuracy() const {
1916
0
    return d->positionalAccuracy;
1917
0
}
1918
1919
// ---------------------------------------------------------------------------
1920
1921
//! @cond Doxygen_Suppress
1922
1923
/* static */
1924
433k
std::string DatumEnsemble::ensembleNameToNonEnsembleName(const std::string &s) {
1925
433k
    if (s == "World Geodetic System 1984 ensemble") {
1926
395k
        return "World Geodetic System 1984";
1927
395k
    } else if (s == "European Terrestrial Reference System 1989 ensemble") {
1928
4.24k
        return "European Terrestrial Reference System 1989";
1929
33.8k
    } else if (s == "Greenland Reference 1996 ensemble") {
1930
0
        return "Greenland 1996";
1931
0
    }
1932
33.8k
    return std::string();
1933
433k
}
1934
1935
//! @endcond
1936
1937
// ---------------------------------------------------------------------------
1938
1939
// ---------------------------------------------------------------------------
1940
1941
//! @cond Doxygen_Suppress
1942
DatumNNPtr
1943
719k
DatumEnsemble::asDatum(const io::DatabaseContextPtr &dbContext) const {
1944
1945
719k
    const auto &l_datums = datums();
1946
719k
    auto *grf = dynamic_cast<const GeodeticReferenceFrame *>(l_datums[0].get());
1947
1948
719k
    const auto &l_identifiers = identifiers();
1949
719k
    if (dbContext) {
1950
322k
        if (!l_identifiers.empty()) {
1951
321k
            const auto &id = l_identifiers[0];
1952
321k
            try {
1953
321k
                auto factory = io::AuthorityFactory::create(
1954
321k
                    NN_NO_CHECK(dbContext), *(id->codeSpace()));
1955
321k
                if (grf) {
1956
321k
                    return factory->createGeodeticDatum(id->code());
1957
321k
                } else {
1958
0
                    return factory->createVerticalDatum(id->code());
1959
0
                }
1960
321k
            } catch (const std::exception &) {
1961
14
            }
1962
321k
        }
1963
322k
    }
1964
1965
397k
    std::string l_name(nameStr());
1966
397k
    if (grf) {
1967
        // Remap to traditional datum names
1968
397k
        auto oldName = ensembleNameToNonEnsembleName(l_name);
1969
397k
        if (!oldName.empty())
1970
397k
            l_name = std::move(oldName);
1971
397k
    }
1972
397k
    auto props =
1973
397k
        util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, l_name);
1974
397k
    if (isDeprecated()) {
1975
0
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
1976
0
    }
1977
397k
    if (!l_identifiers.empty()) {
1978
397k
        const auto &id = l_identifiers[0];
1979
397k
        props.set(metadata::Identifier::CODESPACE_KEY, *(id->codeSpace()))
1980
397k
            .set(metadata::Identifier::CODE_KEY, id->code());
1981
397k
    }
1982
397k
    const auto &l_usages = domains();
1983
397k
    if (!l_usages.empty()) {
1984
1985
397k
        auto array(util::ArrayOfBaseObject::create());
1986
398k
        for (const auto &usage : l_usages) {
1987
398k
            array->add(usage);
1988
398k
        }
1989
397k
        props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
1990
397k
                  util::nn_static_pointer_cast<util::BaseObject>(array));
1991
397k
    }
1992
397k
    const auto anchor = util::optional<std::string>();
1993
1994
397k
    if (grf) {
1995
397k
        return GeodeticReferenceFrame::create(props, grf->ellipsoid(), anchor,
1996
397k
                                              grf->primeMeridian());
1997
397k
    } else {
1998
14
        assert(dynamic_cast<VerticalReferenceFrame *>(l_datums[0].get()));
1999
14
        return datum::VerticalReferenceFrame::create(props, anchor);
2000
14
    }
2001
397k
}
2002
//! @endcond
2003
2004
// ---------------------------------------------------------------------------
2005
2006
//! @cond Doxygen_Suppress
2007
void DatumEnsemble::_exportToWKT(
2008
    io::WKTFormatter *formatter) const // throw(FormattingException)
2009
0
{
2010
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2011
0
    if (!isWKT2 || !formatter->use2019Keywords()) {
2012
0
        return asDatum(formatter->databaseContext())->_exportToWKT(formatter);
2013
0
    }
2014
2015
0
    const auto &l_datums = datums();
2016
0
    assert(!l_datums.empty());
2017
2018
0
    formatter->startNode(io::WKTConstants::ENSEMBLE, false);
2019
0
    const auto &l_name = nameStr();
2020
0
    if (!l_name.empty()) {
2021
0
        formatter->addQuotedString(l_name);
2022
0
    } else {
2023
0
        formatter->addQuotedString("unnamed");
2024
0
    }
2025
2026
0
    for (const auto &datum : l_datums) {
2027
0
        formatter->startNode(io::WKTConstants::MEMBER,
2028
0
                             !datum->identifiers().empty());
2029
0
        const auto &l_datum_name = datum->nameStr();
2030
0
        if (!l_datum_name.empty()) {
2031
0
            formatter->addQuotedString(l_datum_name);
2032
0
        } else {
2033
0
            formatter->addQuotedString("unnamed");
2034
0
        }
2035
0
        if (formatter->outputId()) {
2036
0
            datum->formatID(formatter);
2037
0
        }
2038
0
        formatter->endNode();
2039
0
    }
2040
2041
0
    auto grfFirst = std::dynamic_pointer_cast<GeodeticReferenceFrame>(
2042
0
        l_datums[0].as_nullable());
2043
0
    if (grfFirst) {
2044
0
        grfFirst->ellipsoid()->_exportToWKT(formatter);
2045
0
    }
2046
2047
0
    formatter->startNode(io::WKTConstants::ENSEMBLEACCURACY, false);
2048
0
    formatter->add(positionalAccuracy()->value());
2049
0
    formatter->endNode();
2050
2051
    // In theory, we should do the following, but currently the WKT grammar
2052
    // doesn't allow this
2053
    // ObjectUsage::baseExportToWKT(formatter);
2054
0
    if (formatter->outputId()) {
2055
0
        formatID(formatter);
2056
0
    }
2057
2058
0
    formatter->endNode();
2059
0
}
2060
//! @endcond
2061
2062
// ---------------------------------------------------------------------------
2063
2064
//! @cond Doxygen_Suppress
2065
void DatumEnsemble::_exportToJSON(
2066
    io::JSONFormatter *formatter) const // throw(FormattingException)
2067
0
{
2068
0
    auto objectContext(
2069
0
        formatter->MakeObjectContext("DatumEnsemble", !identifiers().empty()));
2070
0
    auto writer = formatter->writer();
2071
2072
0
    writer->AddObjKey("name");
2073
0
    const auto &l_name = nameStr();
2074
0
    if (l_name.empty()) {
2075
0
        writer->Add("unnamed");
2076
0
    } else {
2077
0
        writer->Add(l_name);
2078
0
    }
2079
2080
0
    const auto &l_datums = datums();
2081
0
    writer->AddObjKey("members");
2082
0
    {
2083
0
        auto membersContext(writer->MakeArrayContext(false));
2084
0
        for (const auto &datum : l_datums) {
2085
0
            auto memberContext(writer->MakeObjectContext());
2086
0
            writer->AddObjKey("name");
2087
0
            const auto &l_datum_name = datum->nameStr();
2088
0
            if (!l_datum_name.empty()) {
2089
0
                writer->Add(l_datum_name);
2090
0
            } else {
2091
0
                writer->Add("unnamed");
2092
0
            }
2093
0
            datum->formatID(formatter);
2094
0
        }
2095
0
    }
2096
2097
0
    auto grfFirst = std::dynamic_pointer_cast<GeodeticReferenceFrame>(
2098
0
        l_datums[0].as_nullable());
2099
0
    if (grfFirst) {
2100
0
        writer->AddObjKey("ellipsoid");
2101
0
        formatter->setOmitTypeInImmediateChild();
2102
0
        grfFirst->ellipsoid()->_exportToJSON(formatter);
2103
0
    }
2104
2105
0
    writer->AddObjKey("accuracy");
2106
0
    writer->Add(positionalAccuracy()->value());
2107
2108
0
    formatID(formatter);
2109
0
}
2110
//! @endcond
2111
2112
// ---------------------------------------------------------------------------
2113
2114
/** \brief Instantiate a DatumEnsemble.
2115
 *
2116
 * @param properties See \ref general_properties.
2117
 * At minimum the name should be defined.
2118
 * @param datumsIn Array of at least 2 datums.
2119
 * @param accuracy Accuracy of the datum ensemble
2120
 * @return new DatumEnsemble.
2121
 * @throw util::Exception in case of error.
2122
 */
2123
DatumEnsembleNNPtr DatumEnsemble::create(
2124
    const util::PropertyMap &properties,
2125
    const std::vector<DatumNNPtr> &datumsIn,
2126
    const metadata::PositionalAccuracyNNPtr &accuracy) // throw(Exception)
2127
3.09k
{
2128
3.09k
    if (datumsIn.size() < 2) {
2129
3
        throw util::Exception("ensemble should have at least 2 datums");
2130
3
    }
2131
3.08k
    if (auto grfFirst =
2132
3.08k
            dynamic_cast<const GeodeticReferenceFrame *>(datumsIn[0].get())) {
2133
33.6k
        for (size_t i = 1; i < datumsIn.size(); i++) {
2134
30.6k
            auto grf =
2135
30.6k
                dynamic_cast<const GeodeticReferenceFrame *>(datumsIn[i].get());
2136
30.6k
            if (!grf) {
2137
0
                throw util::Exception(
2138
0
                    "ensemble should have consistent datum types");
2139
0
            }
2140
30.6k
            if (!grfFirst->ellipsoid()->_isEquivalentTo(
2141
30.6k
                    grf->ellipsoid().get())) {
2142
0
                throw util::Exception(
2143
0
                    "ensemble should have datums with identical ellipsoid");
2144
0
            }
2145
30.6k
            if (!grfFirst->primeMeridian()->_isEquivalentTo(
2146
30.6k
                    grf->primeMeridian().get())) {
2147
0
                throw util::Exception(
2148
0
                    "ensemble should have datums with identical "
2149
0
                    "prime meridian");
2150
0
            }
2151
30.6k
        }
2152
3.05k
    } else if (dynamic_cast<VerticalReferenceFrame *>(datumsIn[0].get())) {
2153
127
        for (size_t i = 1; i < datumsIn.size(); i++) {
2154
93
            if (!dynamic_cast<VerticalReferenceFrame *>(datumsIn[i].get())) {
2155
0
                throw util::Exception(
2156
0
                    "ensemble should have consistent datum types");
2157
0
            }
2158
93
        }
2159
34
    }
2160
3.08k
    auto ensemble(
2161
3.08k
        DatumEnsemble::nn_make_shared<DatumEnsemble>(datumsIn, accuracy));
2162
3.08k
    ensemble->setProperties(properties);
2163
3.08k
    return ensemble;
2164
3.08k
}
2165
2166
// ---------------------------------------------------------------------------
2167
2168
RealizationMethod::RealizationMethod(const std::string &nameIn)
2169
18.0k
    : CodeList(nameIn) {}
2170
2171
// ---------------------------------------------------------------------------
2172
2173
RealizationMethod &
2174
0
RealizationMethod::operator=(const RealizationMethod &other) {
2175
0
    CodeList::operator=(other);
2176
0
    return *this;
2177
0
}
2178
2179
// ---------------------------------------------------------------------------
2180
2181
//! @cond Doxygen_Suppress
2182
struct VerticalReferenceFrame::Private {
2183
    util::optional<RealizationMethod> realizationMethod_{};
2184
2185
    // 2005 = CS_VD_GeoidModelDerived from OGC 01-009
2186
    std::string wkt1DatumType_{"2005"};
2187
};
2188
//! @endcond
2189
2190
// ---------------------------------------------------------------------------
2191
2192
VerticalReferenceFrame::VerticalReferenceFrame(
2193
    const util::optional<RealizationMethod> &realizationMethodIn)
2194
9.00k
    : d(std::make_unique<Private>()) {
2195
9.00k
    if (!realizationMethodIn->toString().empty()) {
2196
0
        d->realizationMethod_ = *realizationMethodIn;
2197
0
    }
2198
9.00k
}
2199
2200
// ---------------------------------------------------------------------------
2201
2202
//! @cond Doxygen_Suppress
2203
9.00k
VerticalReferenceFrame::~VerticalReferenceFrame() = default;
2204
//! @endcond
2205
2206
// ---------------------------------------------------------------------------
2207
2208
/** \brief Return the method through which this vertical reference frame is
2209
 * realized.
2210
 *
2211
 * @return the realization method.
2212
 */
2213
const util::optional<RealizationMethod> &
2214
198k
VerticalReferenceFrame::realizationMethod() const {
2215
198k
    return d->realizationMethod_;
2216
198k
}
2217
2218
// ---------------------------------------------------------------------------
2219
2220
/** \brief Instantiate a VerticalReferenceFrame
2221
 *
2222
 * @param properties See \ref general_properties.
2223
 * At minimum the name should be defined.
2224
 * @param anchor the anchor definition, or empty.
2225
 * @param realizationMethodIn the realization method, or empty.
2226
 * @return new VerticalReferenceFrame.
2227
 */
2228
VerticalReferenceFrameNNPtr VerticalReferenceFrame::create(
2229
    const util::PropertyMap &properties,
2230
    const util::optional<std::string> &anchor,
2231
8.20k
    const util::optional<RealizationMethod> &realizationMethodIn) {
2232
8.20k
    auto rf(VerticalReferenceFrame::nn_make_shared<VerticalReferenceFrame>(
2233
8.20k
        realizationMethodIn));
2234
8.20k
    rf->setAnchor(anchor);
2235
8.20k
    rf->setProperties(properties);
2236
8.20k
    properties.getStringValue("VERT_DATUM_TYPE", rf->d->wkt1DatumType_);
2237
8.20k
    return rf;
2238
8.20k
}
2239
2240
// ---------------------------------------------------------------------------
2241
2242
/** \brief Instantiate a VerticalReferenceFrame
2243
 *
2244
 * @param properties See \ref general_properties.
2245
 * At minimum the name should be defined.
2246
 * @param anchor the anchor definition, or empty.
2247
 * @param anchorEpoch the anchor epoch, or empty.
2248
 * @param realizationMethodIn the realization method, or empty.
2249
 * @return new VerticalReferenceFrame.
2250
 * @since 9.2
2251
 */
2252
VerticalReferenceFrameNNPtr VerticalReferenceFrame::create(
2253
    const util::PropertyMap &properties,
2254
    const util::optional<std::string> &anchor,
2255
    const util::optional<common::Measure> &anchorEpoch,
2256
800
    const util::optional<RealizationMethod> &realizationMethodIn) {
2257
800
    auto rf(VerticalReferenceFrame::nn_make_shared<VerticalReferenceFrame>(
2258
800
        realizationMethodIn));
2259
800
    rf->setAnchor(anchor);
2260
800
    rf->setAnchorEpoch(anchorEpoch);
2261
800
    rf->setProperties(properties);
2262
800
    properties.getStringValue("VERT_DATUM_TYPE", rf->d->wkt1DatumType_);
2263
800
    return rf;
2264
800
}
2265
2266
// ---------------------------------------------------------------------------
2267
2268
//! @cond Doxygen_Suppress
2269
802
const std::string &VerticalReferenceFrame::getWKT1DatumType() const {
2270
802
    return d->wkt1DatumType_;
2271
802
}
2272
//! @endcond
2273
2274
// ---------------------------------------------------------------------------
2275
2276
//! @cond Doxygen_Suppress
2277
void VerticalReferenceFrame::_exportToWKT(
2278
    io::WKTFormatter *formatter) const // throw(FormattingException)
2279
0
{
2280
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2281
0
    formatter->startNode(isWKT2 ? io::WKTConstants::VDATUM
2282
0
                         : formatter->useESRIDialect()
2283
0
                             ? io::WKTConstants::VDATUM
2284
0
                             : io::WKTConstants::VERT_DATUM,
2285
0
                         !identifiers().empty());
2286
0
    std::string l_name(nameStr());
2287
0
    if (!l_name.empty()) {
2288
0
        if (!isWKT2 && formatter->useESRIDialect()) {
2289
0
            bool aliasFound = false;
2290
0
            const auto &dbContext = formatter->databaseContext();
2291
0
            if (dbContext) {
2292
0
                auto l_alias = dbContext->getAliasFromOfficialName(
2293
0
                    l_name, "vertical_datum", "ESRI");
2294
0
                if (!l_alias.empty()) {
2295
0
                    l_name = std::move(l_alias);
2296
0
                    aliasFound = true;
2297
0
                }
2298
0
            }
2299
0
            if (!aliasFound && dbContext) {
2300
0
                auto authFactory = io::AuthorityFactory::create(
2301
0
                    NN_NO_CHECK(dbContext), "ESRI");
2302
0
                aliasFound = authFactory
2303
0
                                 ->createObjectsFromName(
2304
0
                                     l_name,
2305
0
                                     {io::AuthorityFactory::ObjectType::
2306
0
                                          VERTICAL_REFERENCE_FRAME},
2307
0
                                     false // approximateMatch
2308
0
                                     )
2309
0
                                 .size() == 1;
2310
0
            }
2311
0
            if (!aliasFound) {
2312
0
                l_name = io::WKTFormatter::morphNameToESRI(l_name);
2313
0
            }
2314
0
        }
2315
0
        formatter->addQuotedString(l_name);
2316
0
    } else {
2317
0
        formatter->addQuotedString("unnamed");
2318
0
    }
2319
0
    if (isWKT2) {
2320
0
        Datum::getPrivate()->exportAnchorDefinition(formatter);
2321
0
        if (formatter->use2019Keywords()) {
2322
0
            Datum::getPrivate()->exportAnchorEpoch(formatter);
2323
0
        }
2324
0
    } else if (!formatter->useESRIDialect()) {
2325
0
        formatter->add(d->wkt1DatumType_);
2326
0
        const auto &extension = formatter->getVDatumExtension();
2327
0
        if (!extension.empty()) {
2328
0
            formatter->startNode(io::WKTConstants::EXTENSION, false);
2329
0
            formatter->addQuotedString("PROJ4_GRIDS");
2330
0
            formatter->addQuotedString(extension);
2331
0
            formatter->endNode();
2332
0
        }
2333
0
    }
2334
0
    if (formatter->outputId()) {
2335
0
        formatID(formatter);
2336
0
    }
2337
0
    formatter->endNode();
2338
0
}
2339
//! @endcond
2340
2341
// ---------------------------------------------------------------------------
2342
2343
//! @cond Doxygen_Suppress
2344
void VerticalReferenceFrame::_exportToJSON(
2345
    io::JSONFormatter *formatter) const // throw(FormattingException)
2346
0
{
2347
0
    auto dynamicGRF = dynamic_cast<const DynamicVerticalReferenceFrame *>(this);
2348
2349
0
    auto objectContext(formatter->MakeObjectContext(
2350
0
        dynamicGRF ? "DynamicVerticalReferenceFrame" : "VerticalReferenceFrame",
2351
0
        !identifiers().empty()));
2352
0
    auto writer = formatter->writer();
2353
2354
0
    writer->AddObjKey("name");
2355
0
    const auto &l_name = nameStr();
2356
0
    if (l_name.empty()) {
2357
0
        writer->Add("unnamed");
2358
0
    } else {
2359
0
        writer->Add(l_name);
2360
0
    }
2361
2362
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
2363
0
    Datum::getPrivate()->exportAnchorEpoch(formatter);
2364
2365
0
    if (dynamicGRF) {
2366
0
        writer->AddObjKey("frame_reference_epoch");
2367
0
        writer->Add(dynamicGRF->frameReferenceEpoch().value());
2368
0
    }
2369
2370
0
    ObjectUsage::baseExportToJSON(formatter);
2371
0
}
2372
//! @endcond
2373
2374
// ---------------------------------------------------------------------------
2375
2376
//! @cond Doxygen_Suppress
2377
bool VerticalReferenceFrame::isEquivalentToNoExactTypeCheck(
2378
    const util::IComparable *other, util::IComparable::Criterion criterion,
2379
67.1k
    const io::DatabaseContextPtr &dbContext) const {
2380
67.1k
    auto otherVRF = dynamic_cast<const VerticalReferenceFrame *>(other);
2381
67.1k
    if (otherVRF == nullptr ||
2382
67.1k
        !Datum::_isEquivalentTo(other, criterion, dbContext)) {
2383
1.01k
        return false;
2384
1.01k
    }
2385
66.1k
    if ((realizationMethod().has_value() ^
2386
66.1k
         otherVRF->realizationMethod().has_value())) {
2387
0
        return false;
2388
0
    }
2389
66.1k
    if (realizationMethod().has_value() &&
2390
0
        otherVRF->realizationMethod().has_value()) {
2391
0
        if (*(realizationMethod()) != *(otherVRF->realizationMethod())) {
2392
0
            return false;
2393
0
        }
2394
0
    }
2395
66.1k
    return true;
2396
66.1k
}
2397
2398
// ---------------------------------------------------------------------------
2399
2400
bool VerticalReferenceFrame::_isEquivalentTo(
2401
    const util::IComparable *other, util::IComparable::Criterion criterion,
2402
67.1k
    const io::DatabaseContextPtr &dbContext) const {
2403
67.1k
    if (criterion == Criterion::STRICT &&
2404
0
        !util::isOfExactType<VerticalReferenceFrame>(*other)) {
2405
0
        return false;
2406
0
    }
2407
67.1k
    return isEquivalentToNoExactTypeCheck(other, criterion, dbContext);
2408
67.1k
}
2409
2410
//! @endcond
2411
2412
// ---------------------------------------------------------------------------
2413
2414
//! @cond Doxygen_Suppress
2415
struct DynamicVerticalReferenceFrame::Private {
2416
    common::Measure frameReferenceEpoch{};
2417
    util::optional<std::string> deformationModelName{};
2418
2419
    explicit Private(const common::Measure &frameReferenceEpochIn)
2420
6
        : frameReferenceEpoch(frameReferenceEpochIn) {}
2421
};
2422
//! @endcond
2423
2424
// ---------------------------------------------------------------------------
2425
2426
DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame(
2427
    const util::optional<RealizationMethod> &realizationMethodIn,
2428
    const common::Measure &frameReferenceEpochIn,
2429
    const util::optional<std::string> &deformationModelNameIn)
2430
6
    : VerticalReferenceFrame(realizationMethodIn),
2431
6
      d(std::make_unique<Private>(frameReferenceEpochIn)) {
2432
6
    d->deformationModelName = deformationModelNameIn;
2433
6
}
2434
2435
// ---------------------------------------------------------------------------
2436
2437
#ifdef notdef
2438
DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame(
2439
    const DynamicVerticalReferenceFrame &other)
2440
    : VerticalReferenceFrame(other), d(std::make_unique<Private>(*other.d)) {}
2441
#endif
2442
2443
// ---------------------------------------------------------------------------
2444
2445
//! @cond Doxygen_Suppress
2446
6
DynamicVerticalReferenceFrame::~DynamicVerticalReferenceFrame() = default;
2447
//! @endcond
2448
2449
// ---------------------------------------------------------------------------
2450
2451
/** \brief Return the epoch to which the coordinates of stations defining the
2452
 * dynamic geodetic reference frame are referenced.
2453
 *
2454
 * Usually given as a decimal year e.g. 2016.47.
2455
 *
2456
 * @return the frame reference epoch.
2457
 */
2458
const common::Measure &
2459
0
DynamicVerticalReferenceFrame::frameReferenceEpoch() const {
2460
0
    return d->frameReferenceEpoch;
2461
0
}
2462
2463
// ---------------------------------------------------------------------------
2464
2465
/** \brief Return the name of the deformation model.
2466
 *
2467
 * @note This is an extension to the \ref ISO_19111_2019 modeling, to
2468
 * hold the content of the DYNAMIC.MODEL WKT2 node.
2469
 *
2470
 * @return the name of the deformation model.
2471
 */
2472
const util::optional<std::string> &
2473
0
DynamicVerticalReferenceFrame::deformationModelName() const {
2474
0
    return d->deformationModelName;
2475
0
}
2476
2477
// ---------------------------------------------------------------------------
2478
2479
//! @cond Doxygen_Suppress
2480
bool DynamicVerticalReferenceFrame::_isEquivalentTo(
2481
    const util::IComparable *other, util::IComparable::Criterion criterion,
2482
0
    const io::DatabaseContextPtr &dbContext) const {
2483
0
    if (criterion == Criterion::STRICT &&
2484
0
        !util::isOfExactType<DynamicVerticalReferenceFrame>(*other)) {
2485
0
        return false;
2486
0
    }
2487
0
    if (!VerticalReferenceFrame::isEquivalentToNoExactTypeCheck(
2488
0
            other, criterion, dbContext)) {
2489
0
        return false;
2490
0
    }
2491
0
    auto otherDGRF = dynamic_cast<const DynamicVerticalReferenceFrame *>(other);
2492
0
    if (otherDGRF == nullptr) {
2493
        // we can go here only if criterion != Criterion::STRICT, and thus
2494
        // given the above check we can consider the objects equivalent.
2495
0
        return true;
2496
0
    }
2497
0
    return frameReferenceEpoch()._isEquivalentTo(
2498
0
               otherDGRF->frameReferenceEpoch(), criterion) &&
2499
0
           metadata::Identifier::isEquivalentName(
2500
0
               deformationModelName()->c_str(),
2501
0
               otherDGRF->deformationModelName()->c_str());
2502
0
}
2503
//! @endcond
2504
2505
// ---------------------------------------------------------------------------
2506
2507
//! @cond Doxygen_Suppress
2508
void DynamicVerticalReferenceFrame::_exportToWKT(
2509
    io::WKTFormatter *formatter) const // throw(FormattingException)
2510
0
{
2511
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2512
0
    if (isWKT2 && formatter->use2019Keywords()) {
2513
0
        formatter->startNode(io::WKTConstants::DYNAMIC, false);
2514
0
        formatter->startNode(io::WKTConstants::FRAMEEPOCH, false);
2515
0
        formatter->add(
2516
0
            frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR));
2517
0
        formatter->endNode();
2518
0
        if (!deformationModelName()->empty()) {
2519
0
            formatter->startNode(io::WKTConstants::MODEL, false);
2520
0
            formatter->addQuotedString(*deformationModelName());
2521
0
            formatter->endNode();
2522
0
        }
2523
0
        formatter->endNode();
2524
0
    }
2525
0
    VerticalReferenceFrame::_exportToWKT(formatter);
2526
0
}
2527
//! @endcond
2528
2529
// ---------------------------------------------------------------------------
2530
2531
/** \brief Instantiate a DynamicVerticalReferenceFrame
2532
 *
2533
 * @param properties See \ref general_properties.
2534
 * At minimum the name should be defined.
2535
 * @param anchor the anchor definition, or empty.
2536
 * @param realizationMethodIn the realization method, or empty.
2537
 * @param frameReferenceEpochIn the frame reference epoch.
2538
 * @param deformationModelNameIn deformation model name, or empty
2539
 * @return new DynamicVerticalReferenceFrame.
2540
 */
2541
DynamicVerticalReferenceFrameNNPtr DynamicVerticalReferenceFrame::create(
2542
    const util::PropertyMap &properties,
2543
    const util::optional<std::string> &anchor,
2544
    const util::optional<RealizationMethod> &realizationMethodIn,
2545
    const common::Measure &frameReferenceEpochIn,
2546
6
    const util::optional<std::string> &deformationModelNameIn) {
2547
6
    DynamicVerticalReferenceFrameNNPtr grf(
2548
6
        DynamicVerticalReferenceFrame::nn_make_shared<
2549
6
            DynamicVerticalReferenceFrame>(realizationMethodIn,
2550
6
                                           frameReferenceEpochIn,
2551
6
                                           deformationModelNameIn));
2552
6
    grf->setAnchor(anchor);
2553
6
    grf->setProperties(properties);
2554
6
    return grf;
2555
6
}
2556
2557
// ---------------------------------------------------------------------------
2558
2559
//! @cond Doxygen_Suppress
2560
struct TemporalDatum::Private {
2561
    common::DateTime temporalOrigin_;
2562
    std::string calendar_;
2563
2564
    Private(const common::DateTime &temporalOriginIn,
2565
            const std::string &calendarIn)
2566
13
        : temporalOrigin_(temporalOriginIn), calendar_(calendarIn) {}
2567
};
2568
//! @endcond
2569
2570
// ---------------------------------------------------------------------------
2571
2572
TemporalDatum::TemporalDatum(const common::DateTime &temporalOriginIn,
2573
                             const std::string &calendarIn)
2574
13
    : d(std::make_unique<Private>(temporalOriginIn, calendarIn)) {}
2575
2576
// ---------------------------------------------------------------------------
2577
2578
//! @cond Doxygen_Suppress
2579
13
TemporalDatum::~TemporalDatum() = default;
2580
//! @endcond
2581
2582
// ---------------------------------------------------------------------------
2583
2584
/** \brief Return the date and time to which temporal coordinates are
2585
 * referenced, expressed in conformance with ISO 8601.
2586
 *
2587
 * @return the temporal origin.
2588
 */
2589
0
const common::DateTime &TemporalDatum::temporalOrigin() const {
2590
0
    return d->temporalOrigin_;
2591
0
}
2592
2593
// ---------------------------------------------------------------------------
2594
2595
/** \brief Return the calendar to which the temporal origin is referenced
2596
 *
2597
 * Default value: TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN.
2598
 *
2599
 * @return the calendar.
2600
 */
2601
0
const std::string &TemporalDatum::calendar() const { return d->calendar_; }
2602
2603
// ---------------------------------------------------------------------------
2604
2605
/** \brief Instantiate a TemporalDatum
2606
 *
2607
 * @param properties See \ref general_properties.
2608
 * At minimum the name should be defined.
2609
 * @param temporalOriginIn the temporal origin into which temporal coordinates
2610
 * are referenced.
2611
 * @param calendarIn the calendar (generally
2612
 * TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN)
2613
 * @return new TemporalDatum.
2614
 */
2615
TemporalDatumNNPtr
2616
TemporalDatum::create(const util::PropertyMap &properties,
2617
                      const common::DateTime &temporalOriginIn,
2618
13
                      const std::string &calendarIn) {
2619
13
    auto datum(TemporalDatum::nn_make_shared<TemporalDatum>(temporalOriginIn,
2620
13
                                                            calendarIn));
2621
13
    datum->setProperties(properties);
2622
13
    return datum;
2623
13
}
2624
2625
// ---------------------------------------------------------------------------
2626
2627
//! @cond Doxygen_Suppress
2628
void TemporalDatum::_exportToWKT(
2629
    io::WKTFormatter *formatter) const // throw(FormattingException)
2630
0
{
2631
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2632
0
    if (!isWKT2) {
2633
0
        throw io::FormattingException(
2634
0
            "TemporalDatum can only be exported to WKT2");
2635
0
    }
2636
0
    formatter->startNode(io::WKTConstants::TDATUM, !identifiers().empty());
2637
0
    formatter->addQuotedString(nameStr());
2638
0
    if (formatter->use2019Keywords()) {
2639
0
        formatter->startNode(io::WKTConstants::CALENDAR, false);
2640
0
        formatter->addQuotedString(calendar());
2641
0
        formatter->endNode();
2642
0
    }
2643
2644
0
    const auto &timeOriginStr = temporalOrigin().toString();
2645
0
    if (!timeOriginStr.empty()) {
2646
0
        formatter->startNode(io::WKTConstants::TIMEORIGIN, false);
2647
0
        if (temporalOrigin().isISO_8601()) {
2648
0
            formatter->add(timeOriginStr);
2649
0
        } else {
2650
0
            formatter->addQuotedString(timeOriginStr);
2651
0
        }
2652
0
        formatter->endNode();
2653
0
    }
2654
2655
0
    formatter->endNode();
2656
0
}
2657
//! @endcond
2658
2659
// ---------------------------------------------------------------------------
2660
2661
//! @cond Doxygen_Suppress
2662
void TemporalDatum::_exportToJSON(
2663
    io::JSONFormatter *formatter) const // throw(FormattingException)
2664
0
{
2665
0
    auto objectContext(
2666
0
        formatter->MakeObjectContext("TemporalDatum", !identifiers().empty()));
2667
0
    auto writer = formatter->writer();
2668
2669
0
    writer->AddObjKey("name");
2670
0
    writer->Add(nameStr());
2671
2672
0
    writer->AddObjKey("calendar");
2673
0
    writer->Add(calendar());
2674
2675
0
    const auto &timeOriginStr = temporalOrigin().toString();
2676
0
    if (!timeOriginStr.empty()) {
2677
0
        writer->AddObjKey("time_origin");
2678
0
        writer->Add(timeOriginStr);
2679
0
    }
2680
2681
0
    ObjectUsage::baseExportToJSON(formatter);
2682
0
}
2683
//! @endcond
2684
2685
// ---------------------------------------------------------------------------
2686
2687
//! @cond Doxygen_Suppress
2688
bool TemporalDatum::_isEquivalentTo(
2689
    const util::IComparable *other, util::IComparable::Criterion criterion,
2690
0
    const io::DatabaseContextPtr &dbContext) const {
2691
0
    auto otherTD = dynamic_cast<const TemporalDatum *>(other);
2692
0
    if (otherTD == nullptr ||
2693
0
        !Datum::_isEquivalentTo(other, criterion, dbContext)) {
2694
0
        return false;
2695
0
    }
2696
0
    return temporalOrigin().toString() ==
2697
0
               otherTD->temporalOrigin().toString() &&
2698
0
           calendar() == otherTD->calendar();
2699
0
}
2700
//! @endcond
2701
2702
// ---------------------------------------------------------------------------
2703
2704
//! @cond Doxygen_Suppress
2705
struct EngineeringDatum::Private {};
2706
//! @endcond
2707
2708
// ---------------------------------------------------------------------------
2709
2710
340
EngineeringDatum::EngineeringDatum() : d(nullptr) {}
2711
2712
// ---------------------------------------------------------------------------
2713
2714
//! @cond Doxygen_Suppress
2715
340
EngineeringDatum::~EngineeringDatum() = default;
2716
//! @endcond
2717
2718
// ---------------------------------------------------------------------------
2719
2720
/** \brief Instantiate a EngineeringDatum
2721
 *
2722
 * @param properties See \ref general_properties.
2723
 * At minimum the name should be defined.
2724
 * @param anchor the anchor definition, or empty.
2725
 * @return new EngineeringDatum.
2726
 */
2727
EngineeringDatumNNPtr
2728
EngineeringDatum::create(const util::PropertyMap &properties,
2729
340
                         const util::optional<std::string> &anchor) {
2730
340
    auto datum(EngineeringDatum::nn_make_shared<EngineeringDatum>());
2731
340
    datum->setAnchor(anchor);
2732
340
    datum->setProperties(properties);
2733
340
    return datum;
2734
340
}
2735
2736
// ---------------------------------------------------------------------------
2737
2738
//! @cond Doxygen_Suppress
2739
void EngineeringDatum::_exportToWKT(
2740
    io::WKTFormatter *formatter) const // throw(FormattingException)
2741
0
{
2742
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2743
0
    formatter->startNode(isWKT2 ? io::WKTConstants::EDATUM
2744
0
                                : io::WKTConstants::LOCAL_DATUM,
2745
0
                         !identifiers().empty());
2746
0
    formatter->addQuotedString(nameStr());
2747
0
    if (isWKT2) {
2748
0
        Datum::getPrivate()->exportAnchorDefinition(formatter);
2749
0
    } else {
2750
        // Somewhat picked up arbitrarily from OGC 01-009:
2751
        // CS_LD_Max (Attribute) : 32767
2752
        // Highest possible value for local datum types.
2753
0
        formatter->add(32767);
2754
0
    }
2755
0
    formatter->endNode();
2756
0
}
2757
//! @endcond
2758
2759
// ---------------------------------------------------------------------------
2760
2761
//! @cond Doxygen_Suppress
2762
void EngineeringDatum::_exportToJSON(
2763
    io::JSONFormatter *formatter) const // throw(FormattingException)
2764
0
{
2765
0
    auto objectContext(formatter->MakeObjectContext("EngineeringDatum",
2766
0
                                                    !identifiers().empty()));
2767
0
    auto writer = formatter->writer();
2768
2769
0
    writer->AddObjKey("name");
2770
0
    writer->Add(nameStr());
2771
2772
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
2773
2774
0
    ObjectUsage::baseExportToJSON(formatter);
2775
0
}
2776
//! @endcond
2777
2778
// ---------------------------------------------------------------------------
2779
2780
//! @cond Doxygen_Suppress
2781
bool EngineeringDatum::_isEquivalentTo(
2782
    const util::IComparable *other, util::IComparable::Criterion criterion,
2783
5
    const io::DatabaseContextPtr &dbContext) const {
2784
5
    auto otherDatum = dynamic_cast<const EngineeringDatum *>(other);
2785
5
    if (otherDatum == nullptr) {
2786
0
        return false;
2787
0
    }
2788
5
    if (criterion != util::IComparable::Criterion::STRICT &&
2789
5
        (nameStr().empty() || nameStr() == UNKNOWN_ENGINEERING_DATUM) &&
2790
5
        (otherDatum->nameStr().empty() ||
2791
5
         otherDatum->nameStr() == UNKNOWN_ENGINEERING_DATUM)) {
2792
5
        return true;
2793
5
    }
2794
0
    return Datum::_isEquivalentTo(other, criterion, dbContext);
2795
5
}
2796
//! @endcond
2797
2798
// ---------------------------------------------------------------------------
2799
2800
//! @cond Doxygen_Suppress
2801
struct ParametricDatum::Private {};
2802
//! @endcond
2803
2804
// ---------------------------------------------------------------------------
2805
2806
5
ParametricDatum::ParametricDatum() : d(nullptr) {}
2807
2808
// ---------------------------------------------------------------------------
2809
2810
//! @cond Doxygen_Suppress
2811
5
ParametricDatum::~ParametricDatum() = default;
2812
//! @endcond
2813
2814
// ---------------------------------------------------------------------------
2815
2816
/** \brief Instantiate a ParametricDatum
2817
 *
2818
 * @param properties See \ref general_properties.
2819
 * At minimum the name should be defined.
2820
 * @param anchor the anchor definition, or empty.
2821
 * @return new ParametricDatum.
2822
 */
2823
ParametricDatumNNPtr
2824
ParametricDatum::create(const util::PropertyMap &properties,
2825
5
                        const util::optional<std::string> &anchor) {
2826
5
    auto datum(ParametricDatum::nn_make_shared<ParametricDatum>());
2827
5
    datum->setAnchor(anchor);
2828
5
    datum->setProperties(properties);
2829
5
    return datum;
2830
5
}
2831
2832
// ---------------------------------------------------------------------------
2833
2834
//! @cond Doxygen_Suppress
2835
void ParametricDatum::_exportToWKT(
2836
    io::WKTFormatter *formatter) const // throw(FormattingException)
2837
0
{
2838
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2839
0
    if (!isWKT2) {
2840
0
        throw io::FormattingException(
2841
0
            "ParametricDatum can only be exported to WKT2");
2842
0
    }
2843
0
    formatter->startNode(io::WKTConstants::PDATUM, !identifiers().empty());
2844
0
    formatter->addQuotedString(nameStr());
2845
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
2846
0
    formatter->endNode();
2847
0
}
2848
//! @endcond
2849
2850
// ---------------------------------------------------------------------------
2851
2852
//! @cond Doxygen_Suppress
2853
void ParametricDatum::_exportToJSON(
2854
    io::JSONFormatter *formatter) const // throw(FormattingException)
2855
0
{
2856
0
    auto objectContext(formatter->MakeObjectContext("ParametricDatum",
2857
0
                                                    !identifiers().empty()));
2858
0
    auto writer = formatter->writer();
2859
2860
0
    writer->AddObjKey("name");
2861
0
    writer->Add(nameStr());
2862
2863
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
2864
2865
0
    ObjectUsage::baseExportToJSON(formatter);
2866
0
}
2867
//! @endcond
2868
2869
// ---------------------------------------------------------------------------
2870
2871
//! @cond Doxygen_Suppress
2872
bool ParametricDatum::_isEquivalentTo(
2873
    const util::IComparable *other, util::IComparable::Criterion criterion,
2874
0
    const io::DatabaseContextPtr &dbContext) const {
2875
0
    auto otherTD = dynamic_cast<const ParametricDatum *>(other);
2876
0
    if (otherTD == nullptr ||
2877
0
        !Datum::_isEquivalentTo(other, criterion, dbContext)) {
2878
0
        return false;
2879
0
    }
2880
0
    return true;
2881
0
}
2882
//! @endcond
2883
2884
} // namespace datum
2885
NS_PROJ_END