Coverage Report

Created: 2025-06-13 06:18

/src/proj/src/iso19111/datum.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#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
8
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
0
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
0
const util::optional<std::string> &Datum::anchorDefinition() const {
198
0
    return d->anchorDefinition;
199
0
}
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
0
const util::optional<common::DateTime> &Datum::publicationDate() const {
230
0
    return d->publicationDate;
231
0
}
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
0
const common::IdentifiedObjectPtr &Datum::conventionalRS() const {
245
0
    return d->conventionalRS;
246
0
}
247
248
// ---------------------------------------------------------------------------
249
250
8
void Datum::setAnchor(const util::optional<std::string> &anchor) {
251
8
    d->anchorDefinition = anchor;
252
8
}
253
254
// ---------------------------------------------------------------------------
255
256
0
void Datum::setAnchorEpoch(const util::optional<common::Measure> &anchorEpoch) {
257
0
    d->anchorEpoch =
258
0
        std::make_shared<util::optional<common::Measure>>(anchorEpoch);
259
0
}
260
261
// ---------------------------------------------------------------------------
262
263
void Datum::setProperties(
264
    const util::PropertyMap &properties) // throw(InvalidValueTypeException)
265
8
{
266
8
    std::string publicationDateResult;
267
8
    properties.getStringValue("PUBLICATION_DATE", publicationDateResult);
268
8
    if (!publicationDateResult.empty()) {
269
0
        d->publicationDate = common::DateTime::create(publicationDateResult);
270
0
    }
271
8
    std::string anchorEpoch;
272
8
    properties.getStringValue("ANCHOR_EPOCH", anchorEpoch);
273
8
    if (!anchorEpoch.empty()) {
274
0
        bool success = false;
275
0
        const double anchorEpochYear = c_locale_stod(anchorEpoch, success);
276
0
        if (success) {
277
0
            setAnchorEpoch(util::optional<common::Measure>(
278
0
                common::Measure(anchorEpochYear, common::UnitOfMeasure::YEAR)));
279
0
        }
280
0
    }
281
8
    ObjectUsage::setProperties(properties);
282
8
}
283
284
// ---------------------------------------------------------------------------
285
286
//! @cond Doxygen_Suppress
287
bool Datum::_isEquivalentTo(const util::IComparable *other,
288
                            util::IComparable::Criterion criterion,
289
0
                            const io::DatabaseContextPtr &dbContext) const {
290
0
    auto otherDatum = dynamic_cast<const Datum *>(other);
291
0
    if (otherDatum == nullptr ||
292
0
        !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) {
293
0
        return false;
294
0
    }
295
0
    if (criterion == util::IComparable::Criterion::STRICT) {
296
0
        if ((anchorDefinition().has_value() ^
297
0
             otherDatum->anchorDefinition().has_value())) {
298
0
            return false;
299
0
        }
300
0
        if (anchorDefinition().has_value() &&
301
0
            otherDatum->anchorDefinition().has_value() &&
302
0
            *anchorDefinition() != *otherDatum->anchorDefinition()) {
303
0
            return false;
304
0
        }
305
306
0
        if ((publicationDate().has_value() ^
307
0
             otherDatum->publicationDate().has_value())) {
308
0
            return false;
309
0
        }
310
0
        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
0
        if (((conventionalRS() != nullptr) ^
318
0
             (otherDatum->conventionalRS() != nullptr))) {
319
0
            return false;
320
0
        }
321
0
        if (conventionalRS() && otherDatum->conventionalRS() &&
322
0
            conventionalRS()->_isEquivalentTo(
323
0
                otherDatum->conventionalRS().get(), criterion, dbContext)) {
324
0
            return false;
325
0
        }
326
0
    }
327
0
    return true;
328
0
}
329
//! @endcond
330
331
// ---------------------------------------------------------------------------
332
333
//! @cond Doxygen_Suppress
334
struct PrimeMeridian::Private {
335
    common::Angle longitude_{};
336
337
6
    explicit Private(const common::Angle &longitude) : longitude_(longitude) {}
338
};
339
//! @endcond
340
341
// ---------------------------------------------------------------------------
342
343
PrimeMeridian::PrimeMeridian(const common::Angle &longitudeIn)
344
6
    : 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
0
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
0
const common::Angle &PrimeMeridian::longitude() PROJ_PURE_DEFN {
370
0
    return d->longitude_;
371
0
}
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
6
                                         const common::Angle &longitudeIn) {
384
6
    auto pm(PrimeMeridian::nn_make_shared<PrimeMeridian>(longitudeIn));
385
6
    pm->setProperties(properties);
386
6
    return pm;
387
6
}
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
0
PrimeMeridian::getPROJStringWellKnownName(const common::Angle &angle) {
515
0
    const double valRad = angle.getSIValue();
516
0
    std::string projPMName;
517
0
    PJ_CONTEXT *ctxt = proj_context_create();
518
0
    auto proj_pm = proj_list_prime_meridians();
519
0
    for (int i = 0; proj_pm[i].id != nullptr; ++i) {
520
0
        double valRefRad = dmstor_ctx(ctxt, proj_pm[i].defn, nullptr);
521
0
        if (::fabs(valRad - valRefRad) < 1e-10) {
522
0
            projPMName = proj_pm[i].id;
523
0
            break;
524
0
        }
525
0
    }
526
0
    proj_context_destroy(ctxt);
527
0
    return projPMName;
528
0
}
529
//! @endcond
530
531
// ---------------------------------------------------------------------------
532
533
//! @cond Doxygen_Suppress
534
void PrimeMeridian::_exportToPROJString(
535
    io::PROJStringFormatter *formatter) const // throw(FormattingException)
536
0
{
537
0
    if (longitude().getSIValue() != 0) {
538
0
        std::string projPMName(getPROJStringWellKnownName(longitude()));
539
0
        if (!projPMName.empty()) {
540
0
            formatter->addParam("pm", projPMName);
541
0
        } else {
542
0
            const double valDeg =
543
0
                longitude().convertToUnit(common::UnitOfMeasure::DEGREE);
544
0
            formatter->addParam("pm", valDeg);
545
0
        }
546
0
    }
547
0
}
548
//! @endcond
549
550
// ---------------------------------------------------------------------------
551
552
//! @cond Doxygen_Suppress
553
bool PrimeMeridian::_isEquivalentTo(
554
    const util::IComparable *other, util::IComparable::Criterion criterion,
555
0
    const io::DatabaseContextPtr &dbContext) const {
556
0
    auto otherPM = dynamic_cast<const PrimeMeridian *>(other);
557
0
    if (otherPM == nullptr ||
558
0
        !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
559
0
        return false;
560
0
    }
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
0
    return longitude()._isEquivalentTo(otherPM->longitude(), criterion, 1e-8);
565
0
}
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
0
        : semiMajorAxis_(radius), celestialBody_(celestialBody) {}
581
582
    Private(const common::Length &semiMajorAxisIn,
583
            const common::Scale &invFlattening,
584
            const std::string &celestialBody)
585
6
        : semiMajorAxis_(semiMajorAxisIn), inverseFlattening_(invFlattening),
586
6
          celestialBody_(celestialBody) {}
587
588
    Private(const common::Length &semiMajorAxisIn,
589
            const common::Length &semiMinorAxisIn,
590
            const std::string &celestialBody)
591
2
        : semiMajorAxis_(semiMajorAxisIn), semiMinorAxis_(semiMinorAxisIn),
592
2
          celestialBody_(celestialBody) {}
593
};
594
//! @endcond
595
596
// ---------------------------------------------------------------------------
597
598
Ellipsoid::Ellipsoid(const common::Length &radius,
599
                     const std::string &celestialBodyIn)
600
0
    : 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
6
    : d(std::make_unique<Private>(semiMajorAxisIn, invFlattening,
608
6
                                  celestialBodyIn)) {}
609
610
// ---------------------------------------------------------------------------
611
612
Ellipsoid::Ellipsoid(const common::Length &semiMajorAxisIn,
613
                     const common::Length &semiMinorAxisIn,
614
                     const std::string &celestialBodyIn)
615
2
    : d(std::make_unique<Private>(semiMajorAxisIn, semiMinorAxisIn,
616
2
                                  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
0
Ellipsoid::~Ellipsoid() = default;
629
630
Ellipsoid::Ellipsoid(const Ellipsoid &other)
631
0
    : 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
0
const common::Length &Ellipsoid::semiMajorAxis() PROJ_PURE_DEFN {
642
0
    return d->semiMajorAxis_;
643
0
}
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
0
Ellipsoid::inverseFlattening() PROJ_PURE_DEFN {
659
0
    return d->inverseFlattening_;
660
0
}
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
0
Ellipsoid::semiMinorAxis() PROJ_PURE_DEFN {
676
0
    return d->semiMinorAxis_;
677
0
}
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
0
bool Ellipsoid::isSphere() PROJ_PURE_DEFN {
691
0
    if (d->inverseFlattening_.has_value()) {
692
0
        return d->inverseFlattening_->value() == 0;
693
0
    }
694
695
0
    if (semiMinorAxis().has_value()) {
696
0
        return semiMajorAxis() == *semiMinorAxis();
697
0
    }
698
699
0
    return true;
700
0
}
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
0
Ellipsoid::semiMedianAxis() PROJ_PURE_DEFN {
712
0
    return d->semiMedianAxis_;
713
0
}
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
0
double Ellipsoid::computedInverseFlattening() PROJ_PURE_DEFN {
725
0
    if (d->inverseFlattening_.has_value()) {
726
0
        return d->inverseFlattening_->getSIValue();
727
0
    }
728
729
0
    if (d->semiMinorAxis_.has_value()) {
730
0
        const double a = d->semiMajorAxis_.getSIValue();
731
0
        const double b = d->semiMinorAxis_->getSIValue();
732
0
        return (a == b) ? 0.0 : a / (a - b);
733
0
    }
734
735
0
    return 0.0;
736
0
}
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
0
double Ellipsoid::squaredEccentricity() PROJ_PURE_DEFN {
745
0
    const double rf = computedInverseFlattening();
746
    // coverity[divide_by_zero]
747
0
    const double f = rf != 0.0 ? 1. / rf : 0.0;
748
0
    const double e2 = f * (2 - f);
749
0
    return e2;
750
0
}
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
0
common::Length Ellipsoid::computeSemiMinorAxis() const {
762
0
    if (d->semiMinorAxis_.has_value()) {
763
0
        return *d->semiMinorAxis_;
764
0
    }
765
766
0
    if (inverseFlattening().has_value()) {
767
0
        return common::Length(
768
0
            (1.0 - 1.0 / d->inverseFlattening_->getSIValue()) *
769
0
                d->semiMajorAxis_.value(),
770
0
            d->semiMajorAxis_.unit());
771
0
    }
772
773
0
    return d->semiMajorAxis_;
774
0
}
775
776
// ---------------------------------------------------------------------------
777
778
/** \brief Return the name of the celestial body on which the ellipsoid refers
779
 * to.
780
 */
781
0
const std::string &Ellipsoid::celestialBody() PROJ_PURE_DEFN {
782
0
    return d->celestialBody_;
783
0
}
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
0
                                       const std::string &celestialBody) {
799
0
    auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(radius, celestialBody));
800
0
    ellipsoid->setProperties(properties);
801
0
    return ellipsoid;
802
0
}
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
6
    const common::Scale &invFlattening, const std::string &celestialBody) {
820
6
    if (invFlattening.value() == 0) {
821
0
        auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(semiMajorAxisIn,
822
0
                                                            celestialBody));
823
0
        ellipsoid->setProperties(properties);
824
0
        return ellipsoid;
825
6
    } else {
826
6
        auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(
827
6
            semiMajorAxisIn, invFlattening, celestialBody));
828
6
        ellipsoid->setProperties(properties);
829
6
        return ellipsoid;
830
6
    }
831
6
}
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
2
                                        const std::string &celestialBody) {
849
2
    auto ellipsoid(Ellipsoid::nn_make_shared<Ellipsoid>(
850
2
        semiMajorAxisIn, semiMinorAxisIn, celestialBody));
851
2
    ellipsoid->setProperties(properties);
852
2
    return ellipsoid;
853
2
}
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
0
                                          std::string &ellpsName) const {
1010
0
    const double a = semiMajorAxis().getSIValue();
1011
0
    const double b = computeSemiMinorAxis().getSIValue();
1012
0
    const double rf = computedInverseFlattening();
1013
0
    auto proj_ellps = proj_list_ellps();
1014
0
    for (int i = 0; proj_ellps[i].id != nullptr; i++) {
1015
0
        assert(strncmp(proj_ellps[i].major, "a=", 2) == 0);
1016
0
        const double a_iter = c_locale_stod(proj_ellps[i].major + 2);
1017
0
        if (::fabs(a - a_iter) < 1e-10 * a_iter) {
1018
0
            if (strncmp(proj_ellps[i].ell, "b=", 2) == 0) {
1019
0
                const double b_iter = c_locale_stod(proj_ellps[i].ell + 2);
1020
0
                if (::fabs(b - b_iter) < 1e-10 * b_iter) {
1021
0
                    projEllpsName = proj_ellps[i].id;
1022
0
                    ellpsName = proj_ellps[i].name;
1023
0
                    if (starts_with(ellpsName, "GRS 1980")) {
1024
0
                        ellpsName = "GRS 1980";
1025
0
                    }
1026
0
                    return true;
1027
0
                }
1028
0
            } else {
1029
0
                assert(strncmp(proj_ellps[i].ell, "rf=", 3) == 0);
1030
0
                const double rf_iter = c_locale_stod(proj_ellps[i].ell + 3);
1031
0
                if (::fabs(rf - rf_iter) < 1e-10 * rf_iter) {
1032
0
                    projEllpsName = proj_ellps[i].id;
1033
0
                    ellpsName = proj_ellps[i].name;
1034
0
                    if (starts_with(ellpsName, "GRS 1980")) {
1035
0
                        ellpsName = "GRS 1980";
1036
0
                    }
1037
0
                    return true;
1038
0
                }
1039
0
            }
1040
0
        }
1041
0
    }
1042
0
    return false;
1043
0
}
1044
1045
// ---------------------------------------------------------------------------
1046
1047
//! @cond Doxygen_Suppress
1048
void Ellipsoid::_exportToPROJString(
1049
    io::PROJStringFormatter *formatter) const // throw(FormattingException)
1050
0
{
1051
0
    const double a = semiMajorAxis().getSIValue();
1052
1053
0
    std::string projEllpsName;
1054
0
    std::string ellpsName;
1055
0
    if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) {
1056
0
        formatter->addParam("ellps", projEllpsName);
1057
0
        return;
1058
0
    }
1059
1060
0
    if (isSphere()) {
1061
0
        formatter->addParam("R", a);
1062
0
    } else {
1063
0
        formatter->addParam("a", a);
1064
0
        if (inverseFlattening().has_value()) {
1065
0
            const double rf = computedInverseFlattening();
1066
0
            formatter->addParam("rf", rf);
1067
0
        } else {
1068
0
            const double b = computeSemiMinorAxis().getSIValue();
1069
0
            formatter->addParam("b", b);
1070
0
        }
1071
0
    }
1072
0
}
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
0
EllipsoidNNPtr Ellipsoid::identify() const {
1083
0
    auto newEllipsoid = Ellipsoid::nn_make_shared<Ellipsoid>(*this);
1084
0
    newEllipsoid->assignSelf(
1085
0
        util::nn_static_pointer_cast<util::BaseObject>(newEllipsoid));
1086
1087
0
    if (name()->description()->empty() || nameStr() == "unknown") {
1088
0
        std::string projEllpsName;
1089
0
        std::string ellpsName;
1090
0
        if (lookForProjWellKnownEllps(projEllpsName, ellpsName)) {
1091
0
            newEllipsoid->setProperties(
1092
0
                util::PropertyMap().set(IdentifiedObject::NAME_KEY, ellpsName));
1093
0
        }
1094
0
    }
1095
1096
0
    return newEllipsoid;
1097
0
}
1098
1099
// ---------------------------------------------------------------------------
1100
1101
//! @cond Doxygen_Suppress
1102
bool Ellipsoid::_isEquivalentTo(const util::IComparable *other,
1103
                                util::IComparable::Criterion criterion,
1104
0
                                const io::DatabaseContextPtr &dbContext) const {
1105
0
    auto otherEllipsoid = dynamic_cast<const Ellipsoid *>(other);
1106
0
    if (otherEllipsoid == nullptr ||
1107
0
        (criterion == util::IComparable::Criterion::STRICT &&
1108
0
         !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext))) {
1109
0
        return false;
1110
0
    }
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
0
    if (criterion != util::IComparable::Criterion::STRICT &&
1115
0
        (nameStr() == "Clarke 1880 mod." ||
1116
0
         otherEllipsoid->nameStr() == "Clarke 1880 mod.")) {
1117
0
        return std::fabs(semiMajorAxis().getSIValue() -
1118
0
                         otherEllipsoid->semiMajorAxis().getSIValue()) <
1119
0
                   1e-8 * semiMajorAxis().getSIValue() &&
1120
0
               std::fabs(computedInverseFlattening() -
1121
0
                         otherEllipsoid->computedInverseFlattening()) <
1122
0
                   1e-5 * computedInverseFlattening();
1123
0
    }
1124
1125
0
    if (!semiMajorAxis()._isEquivalentTo(otherEllipsoid->semiMajorAxis(),
1126
0
                                         criterion)) {
1127
0
        return false;
1128
0
    }
1129
1130
0
    const auto &l_semiMinorAxis = semiMinorAxis();
1131
0
    const auto &l_other_semiMinorAxis = otherEllipsoid->semiMinorAxis();
1132
0
    if (l_semiMinorAxis.has_value() && l_other_semiMinorAxis.has_value()) {
1133
0
        if (!l_semiMinorAxis->_isEquivalentTo(*l_other_semiMinorAxis,
1134
0
                                              criterion)) {
1135
0
            return false;
1136
0
        }
1137
0
    }
1138
1139
0
    const auto &l_inverseFlattening = inverseFlattening();
1140
0
    const auto &l_other_sinverseFlattening =
1141
0
        otherEllipsoid->inverseFlattening();
1142
0
    if (l_inverseFlattening.has_value() &&
1143
0
        l_other_sinverseFlattening.has_value()) {
1144
0
        if (!l_inverseFlattening->_isEquivalentTo(*l_other_sinverseFlattening,
1145
0
                                                  criterion)) {
1146
0
            return false;
1147
0
        }
1148
0
    }
1149
1150
0
    if (criterion == util::IComparable::Criterion::STRICT) {
1151
0
        if ((l_semiMinorAxis.has_value() ^ l_other_semiMinorAxis.has_value())) {
1152
0
            return false;
1153
0
        }
1154
1155
0
        if ((l_inverseFlattening.has_value() ^
1156
0
             l_other_sinverseFlattening.has_value())) {
1157
0
            return false;
1158
0
        }
1159
1160
0
    } else {
1161
0
        if (!computeSemiMinorAxis()._isEquivalentTo(
1162
0
                otherEllipsoid->computeSemiMinorAxis(), criterion)) {
1163
0
            return false;
1164
0
        }
1165
0
    }
1166
1167
0
    const auto &l_semiMedianAxis = semiMedianAxis();
1168
0
    const auto &l_other_semiMedianAxis = otherEllipsoid->semiMedianAxis();
1169
0
    if ((l_semiMedianAxis.has_value() ^ l_other_semiMedianAxis.has_value())) {
1170
0
        return false;
1171
0
    }
1172
0
    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
0
    return true;
1179
0
}
1180
//! @endcond
1181
1182
// ---------------------------------------------------------------------------
1183
1184
std::string Ellipsoid::guessBodyName(const io::DatabaseContextPtr &dbContext,
1185
0
                                     double a, const std::string &ellpsName) {
1186
0
    constexpr double earthMeanRadius = 6375000.0;
1187
0
    if (std::fabs(a - earthMeanRadius) <
1188
0
        REL_ERROR_FOR_SAME_CELESTIAL_BODY * earthMeanRadius) {
1189
0
        return Ellipsoid::EARTH;
1190
0
    }
1191
0
    if (dbContext) {
1192
0
        try {
1193
0
            auto factory = io::AuthorityFactory::create(NN_NO_CHECK(dbContext),
1194
0
                                                        std::string());
1195
0
            if (!ellpsName.empty()) {
1196
0
                auto matches = factory->createObjectsFromName(
1197
0
                    ellpsName, {io::AuthorityFactory::ObjectType::ELLIPSOID},
1198
0
                    true, 1);
1199
0
                if (!matches.empty()) {
1200
0
                    auto ellps =
1201
0
                        static_cast<const Ellipsoid *>(matches.front().get());
1202
0
                    if (std::fabs(a - ellps->semiMajorAxis().getSIValue()) <
1203
0
                        REL_ERROR_FOR_SAME_CELESTIAL_BODY * a) {
1204
0
                        return ellps->celestialBody();
1205
0
                    }
1206
0
                }
1207
0
            }
1208
0
            return factory->identifyBodyFromSemiMajorAxis(
1209
0
                a, REL_ERROR_FOR_SAME_CELESTIAL_BODY);
1210
0
        } catch (const std::exception &) {
1211
0
        }
1212
0
    }
1213
0
    return NON_EARTH_BODY;
1214
0
}
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
8
        : primeMeridian_(primeMeridianIn), ellipsoid_(ellipsoidIn) {}
1226
};
1227
//! @endcond
1228
1229
// ---------------------------------------------------------------------------
1230
1231
GeodeticReferenceFrame::GeodeticReferenceFrame(
1232
    const EllipsoidNNPtr &ellipsoidIn,
1233
    const PrimeMeridianNNPtr &primeMeridianIn)
1234
8
    : 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
0
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
0
GeodeticReferenceFrame::primeMeridian() PROJ_PURE_DEFN {
1258
0
    return d->primeMeridian_;
1259
0
}
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
0
const EllipsoidNNPtr &GeodeticReferenceFrame::ellipsoid() PROJ_PURE_DEFN {
1274
0
    return d->ellipsoid_;
1275
0
}
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
8
                               const PrimeMeridianNNPtr &primeMeridian) {
1293
8
    GeodeticReferenceFrameNNPtr grf(
1294
8
        GeodeticReferenceFrame::nn_make_shared<GeodeticReferenceFrame>(
1295
8
            ellipsoid, primeMeridian));
1296
8
    grf->setAnchor(anchor);
1297
8
    grf->setProperties(properties);
1298
8
    return grf;
1299
8
}
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
0
    const PrimeMeridianNNPtr &primeMeridian) {
1319
0
    GeodeticReferenceFrameNNPtr grf(
1320
0
        GeodeticReferenceFrame::nn_make_shared<GeodeticReferenceFrame>(
1321
0
            ellipsoid, primeMeridian));
1322
0
    grf->setAnchor(anchor);
1323
0
    grf->setAnchorEpoch(anchorEpoch);
1324
0
    grf->setProperties(properties);
1325
0
    return grf;
1326
0
}
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) {
1401
0
                    l_name = io::WKTFormatter::morphNameToESRI(l_name);
1402
0
                    if (!starts_with(l_name, "D_")) {
1403
0
                        l_name = "D_" + l_name;
1404
0
                    }
1405
0
                }
1406
0
            }
1407
0
        } else {
1408
            // Replace spaces by underscore for datum names coming from EPSG
1409
            // so as to emulate GDAL < 3 importFromEPSG()
1410
0
            if (ids.size() == 1 && *(ids.front()->codeSpace()) == "EPSG") {
1411
0
                l_name = io::WKTFormatter::morphNameToESRI(l_name);
1412
0
            } else if (ids.empty()) {
1413
0
                const auto &dbContext = formatter->databaseContext();
1414
0
                if (dbContext) {
1415
0
                    auto factory = io::AuthorityFactory::create(
1416
0
                        NN_NO_CHECK(dbContext), std::string());
1417
                    // We use anonymous authority and approximate matching, so
1418
                    // as to trigger the caching done in createObjectsFromName()
1419
                    // in that case.
1420
0
                    auto matches = factory->createObjectsFromName(
1421
0
                        l_name,
1422
0
                        {io::AuthorityFactory::ObjectType::
1423
0
                             GEODETIC_REFERENCE_FRAME},
1424
0
                        true, 2);
1425
0
                    if (matches.size() == 1) {
1426
0
                        const auto &match = matches.front();
1427
0
                        const auto &matchId = match->identifiers();
1428
0
                        if (matchId.size() == 1 &&
1429
0
                            *(matchId.front()->codeSpace()) == "EPSG" &&
1430
0
                            metadata::Identifier::isEquivalentName(
1431
0
                                l_name.c_str(), match->nameStr().c_str())) {
1432
0
                            l_name = io::WKTFormatter::morphNameToESRI(l_name);
1433
0
                        }
1434
0
                    }
1435
0
                }
1436
0
            }
1437
0
            if (l_name == "World_Geodetic_System_1984") {
1438
0
                l_name = "WGS_1984";
1439
0
            }
1440
0
        }
1441
0
    }
1442
0
    formatter->addQuotedString(l_name);
1443
1444
0
    ellipsoid()->_exportToWKT(formatter);
1445
0
    if (isWKT2) {
1446
0
        Datum::getPrivate()->exportAnchorDefinition(formatter);
1447
0
        if (formatter->use2019Keywords()) {
1448
0
            Datum::getPrivate()->exportAnchorEpoch(formatter);
1449
0
        }
1450
0
    } else {
1451
0
        const auto &TOWGS84Params = formatter->getTOWGS84Parameters();
1452
0
        if (TOWGS84Params.size() == 7) {
1453
0
            formatter->startNode(io::WKTConstants::TOWGS84, false);
1454
0
            for (const auto &val : TOWGS84Params) {
1455
0
                formatter->add(val, 12);
1456
0
            }
1457
0
            formatter->endNode();
1458
0
        }
1459
0
        std::string extension = formatter->getHDatumExtension();
1460
0
        if (!extension.empty()) {
1461
0
            formatter->startNode(io::WKTConstants::EXTENSION, false);
1462
0
            formatter->addQuotedString("PROJ4_GRIDS");
1463
0
            formatter->addQuotedString(extension);
1464
0
            formatter->endNode();
1465
0
        }
1466
0
    }
1467
0
    if (formatter->outputId()) {
1468
0
        formatID(formatter);
1469
0
    }
1470
    // the PRIMEM is exported as a child of the CRS
1471
0
    formatter->endNode();
1472
1473
0
    if (formatter->isAtTopLevel()) {
1474
0
        const auto &l_primeMeridian(primeMeridian());
1475
0
        if (l_primeMeridian->nameStr() != "Greenwich") {
1476
0
            l_primeMeridian->_exportToWKT(formatter);
1477
0
        }
1478
0
    }
1479
0
}
1480
//! @endcond
1481
1482
// ---------------------------------------------------------------------------
1483
1484
//! @cond Doxygen_Suppress
1485
void GeodeticReferenceFrame::_exportToJSON(
1486
    io::JSONFormatter *formatter) const // throw(FormattingException)
1487
0
{
1488
0
    auto dynamicGRF = dynamic_cast<const DynamicGeodeticReferenceFrame *>(this);
1489
1490
0
    auto objectContext(formatter->MakeObjectContext(
1491
0
        dynamicGRF ? "DynamicGeodeticReferenceFrame" : "GeodeticReferenceFrame",
1492
0
        !identifiers().empty()));
1493
0
    auto writer = formatter->writer();
1494
1495
0
    writer->AddObjKey("name");
1496
0
    const auto &l_name = nameStr();
1497
0
    if (l_name.empty()) {
1498
0
        writer->Add("unnamed");
1499
0
    } else {
1500
0
        writer->Add(l_name);
1501
0
    }
1502
1503
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
1504
0
    Datum::getPrivate()->exportAnchorEpoch(formatter);
1505
1506
0
    if (dynamicGRF) {
1507
0
        writer->AddObjKey("frame_reference_epoch");
1508
0
        writer->Add(dynamicGRF->frameReferenceEpoch().value());
1509
0
    }
1510
1511
0
    writer->AddObjKey("ellipsoid");
1512
0
    formatter->setOmitTypeInImmediateChild();
1513
0
    ellipsoid()->_exportToJSON(formatter);
1514
1515
0
    const auto &l_primeMeridian(primeMeridian());
1516
0
    if (l_primeMeridian->nameStr() != "Greenwich") {
1517
0
        writer->AddObjKey("prime_meridian");
1518
0
        formatter->setOmitTypeInImmediateChild();
1519
0
        primeMeridian()->_exportToJSON(formatter);
1520
0
    }
1521
1522
0
    ObjectUsage::baseExportToJSON(formatter);
1523
0
}
1524
//! @endcond
1525
1526
// ---------------------------------------------------------------------------
1527
1528
//! @cond Doxygen_Suppress
1529
1530
bool GeodeticReferenceFrame::isEquivalentToNoExactTypeCheck(
1531
    const util::IComparable *other, util::IComparable::Criterion criterion,
1532
0
    const io::DatabaseContextPtr &dbContext) const {
1533
0
    auto otherGRF = dynamic_cast<const GeodeticReferenceFrame *>(other);
1534
0
    if (otherGRF == nullptr ||
1535
0
        !Datum::_isEquivalentTo(other, criterion, dbContext)) {
1536
0
        return false;
1537
0
    }
1538
0
    return primeMeridian()->_isEquivalentTo(otherGRF->primeMeridian().get(),
1539
0
                                            criterion, dbContext) &&
1540
0
           ellipsoid()->_isEquivalentTo(otherGRF->ellipsoid().get(), criterion,
1541
0
                                        dbContext);
1542
0
}
1543
1544
// ---------------------------------------------------------------------------
1545
1546
bool GeodeticReferenceFrame::_isEquivalentTo(
1547
    const util::IComparable *other, util::IComparable::Criterion criterion,
1548
0
    const io::DatabaseContextPtr &dbContext) const {
1549
0
    if (criterion == Criterion::STRICT &&
1550
0
        !util::isOfExactType<GeodeticReferenceFrame>(*other)) {
1551
0
        return false;
1552
0
    }
1553
0
    return isEquivalentToNoExactTypeCheck(other, criterion, dbContext);
1554
0
}
1555
1556
//! @endcond
1557
1558
// ---------------------------------------------------------------------------
1559
1560
bool GeodeticReferenceFrame::hasEquivalentNameToUsingAlias(
1561
    const IdentifiedObject *other,
1562
0
    const io::DatabaseContextPtr &dbContext) const {
1563
1564
0
    const auto compare = [this, other,
1565
0
                          &dbContext](const std::string &thisName,
1566
0
                                      const std::string &otherName) {
1567
0
        if (thisName == otherName || thisName == "unknown" ||
1568
0
            otherName == "unknown") {
1569
0
            return true;
1570
0
        }
1571
1572
0
        if (ci_starts_with(thisName, UNKNOWN_BASED_ON) ||
1573
0
            ci_starts_with(otherName, UNKNOWN_BASED_ON)) {
1574
            // Note: they cannot be equal based on initial test.
1575
0
            return false;
1576
0
        }
1577
1578
0
        if (dbContext) {
1579
0
            if (!identifiers().empty()) {
1580
0
                const auto &id = identifiers().front();
1581
1582
0
                const std::string officialNameFromId = dbContext->getName(
1583
0
                    "geodetic_datum", *(id->codeSpace()), id->code());
1584
0
                const auto aliasesResult = dbContext->getAliases(
1585
0
                    *(id->codeSpace()), id->code(), thisName, "geodetic_datum",
1586
0
                    std::string());
1587
1588
0
                const auto isNameMatching =
1589
0
                    [&aliasesResult,
1590
0
                     &officialNameFromId](const std::string &name) {
1591
0
                        const char *nameCstr = name.c_str();
1592
0
                        if (metadata::Identifier::isEquivalentName(
1593
0
                                nameCstr, officialNameFromId.c_str())) {
1594
0
                            return true;
1595
0
                        } else {
1596
0
                            for (const auto &aliasResult : aliasesResult) {
1597
0
                                if (metadata::Identifier::isEquivalentName(
1598
0
                                        nameCstr, aliasResult.c_str())) {
1599
0
                                    return true;
1600
0
                                }
1601
0
                            }
1602
0
                        }
1603
0
                        return false;
1604
0
                    };
1605
1606
0
                return isNameMatching(thisName) && isNameMatching(otherName);
1607
0
            } else if (!other->identifiers().empty()) {
1608
0
                auto otherGRF =
1609
0
                    dynamic_cast<const GeodeticReferenceFrame *>(other);
1610
0
                if (otherGRF) {
1611
0
                    return otherGRF->hasEquivalentNameToUsingAlias(this,
1612
0
                                                                   dbContext);
1613
0
                }
1614
0
                return false;
1615
0
            }
1616
1617
0
            auto aliasesResult =
1618
0
                dbContext->getAliases(std::string(), std::string(), thisName,
1619
0
                                      "geodetic_datum", std::string());
1620
0
            const char *otherNamePtr = otherName.c_str();
1621
0
            for (const auto &aliasResult : aliasesResult) {
1622
0
                if (metadata::Identifier::isEquivalentName(
1623
0
                        otherNamePtr, aliasResult.c_str())) {
1624
0
                    return true;
1625
0
                }
1626
0
            }
1627
0
        }
1628
0
        return false;
1629
0
    };
1630
1631
    // Try to work around issues with Esri style "D_" name prefixing
1632
    // Cf https://github.com/OSGeo/PROJ/issues/4514
1633
0
    const bool thisStartsWithDUnderscore = ci_starts_with(nameStr(), "D_");
1634
0
    const bool otherStartsWithDUnderscore =
1635
0
        ci_starts_with(other->nameStr(), "D_");
1636
0
    if (thisStartsWithDUnderscore && !otherStartsWithDUnderscore) {
1637
0
        const std::string thisNameMod = nameStr().substr(2);
1638
0
        return metadata::Identifier::isEquivalentName(
1639
0
                   thisNameMod.c_str(), other->nameStr().c_str()) ||
1640
0
               compare(thisNameMod, other->nameStr());
1641
0
    } else if (!thisStartsWithDUnderscore && otherStartsWithDUnderscore) {
1642
0
        const std::string otherNameMod = other->nameStr().substr(2);
1643
0
        return metadata::Identifier::isEquivalentName(nameStr().c_str(),
1644
0
                                                      otherNameMod.c_str()) ||
1645
0
               compare(nameStr(), otherNameMod);
1646
0
    } else {
1647
0
        return compare(nameStr(), other->nameStr());
1648
0
    }
1649
0
}
1650
1651
// ---------------------------------------------------------------------------
1652
1653
//! @cond Doxygen_Suppress
1654
struct DynamicGeodeticReferenceFrame::Private {
1655
    common::Measure frameReferenceEpoch{};
1656
    util::optional<std::string> deformationModelName{};
1657
1658
    explicit Private(const common::Measure &frameReferenceEpochIn)
1659
0
        : frameReferenceEpoch(frameReferenceEpochIn) {}
1660
};
1661
//! @endcond
1662
1663
// ---------------------------------------------------------------------------
1664
1665
DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame(
1666
    const EllipsoidNNPtr &ellipsoidIn,
1667
    const PrimeMeridianNNPtr &primeMeridianIn,
1668
    const common::Measure &frameReferenceEpochIn,
1669
    const util::optional<std::string> &deformationModelNameIn)
1670
0
    : GeodeticReferenceFrame(ellipsoidIn, primeMeridianIn),
1671
0
      d(std::make_unique<Private>(frameReferenceEpochIn)) {
1672
0
    d->deformationModelName = deformationModelNameIn;
1673
0
}
1674
1675
// ---------------------------------------------------------------------------
1676
1677
#ifdef notdef
1678
DynamicGeodeticReferenceFrame::DynamicGeodeticReferenceFrame(
1679
    const DynamicGeodeticReferenceFrame &other)
1680
    : GeodeticReferenceFrame(other), d(std::make_unique<Private>(*other.d)) {}
1681
#endif
1682
1683
// ---------------------------------------------------------------------------
1684
1685
//! @cond Doxygen_Suppress
1686
0
DynamicGeodeticReferenceFrame::~DynamicGeodeticReferenceFrame() = default;
1687
//! @endcond
1688
1689
// ---------------------------------------------------------------------------
1690
1691
/** \brief Return the epoch to which the coordinates of stations defining the
1692
 * dynamic geodetic reference frame are referenced.
1693
 *
1694
 * Usually given as a decimal year e.g. 2016.47.
1695
 *
1696
 * @return the frame reference epoch.
1697
 */
1698
const common::Measure &
1699
0
DynamicGeodeticReferenceFrame::frameReferenceEpoch() const {
1700
0
    return d->frameReferenceEpoch;
1701
0
}
1702
1703
// ---------------------------------------------------------------------------
1704
1705
/** \brief Return the name of the deformation model.
1706
 *
1707
 * @note This is an extension to the \ref ISO_19111_2019 modeling, to
1708
 * hold the content of the DYNAMIC.MODEL WKT2 node.
1709
 *
1710
 * @return the name of the deformation model.
1711
 */
1712
const util::optional<std::string> &
1713
0
DynamicGeodeticReferenceFrame::deformationModelName() const {
1714
0
    return d->deformationModelName;
1715
0
}
1716
1717
// ---------------------------------------------------------------------------
1718
1719
//! @cond Doxygen_Suppress
1720
bool DynamicGeodeticReferenceFrame::_isEquivalentTo(
1721
    const util::IComparable *other, util::IComparable::Criterion criterion,
1722
0
    const io::DatabaseContextPtr &dbContext) const {
1723
0
    if (criterion == Criterion::STRICT &&
1724
0
        !util::isOfExactType<DynamicGeodeticReferenceFrame>(*other)) {
1725
0
        return false;
1726
0
    }
1727
0
    if (!GeodeticReferenceFrame::isEquivalentToNoExactTypeCheck(
1728
0
            other, criterion, dbContext)) {
1729
0
        return false;
1730
0
    }
1731
0
    auto otherDGRF = dynamic_cast<const DynamicGeodeticReferenceFrame *>(other);
1732
0
    if (otherDGRF == nullptr) {
1733
        // we can go here only if criterion != Criterion::STRICT, and thus
1734
        // given the above check we can consider the objects equivalent.
1735
0
        return true;
1736
0
    }
1737
0
    return frameReferenceEpoch()._isEquivalentTo(
1738
0
               otherDGRF->frameReferenceEpoch(), criterion) &&
1739
0
           metadata::Identifier::isEquivalentName(
1740
0
               deformationModelName()->c_str(),
1741
0
               otherDGRF->deformationModelName()->c_str());
1742
0
}
1743
//! @endcond
1744
1745
// ---------------------------------------------------------------------------
1746
1747
//! @cond Doxygen_Suppress
1748
void DynamicGeodeticReferenceFrame::_exportToWKT(
1749
    io::WKTFormatter *formatter) const // throw(FormattingException)
1750
0
{
1751
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
1752
0
    if (isWKT2 && formatter->use2019Keywords()) {
1753
0
        formatter->startNode(io::WKTConstants::DYNAMIC, false);
1754
0
        formatter->startNode(io::WKTConstants::FRAMEEPOCH, false);
1755
0
        formatter->add(
1756
0
            frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR));
1757
0
        formatter->endNode();
1758
0
        if (deformationModelName().has_value() &&
1759
0
            !deformationModelName()->empty()) {
1760
0
            formatter->startNode(io::WKTConstants::MODEL, false);
1761
0
            formatter->addQuotedString(*deformationModelName());
1762
0
            formatter->endNode();
1763
0
        }
1764
0
        formatter->endNode();
1765
0
    }
1766
0
    GeodeticReferenceFrame::_exportToWKT(formatter);
1767
0
}
1768
//! @endcond
1769
1770
// ---------------------------------------------------------------------------
1771
1772
/** \brief Instantiate a DynamicGeodeticReferenceFrame
1773
 *
1774
 * @param properties See \ref general_properties.
1775
 * At minimum the name should be defined.
1776
 * @param ellipsoid the Ellipsoid.
1777
 * @param anchor the anchor definition, or empty.
1778
 * @param primeMeridian the PrimeMeridian.
1779
 * @param frameReferenceEpochIn the frame reference epoch.
1780
 * @param deformationModelNameIn deformation model name, or empty
1781
 * @return new DynamicGeodeticReferenceFrame.
1782
 */
1783
DynamicGeodeticReferenceFrameNNPtr DynamicGeodeticReferenceFrame::create(
1784
    const util::PropertyMap &properties, const EllipsoidNNPtr &ellipsoid,
1785
    const util::optional<std::string> &anchor,
1786
    const PrimeMeridianNNPtr &primeMeridian,
1787
    const common::Measure &frameReferenceEpochIn,
1788
0
    const util::optional<std::string> &deformationModelNameIn) {
1789
0
    DynamicGeodeticReferenceFrameNNPtr grf(
1790
0
        DynamicGeodeticReferenceFrame::nn_make_shared<
1791
0
            DynamicGeodeticReferenceFrame>(ellipsoid, primeMeridian,
1792
0
                                           frameReferenceEpochIn,
1793
0
                                           deformationModelNameIn));
1794
0
    grf->setAnchor(anchor);
1795
0
    grf->setProperties(properties);
1796
0
    return grf;
1797
0
}
1798
1799
// ---------------------------------------------------------------------------
1800
1801
//! @cond Doxygen_Suppress
1802
struct DatumEnsemble::Private {
1803
    std::vector<DatumNNPtr> datums{};
1804
    metadata::PositionalAccuracyNNPtr positionalAccuracy;
1805
1806
    Private(const std::vector<DatumNNPtr> &datumsIn,
1807
            const metadata::PositionalAccuracyNNPtr &accuracy)
1808
0
        : datums(datumsIn), positionalAccuracy(accuracy) {}
1809
};
1810
//! @endcond
1811
1812
// ---------------------------------------------------------------------------
1813
1814
DatumEnsemble::DatumEnsemble(const std::vector<DatumNNPtr> &datumsIn,
1815
                             const metadata::PositionalAccuracyNNPtr &accuracy)
1816
0
    : d(std::make_unique<Private>(datumsIn, accuracy)) {}
1817
1818
// ---------------------------------------------------------------------------
1819
1820
#ifdef notdef
1821
DatumEnsemble::DatumEnsemble(const DatumEnsemble &other)
1822
    : common::ObjectUsage(other), d(std::make_unique<Private>(*other.d)) {}
1823
#endif
1824
1825
// ---------------------------------------------------------------------------
1826
1827
//! @cond Doxygen_Suppress
1828
0
DatumEnsemble::~DatumEnsemble() = default;
1829
//! @endcond
1830
1831
// ---------------------------------------------------------------------------
1832
1833
/** \brief Return the set of datums which may be considered to be
1834
 * insignificantly different from each other.
1835
 *
1836
 * @return the set of datums of the DatumEnsemble.
1837
 */
1838
0
const std::vector<DatumNNPtr> &DatumEnsemble::datums() const {
1839
0
    return d->datums;
1840
0
}
1841
1842
// ---------------------------------------------------------------------------
1843
1844
/** \brief Return the inaccuracy introduced through use of this collection of
1845
 * datums.
1846
 *
1847
 * It is an indication of the differences in coordinate values at all points
1848
 * between the various realizations that have been grouped into this datum
1849
 * ensemble.
1850
 *
1851
 * @return the accuracy.
1852
 */
1853
const metadata::PositionalAccuracyNNPtr &
1854
0
DatumEnsemble::positionalAccuracy() const {
1855
0
    return d->positionalAccuracy;
1856
0
}
1857
1858
// ---------------------------------------------------------------------------
1859
1860
//! @cond Doxygen_Suppress
1861
DatumNNPtr
1862
0
DatumEnsemble::asDatum(const io::DatabaseContextPtr &dbContext) const {
1863
1864
0
    const auto &l_datums = datums();
1865
0
    auto *grf = dynamic_cast<const GeodeticReferenceFrame *>(l_datums[0].get());
1866
1867
0
    const auto &l_identifiers = identifiers();
1868
0
    if (dbContext) {
1869
0
        if (!l_identifiers.empty()) {
1870
0
            const auto &id = l_identifiers[0];
1871
0
            try {
1872
0
                auto factory = io::AuthorityFactory::create(
1873
0
                    NN_NO_CHECK(dbContext), *(id->codeSpace()));
1874
0
                if (grf) {
1875
0
                    return factory->createGeodeticDatum(id->code());
1876
0
                } else {
1877
0
                    return factory->createVerticalDatum(id->code());
1878
0
                }
1879
0
            } catch (const std::exception &) {
1880
0
            }
1881
0
        }
1882
0
    }
1883
1884
0
    std::string l_name(nameStr());
1885
0
    if (grf) {
1886
        // Remap to traditional datum names
1887
0
        if (l_name == "World Geodetic System 1984 ensemble") {
1888
0
            l_name = "World Geodetic System 1984";
1889
0
        } else if (l_name ==
1890
0
                   "European Terrestrial Reference System 1989 ensemble") {
1891
0
            l_name = "European Terrestrial Reference System 1989";
1892
0
        }
1893
0
    }
1894
0
    auto props =
1895
0
        util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, l_name);
1896
0
    if (isDeprecated()) {
1897
0
        props.set(common::IdentifiedObject::DEPRECATED_KEY, true);
1898
0
    }
1899
0
    if (!l_identifiers.empty()) {
1900
0
        const auto &id = l_identifiers[0];
1901
0
        props.set(metadata::Identifier::CODESPACE_KEY, *(id->codeSpace()))
1902
0
            .set(metadata::Identifier::CODE_KEY, id->code());
1903
0
    }
1904
0
    const auto &l_usages = domains();
1905
0
    if (!l_usages.empty()) {
1906
1907
0
        auto array(util::ArrayOfBaseObject::create());
1908
0
        for (const auto &usage : l_usages) {
1909
0
            array->add(usage);
1910
0
        }
1911
0
        props.set(common::ObjectUsage::OBJECT_DOMAIN_KEY,
1912
0
                  util::nn_static_pointer_cast<util::BaseObject>(array));
1913
0
    }
1914
0
    const auto anchor = util::optional<std::string>();
1915
1916
0
    if (grf) {
1917
0
        return GeodeticReferenceFrame::create(props, grf->ellipsoid(), anchor,
1918
0
                                              grf->primeMeridian());
1919
0
    } else {
1920
0
        assert(dynamic_cast<VerticalReferenceFrame *>(l_datums[0].get()));
1921
0
        return datum::VerticalReferenceFrame::create(props, anchor);
1922
0
    }
1923
0
}
1924
//! @endcond
1925
1926
// ---------------------------------------------------------------------------
1927
1928
//! @cond Doxygen_Suppress
1929
void DatumEnsemble::_exportToWKT(
1930
    io::WKTFormatter *formatter) const // throw(FormattingException)
1931
0
{
1932
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
1933
0
    if (!isWKT2 || !formatter->use2019Keywords()) {
1934
0
        return asDatum(formatter->databaseContext())->_exportToWKT(formatter);
1935
0
    }
1936
1937
0
    const auto &l_datums = datums();
1938
0
    assert(!l_datums.empty());
1939
1940
0
    formatter->startNode(io::WKTConstants::ENSEMBLE, false);
1941
0
    const auto &l_name = nameStr();
1942
0
    if (!l_name.empty()) {
1943
0
        formatter->addQuotedString(l_name);
1944
0
    } else {
1945
0
        formatter->addQuotedString("unnamed");
1946
0
    }
1947
1948
0
    for (const auto &datum : l_datums) {
1949
0
        formatter->startNode(io::WKTConstants::MEMBER,
1950
0
                             !datum->identifiers().empty());
1951
0
        const auto &l_datum_name = datum->nameStr();
1952
0
        if (!l_datum_name.empty()) {
1953
0
            formatter->addQuotedString(l_datum_name);
1954
0
        } else {
1955
0
            formatter->addQuotedString("unnamed");
1956
0
        }
1957
0
        if (formatter->outputId()) {
1958
0
            datum->formatID(formatter);
1959
0
        }
1960
0
        formatter->endNode();
1961
0
    }
1962
1963
0
    auto grfFirst = std::dynamic_pointer_cast<GeodeticReferenceFrame>(
1964
0
        l_datums[0].as_nullable());
1965
0
    if (grfFirst) {
1966
0
        grfFirst->ellipsoid()->_exportToWKT(formatter);
1967
0
    }
1968
1969
0
    formatter->startNode(io::WKTConstants::ENSEMBLEACCURACY, false);
1970
0
    formatter->add(positionalAccuracy()->value());
1971
0
    formatter->endNode();
1972
1973
    // In theory, we should do the following, but currently the WKT grammar
1974
    // doesn't allow this
1975
    // ObjectUsage::baseExportToWKT(formatter);
1976
0
    if (formatter->outputId()) {
1977
0
        formatID(formatter);
1978
0
    }
1979
1980
0
    formatter->endNode();
1981
0
}
1982
//! @endcond
1983
1984
// ---------------------------------------------------------------------------
1985
1986
//! @cond Doxygen_Suppress
1987
void DatumEnsemble::_exportToJSON(
1988
    io::JSONFormatter *formatter) const // throw(FormattingException)
1989
0
{
1990
0
    auto objectContext(
1991
0
        formatter->MakeObjectContext("DatumEnsemble", !identifiers().empty()));
1992
0
    auto writer = formatter->writer();
1993
1994
0
    writer->AddObjKey("name");
1995
0
    const auto &l_name = nameStr();
1996
0
    if (l_name.empty()) {
1997
0
        writer->Add("unnamed");
1998
0
    } else {
1999
0
        writer->Add(l_name);
2000
0
    }
2001
2002
0
    const auto &l_datums = datums();
2003
0
    writer->AddObjKey("members");
2004
0
    {
2005
0
        auto membersContext(writer->MakeArrayContext(false));
2006
0
        for (const auto &datum : l_datums) {
2007
0
            auto memberContext(writer->MakeObjectContext());
2008
0
            writer->AddObjKey("name");
2009
0
            const auto &l_datum_name = datum->nameStr();
2010
0
            if (!l_datum_name.empty()) {
2011
0
                writer->Add(l_datum_name);
2012
0
            } else {
2013
0
                writer->Add("unnamed");
2014
0
            }
2015
0
            datum->formatID(formatter);
2016
0
        }
2017
0
    }
2018
2019
0
    auto grfFirst = std::dynamic_pointer_cast<GeodeticReferenceFrame>(
2020
0
        l_datums[0].as_nullable());
2021
0
    if (grfFirst) {
2022
0
        writer->AddObjKey("ellipsoid");
2023
0
        formatter->setOmitTypeInImmediateChild();
2024
0
        grfFirst->ellipsoid()->_exportToJSON(formatter);
2025
0
    }
2026
2027
0
    writer->AddObjKey("accuracy");
2028
0
    writer->Add(positionalAccuracy()->value());
2029
2030
0
    formatID(formatter);
2031
0
}
2032
//! @endcond
2033
2034
// ---------------------------------------------------------------------------
2035
2036
/** \brief Instantiate a DatumEnsemble.
2037
 *
2038
 * @param properties See \ref general_properties.
2039
 * At minimum the name should be defined.
2040
 * @param datumsIn Array of at least 2 datums.
2041
 * @param accuracy Accuracy of the datum ensemble
2042
 * @return new DatumEnsemble.
2043
 * @throw util::Exception in case of error.
2044
 */
2045
DatumEnsembleNNPtr DatumEnsemble::create(
2046
    const util::PropertyMap &properties,
2047
    const std::vector<DatumNNPtr> &datumsIn,
2048
    const metadata::PositionalAccuracyNNPtr &accuracy) // throw(Exception)
2049
0
{
2050
0
    if (datumsIn.size() < 2) {
2051
0
        throw util::Exception("ensemble should have at least 2 datums");
2052
0
    }
2053
0
    if (auto grfFirst =
2054
0
            dynamic_cast<const GeodeticReferenceFrame *>(datumsIn[0].get())) {
2055
0
        for (size_t i = 1; i < datumsIn.size(); i++) {
2056
0
            auto grf =
2057
0
                dynamic_cast<const GeodeticReferenceFrame *>(datumsIn[i].get());
2058
0
            if (!grf) {
2059
0
                throw util::Exception(
2060
0
                    "ensemble should have consistent datum types");
2061
0
            }
2062
0
            if (!grfFirst->ellipsoid()->_isEquivalentTo(
2063
0
                    grf->ellipsoid().get())) {
2064
0
                throw util::Exception(
2065
0
                    "ensemble should have datums with identical ellipsoid");
2066
0
            }
2067
0
            if (!grfFirst->primeMeridian()->_isEquivalentTo(
2068
0
                    grf->primeMeridian().get())) {
2069
0
                throw util::Exception(
2070
0
                    "ensemble should have datums with identical "
2071
0
                    "prime meridian");
2072
0
            }
2073
0
        }
2074
0
    } else if (dynamic_cast<VerticalReferenceFrame *>(datumsIn[0].get())) {
2075
0
        for (size_t i = 1; i < datumsIn.size(); i++) {
2076
0
            if (!dynamic_cast<VerticalReferenceFrame *>(datumsIn[i].get())) {
2077
0
                throw util::Exception(
2078
0
                    "ensemble should have consistent datum types");
2079
0
            }
2080
0
        }
2081
0
    }
2082
0
    auto ensemble(
2083
0
        DatumEnsemble::nn_make_shared<DatumEnsemble>(datumsIn, accuracy));
2084
0
    ensemble->setProperties(properties);
2085
0
    return ensemble;
2086
0
}
2087
2088
// ---------------------------------------------------------------------------
2089
2090
RealizationMethod::RealizationMethod(const std::string &nameIn)
2091
6
    : CodeList(nameIn) {}
2092
2093
// ---------------------------------------------------------------------------
2094
2095
RealizationMethod &
2096
0
RealizationMethod::operator=(const RealizationMethod &other) {
2097
0
    CodeList::operator=(other);
2098
0
    return *this;
2099
0
}
2100
2101
// ---------------------------------------------------------------------------
2102
2103
//! @cond Doxygen_Suppress
2104
struct VerticalReferenceFrame::Private {
2105
    util::optional<RealizationMethod> realizationMethod_{};
2106
2107
    // 2005 = CS_VD_GeoidModelDerived from OGC 01-009
2108
    std::string wkt1DatumType_{"2005"};
2109
};
2110
//! @endcond
2111
2112
// ---------------------------------------------------------------------------
2113
2114
VerticalReferenceFrame::VerticalReferenceFrame(
2115
    const util::optional<RealizationMethod> &realizationMethodIn)
2116
0
    : d(std::make_unique<Private>()) {
2117
0
    if (!realizationMethodIn->toString().empty()) {
2118
0
        d->realizationMethod_ = *realizationMethodIn;
2119
0
    }
2120
0
}
2121
2122
// ---------------------------------------------------------------------------
2123
2124
//! @cond Doxygen_Suppress
2125
0
VerticalReferenceFrame::~VerticalReferenceFrame() = default;
2126
//! @endcond
2127
2128
// ---------------------------------------------------------------------------
2129
2130
/** \brief Return the method through which this vertical reference frame is
2131
 * realized.
2132
 *
2133
 * @return the realization method.
2134
 */
2135
const util::optional<RealizationMethod> &
2136
0
VerticalReferenceFrame::realizationMethod() const {
2137
0
    return d->realizationMethod_;
2138
0
}
2139
2140
// ---------------------------------------------------------------------------
2141
2142
/** \brief Instantiate a VerticalReferenceFrame
2143
 *
2144
 * @param properties See \ref general_properties.
2145
 * At minimum the name should be defined.
2146
 * @param anchor the anchor definition, or empty.
2147
 * @param realizationMethodIn the realization method, or empty.
2148
 * @return new VerticalReferenceFrame.
2149
 */
2150
VerticalReferenceFrameNNPtr VerticalReferenceFrame::create(
2151
    const util::PropertyMap &properties,
2152
    const util::optional<std::string> &anchor,
2153
0
    const util::optional<RealizationMethod> &realizationMethodIn) {
2154
0
    auto rf(VerticalReferenceFrame::nn_make_shared<VerticalReferenceFrame>(
2155
0
        realizationMethodIn));
2156
0
    rf->setAnchor(anchor);
2157
0
    rf->setProperties(properties);
2158
0
    properties.getStringValue("VERT_DATUM_TYPE", rf->d->wkt1DatumType_);
2159
0
    return rf;
2160
0
}
2161
2162
// ---------------------------------------------------------------------------
2163
2164
/** \brief Instantiate a VerticalReferenceFrame
2165
 *
2166
 * @param properties See \ref general_properties.
2167
 * At minimum the name should be defined.
2168
 * @param anchor the anchor definition, or empty.
2169
 * @param anchorEpoch the anchor epoch, or empty.
2170
 * @param realizationMethodIn the realization method, or empty.
2171
 * @return new VerticalReferenceFrame.
2172
 * @since 9.2
2173
 */
2174
VerticalReferenceFrameNNPtr VerticalReferenceFrame::create(
2175
    const util::PropertyMap &properties,
2176
    const util::optional<std::string> &anchor,
2177
    const util::optional<common::Measure> &anchorEpoch,
2178
0
    const util::optional<RealizationMethod> &realizationMethodIn) {
2179
0
    auto rf(VerticalReferenceFrame::nn_make_shared<VerticalReferenceFrame>(
2180
0
        realizationMethodIn));
2181
0
    rf->setAnchor(anchor);
2182
0
    rf->setAnchorEpoch(anchorEpoch);
2183
0
    rf->setProperties(properties);
2184
0
    properties.getStringValue("VERT_DATUM_TYPE", rf->d->wkt1DatumType_);
2185
0
    return rf;
2186
0
}
2187
2188
// ---------------------------------------------------------------------------
2189
2190
//! @cond Doxygen_Suppress
2191
0
const std::string &VerticalReferenceFrame::getWKT1DatumType() const {
2192
0
    return d->wkt1DatumType_;
2193
0
}
2194
//! @endcond
2195
2196
// ---------------------------------------------------------------------------
2197
2198
//! @cond Doxygen_Suppress
2199
void VerticalReferenceFrame::_exportToWKT(
2200
    io::WKTFormatter *formatter) const // throw(FormattingException)
2201
0
{
2202
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2203
0
    formatter->startNode(isWKT2 ? io::WKTConstants::VDATUM
2204
0
                         : formatter->useESRIDialect()
2205
0
                             ? io::WKTConstants::VDATUM
2206
0
                             : io::WKTConstants::VERT_DATUM,
2207
0
                         !identifiers().empty());
2208
0
    std::string l_name(nameStr());
2209
0
    if (!l_name.empty()) {
2210
0
        if (!isWKT2 && formatter->useESRIDialect()) {
2211
0
            bool aliasFound = false;
2212
0
            const auto &dbContext = formatter->databaseContext();
2213
0
            if (dbContext) {
2214
0
                auto l_alias = dbContext->getAliasFromOfficialName(
2215
0
                    l_name, "vertical_datum", "ESRI");
2216
0
                if (!l_alias.empty()) {
2217
0
                    l_name = std::move(l_alias);
2218
0
                    aliasFound = true;
2219
0
                }
2220
0
            }
2221
0
            if (!aliasFound && dbContext) {
2222
0
                auto authFactory = io::AuthorityFactory::create(
2223
0
                    NN_NO_CHECK(dbContext), "ESRI");
2224
0
                aliasFound = authFactory
2225
0
                                 ->createObjectsFromName(
2226
0
                                     l_name,
2227
0
                                     {io::AuthorityFactory::ObjectType::
2228
0
                                          VERTICAL_REFERENCE_FRAME},
2229
0
                                     false // approximateMatch
2230
0
                                     )
2231
0
                                 .size() == 1;
2232
0
            }
2233
0
            if (!aliasFound) {
2234
0
                l_name = io::WKTFormatter::morphNameToESRI(l_name);
2235
0
            }
2236
0
        }
2237
0
        formatter->addQuotedString(l_name);
2238
0
    } else {
2239
0
        formatter->addQuotedString("unnamed");
2240
0
    }
2241
0
    if (isWKT2) {
2242
0
        Datum::getPrivate()->exportAnchorDefinition(formatter);
2243
0
        if (formatter->use2019Keywords()) {
2244
0
            Datum::getPrivate()->exportAnchorEpoch(formatter);
2245
0
        }
2246
0
    } else if (!formatter->useESRIDialect()) {
2247
0
        formatter->add(d->wkt1DatumType_);
2248
0
        const auto &extension = formatter->getVDatumExtension();
2249
0
        if (!extension.empty()) {
2250
0
            formatter->startNode(io::WKTConstants::EXTENSION, false);
2251
0
            formatter->addQuotedString("PROJ4_GRIDS");
2252
0
            formatter->addQuotedString(extension);
2253
0
            formatter->endNode();
2254
0
        }
2255
0
    }
2256
0
    if (formatter->outputId()) {
2257
0
        formatID(formatter);
2258
0
    }
2259
0
    formatter->endNode();
2260
0
}
2261
//! @endcond
2262
2263
// ---------------------------------------------------------------------------
2264
2265
//! @cond Doxygen_Suppress
2266
void VerticalReferenceFrame::_exportToJSON(
2267
    io::JSONFormatter *formatter) const // throw(FormattingException)
2268
0
{
2269
0
    auto dynamicGRF = dynamic_cast<const DynamicVerticalReferenceFrame *>(this);
2270
2271
0
    auto objectContext(formatter->MakeObjectContext(
2272
0
        dynamicGRF ? "DynamicVerticalReferenceFrame" : "VerticalReferenceFrame",
2273
0
        !identifiers().empty()));
2274
0
    auto writer = formatter->writer();
2275
2276
0
    writer->AddObjKey("name");
2277
0
    const auto &l_name = nameStr();
2278
0
    if (l_name.empty()) {
2279
0
        writer->Add("unnamed");
2280
0
    } else {
2281
0
        writer->Add(l_name);
2282
0
    }
2283
2284
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
2285
0
    Datum::getPrivate()->exportAnchorEpoch(formatter);
2286
2287
0
    if (dynamicGRF) {
2288
0
        writer->AddObjKey("frame_reference_epoch");
2289
0
        writer->Add(dynamicGRF->frameReferenceEpoch().value());
2290
0
    }
2291
2292
0
    ObjectUsage::baseExportToJSON(formatter);
2293
0
}
2294
//! @endcond
2295
2296
// ---------------------------------------------------------------------------
2297
2298
//! @cond Doxygen_Suppress
2299
bool VerticalReferenceFrame::isEquivalentToNoExactTypeCheck(
2300
    const util::IComparable *other, util::IComparable::Criterion criterion,
2301
0
    const io::DatabaseContextPtr &dbContext) const {
2302
0
    auto otherVRF = dynamic_cast<const VerticalReferenceFrame *>(other);
2303
0
    if (otherVRF == nullptr ||
2304
0
        !Datum::_isEquivalentTo(other, criterion, dbContext)) {
2305
0
        return false;
2306
0
    }
2307
0
    if ((realizationMethod().has_value() ^
2308
0
         otherVRF->realizationMethod().has_value())) {
2309
0
        return false;
2310
0
    }
2311
0
    if (realizationMethod().has_value() &&
2312
0
        otherVRF->realizationMethod().has_value()) {
2313
0
        if (*(realizationMethod()) != *(otherVRF->realizationMethod())) {
2314
0
            return false;
2315
0
        }
2316
0
    }
2317
0
    return true;
2318
0
}
2319
2320
// ---------------------------------------------------------------------------
2321
2322
bool VerticalReferenceFrame::_isEquivalentTo(
2323
    const util::IComparable *other, util::IComparable::Criterion criterion,
2324
0
    const io::DatabaseContextPtr &dbContext) const {
2325
0
    if (criterion == Criterion::STRICT &&
2326
0
        !util::isOfExactType<VerticalReferenceFrame>(*other)) {
2327
0
        return false;
2328
0
    }
2329
0
    return isEquivalentToNoExactTypeCheck(other, criterion, dbContext);
2330
0
}
2331
2332
//! @endcond
2333
2334
// ---------------------------------------------------------------------------
2335
2336
//! @cond Doxygen_Suppress
2337
struct DynamicVerticalReferenceFrame::Private {
2338
    common::Measure frameReferenceEpoch{};
2339
    util::optional<std::string> deformationModelName{};
2340
2341
    explicit Private(const common::Measure &frameReferenceEpochIn)
2342
0
        : frameReferenceEpoch(frameReferenceEpochIn) {}
2343
};
2344
//! @endcond
2345
2346
// ---------------------------------------------------------------------------
2347
2348
DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame(
2349
    const util::optional<RealizationMethod> &realizationMethodIn,
2350
    const common::Measure &frameReferenceEpochIn,
2351
    const util::optional<std::string> &deformationModelNameIn)
2352
0
    : VerticalReferenceFrame(realizationMethodIn),
2353
0
      d(std::make_unique<Private>(frameReferenceEpochIn)) {
2354
0
    d->deformationModelName = deformationModelNameIn;
2355
0
}
2356
2357
// ---------------------------------------------------------------------------
2358
2359
#ifdef notdef
2360
DynamicVerticalReferenceFrame::DynamicVerticalReferenceFrame(
2361
    const DynamicVerticalReferenceFrame &other)
2362
    : VerticalReferenceFrame(other), d(std::make_unique<Private>(*other.d)) {}
2363
#endif
2364
2365
// ---------------------------------------------------------------------------
2366
2367
//! @cond Doxygen_Suppress
2368
0
DynamicVerticalReferenceFrame::~DynamicVerticalReferenceFrame() = default;
2369
//! @endcond
2370
2371
// ---------------------------------------------------------------------------
2372
2373
/** \brief Return the epoch to which the coordinates of stations defining the
2374
 * dynamic geodetic reference frame are referenced.
2375
 *
2376
 * Usually given as a decimal year e.g. 2016.47.
2377
 *
2378
 * @return the frame reference epoch.
2379
 */
2380
const common::Measure &
2381
0
DynamicVerticalReferenceFrame::frameReferenceEpoch() const {
2382
0
    return d->frameReferenceEpoch;
2383
0
}
2384
2385
// ---------------------------------------------------------------------------
2386
2387
/** \brief Return the name of the deformation model.
2388
 *
2389
 * @note This is an extension to the \ref ISO_19111_2019 modeling, to
2390
 * hold the content of the DYNAMIC.MODEL WKT2 node.
2391
 *
2392
 * @return the name of the deformation model.
2393
 */
2394
const util::optional<std::string> &
2395
0
DynamicVerticalReferenceFrame::deformationModelName() const {
2396
0
    return d->deformationModelName;
2397
0
}
2398
2399
// ---------------------------------------------------------------------------
2400
2401
//! @cond Doxygen_Suppress
2402
bool DynamicVerticalReferenceFrame::_isEquivalentTo(
2403
    const util::IComparable *other, util::IComparable::Criterion criterion,
2404
0
    const io::DatabaseContextPtr &dbContext) const {
2405
0
    if (criterion == Criterion::STRICT &&
2406
0
        !util::isOfExactType<DynamicVerticalReferenceFrame>(*other)) {
2407
0
        return false;
2408
0
    }
2409
0
    if (!VerticalReferenceFrame::isEquivalentToNoExactTypeCheck(
2410
0
            other, criterion, dbContext)) {
2411
0
        return false;
2412
0
    }
2413
0
    auto otherDGRF = dynamic_cast<const DynamicVerticalReferenceFrame *>(other);
2414
0
    if (otherDGRF == nullptr) {
2415
        // we can go here only if criterion != Criterion::STRICT, and thus
2416
        // given the above check we can consider the objects equivalent.
2417
0
        return true;
2418
0
    }
2419
0
    return frameReferenceEpoch()._isEquivalentTo(
2420
0
               otherDGRF->frameReferenceEpoch(), criterion) &&
2421
0
           metadata::Identifier::isEquivalentName(
2422
0
               deformationModelName()->c_str(),
2423
0
               otherDGRF->deformationModelName()->c_str());
2424
0
}
2425
//! @endcond
2426
2427
// ---------------------------------------------------------------------------
2428
2429
//! @cond Doxygen_Suppress
2430
void DynamicVerticalReferenceFrame::_exportToWKT(
2431
    io::WKTFormatter *formatter) const // throw(FormattingException)
2432
0
{
2433
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2434
0
    if (isWKT2 && formatter->use2019Keywords()) {
2435
0
        formatter->startNode(io::WKTConstants::DYNAMIC, false);
2436
0
        formatter->startNode(io::WKTConstants::FRAMEEPOCH, false);
2437
0
        formatter->add(
2438
0
            frameReferenceEpoch().convertToUnit(common::UnitOfMeasure::YEAR));
2439
0
        formatter->endNode();
2440
0
        if (!deformationModelName()->empty()) {
2441
0
            formatter->startNode(io::WKTConstants::MODEL, false);
2442
0
            formatter->addQuotedString(*deformationModelName());
2443
0
            formatter->endNode();
2444
0
        }
2445
0
        formatter->endNode();
2446
0
    }
2447
0
    VerticalReferenceFrame::_exportToWKT(formatter);
2448
0
}
2449
//! @endcond
2450
2451
// ---------------------------------------------------------------------------
2452
2453
/** \brief Instantiate a DynamicVerticalReferenceFrame
2454
 *
2455
 * @param properties See \ref general_properties.
2456
 * At minimum the name should be defined.
2457
 * @param anchor the anchor definition, or empty.
2458
 * @param realizationMethodIn the realization method, or empty.
2459
 * @param frameReferenceEpochIn the frame reference epoch.
2460
 * @param deformationModelNameIn deformation model name, or empty
2461
 * @return new DynamicVerticalReferenceFrame.
2462
 */
2463
DynamicVerticalReferenceFrameNNPtr DynamicVerticalReferenceFrame::create(
2464
    const util::PropertyMap &properties,
2465
    const util::optional<std::string> &anchor,
2466
    const util::optional<RealizationMethod> &realizationMethodIn,
2467
    const common::Measure &frameReferenceEpochIn,
2468
0
    const util::optional<std::string> &deformationModelNameIn) {
2469
0
    DynamicVerticalReferenceFrameNNPtr grf(
2470
0
        DynamicVerticalReferenceFrame::nn_make_shared<
2471
0
            DynamicVerticalReferenceFrame>(realizationMethodIn,
2472
0
                                           frameReferenceEpochIn,
2473
0
                                           deformationModelNameIn));
2474
0
    grf->setAnchor(anchor);
2475
0
    grf->setProperties(properties);
2476
0
    return grf;
2477
0
}
2478
2479
// ---------------------------------------------------------------------------
2480
2481
//! @cond Doxygen_Suppress
2482
struct TemporalDatum::Private {
2483
    common::DateTime temporalOrigin_;
2484
    std::string calendar_;
2485
2486
    Private(const common::DateTime &temporalOriginIn,
2487
            const std::string &calendarIn)
2488
0
        : temporalOrigin_(temporalOriginIn), calendar_(calendarIn) {}
2489
};
2490
//! @endcond
2491
2492
// ---------------------------------------------------------------------------
2493
2494
TemporalDatum::TemporalDatum(const common::DateTime &temporalOriginIn,
2495
                             const std::string &calendarIn)
2496
0
    : d(std::make_unique<Private>(temporalOriginIn, calendarIn)) {}
2497
2498
// ---------------------------------------------------------------------------
2499
2500
//! @cond Doxygen_Suppress
2501
0
TemporalDatum::~TemporalDatum() = default;
2502
//! @endcond
2503
2504
// ---------------------------------------------------------------------------
2505
2506
/** \brief Return the date and time to which temporal coordinates are
2507
 * referenced, expressed in conformance with ISO 8601.
2508
 *
2509
 * @return the temporal origin.
2510
 */
2511
0
const common::DateTime &TemporalDatum::temporalOrigin() const {
2512
0
    return d->temporalOrigin_;
2513
0
}
2514
2515
// ---------------------------------------------------------------------------
2516
2517
/** \brief Return the calendar to which the temporal origin is referenced
2518
 *
2519
 * Default value: TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN.
2520
 *
2521
 * @return the calendar.
2522
 */
2523
0
const std::string &TemporalDatum::calendar() const { return d->calendar_; }
2524
2525
// ---------------------------------------------------------------------------
2526
2527
/** \brief Instantiate a TemporalDatum
2528
 *
2529
 * @param properties See \ref general_properties.
2530
 * At minimum the name should be defined.
2531
 * @param temporalOriginIn the temporal origin into which temporal coordinates
2532
 * are referenced.
2533
 * @param calendarIn the calendar (generally
2534
 * TemporalDatum::CALENDAR_PROLEPTIC_GREGORIAN)
2535
 * @return new TemporalDatum.
2536
 */
2537
TemporalDatumNNPtr
2538
TemporalDatum::create(const util::PropertyMap &properties,
2539
                      const common::DateTime &temporalOriginIn,
2540
0
                      const std::string &calendarIn) {
2541
0
    auto datum(TemporalDatum::nn_make_shared<TemporalDatum>(temporalOriginIn,
2542
0
                                                            calendarIn));
2543
0
    datum->setProperties(properties);
2544
0
    return datum;
2545
0
}
2546
2547
// ---------------------------------------------------------------------------
2548
2549
//! @cond Doxygen_Suppress
2550
void TemporalDatum::_exportToWKT(
2551
    io::WKTFormatter *formatter) const // throw(FormattingException)
2552
0
{
2553
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2554
0
    if (!isWKT2) {
2555
0
        throw io::FormattingException(
2556
0
            "TemporalDatum can only be exported to WKT2");
2557
0
    }
2558
0
    formatter->startNode(io::WKTConstants::TDATUM, !identifiers().empty());
2559
0
    formatter->addQuotedString(nameStr());
2560
0
    if (formatter->use2019Keywords()) {
2561
0
        formatter->startNode(io::WKTConstants::CALENDAR, false);
2562
0
        formatter->addQuotedString(calendar());
2563
0
        formatter->endNode();
2564
0
    }
2565
2566
0
    const auto &timeOriginStr = temporalOrigin().toString();
2567
0
    if (!timeOriginStr.empty()) {
2568
0
        formatter->startNode(io::WKTConstants::TIMEORIGIN, false);
2569
0
        if (temporalOrigin().isISO_8601()) {
2570
0
            formatter->add(timeOriginStr);
2571
0
        } else {
2572
0
            formatter->addQuotedString(timeOriginStr);
2573
0
        }
2574
0
        formatter->endNode();
2575
0
    }
2576
2577
0
    formatter->endNode();
2578
0
}
2579
//! @endcond
2580
2581
// ---------------------------------------------------------------------------
2582
2583
//! @cond Doxygen_Suppress
2584
void TemporalDatum::_exportToJSON(
2585
    io::JSONFormatter *formatter) const // throw(FormattingException)
2586
0
{
2587
0
    auto objectContext(
2588
0
        formatter->MakeObjectContext("TemporalDatum", !identifiers().empty()));
2589
0
    auto writer = formatter->writer();
2590
2591
0
    writer->AddObjKey("name");
2592
0
    writer->Add(nameStr());
2593
2594
0
    writer->AddObjKey("calendar");
2595
0
    writer->Add(calendar());
2596
2597
0
    const auto &timeOriginStr = temporalOrigin().toString();
2598
0
    if (!timeOriginStr.empty()) {
2599
0
        writer->AddObjKey("time_origin");
2600
0
        writer->Add(timeOriginStr);
2601
0
    }
2602
2603
0
    ObjectUsage::baseExportToJSON(formatter);
2604
0
}
2605
//! @endcond
2606
2607
// ---------------------------------------------------------------------------
2608
2609
//! @cond Doxygen_Suppress
2610
bool TemporalDatum::_isEquivalentTo(
2611
    const util::IComparable *other, util::IComparable::Criterion criterion,
2612
0
    const io::DatabaseContextPtr &dbContext) const {
2613
0
    auto otherTD = dynamic_cast<const TemporalDatum *>(other);
2614
0
    if (otherTD == nullptr ||
2615
0
        !Datum::_isEquivalentTo(other, criterion, dbContext)) {
2616
0
        return false;
2617
0
    }
2618
0
    return temporalOrigin().toString() ==
2619
0
               otherTD->temporalOrigin().toString() &&
2620
0
           calendar() == otherTD->calendar();
2621
0
}
2622
//! @endcond
2623
2624
// ---------------------------------------------------------------------------
2625
2626
//! @cond Doxygen_Suppress
2627
struct EngineeringDatum::Private {};
2628
//! @endcond
2629
2630
// ---------------------------------------------------------------------------
2631
2632
0
EngineeringDatum::EngineeringDatum() : d(nullptr) {}
2633
2634
// ---------------------------------------------------------------------------
2635
2636
//! @cond Doxygen_Suppress
2637
0
EngineeringDatum::~EngineeringDatum() = default;
2638
//! @endcond
2639
2640
// ---------------------------------------------------------------------------
2641
2642
/** \brief Instantiate a EngineeringDatum
2643
 *
2644
 * @param properties See \ref general_properties.
2645
 * At minimum the name should be defined.
2646
 * @param anchor the anchor definition, or empty.
2647
 * @return new EngineeringDatum.
2648
 */
2649
EngineeringDatumNNPtr
2650
EngineeringDatum::create(const util::PropertyMap &properties,
2651
0
                         const util::optional<std::string> &anchor) {
2652
0
    auto datum(EngineeringDatum::nn_make_shared<EngineeringDatum>());
2653
0
    datum->setAnchor(anchor);
2654
0
    datum->setProperties(properties);
2655
0
    return datum;
2656
0
}
2657
2658
// ---------------------------------------------------------------------------
2659
2660
//! @cond Doxygen_Suppress
2661
void EngineeringDatum::_exportToWKT(
2662
    io::WKTFormatter *formatter) const // throw(FormattingException)
2663
0
{
2664
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2665
0
    formatter->startNode(isWKT2 ? io::WKTConstants::EDATUM
2666
0
                                : io::WKTConstants::LOCAL_DATUM,
2667
0
                         !identifiers().empty());
2668
0
    formatter->addQuotedString(nameStr());
2669
0
    if (isWKT2) {
2670
0
        Datum::getPrivate()->exportAnchorDefinition(formatter);
2671
0
    } else {
2672
        // Somewhat picked up arbitrarily from OGC 01-009:
2673
        // CS_LD_Max (Attribute) : 32767
2674
        // Highest possible value for local datum types.
2675
0
        formatter->add(32767);
2676
0
    }
2677
0
    formatter->endNode();
2678
0
}
2679
//! @endcond
2680
2681
// ---------------------------------------------------------------------------
2682
2683
//! @cond Doxygen_Suppress
2684
void EngineeringDatum::_exportToJSON(
2685
    io::JSONFormatter *formatter) const // throw(FormattingException)
2686
0
{
2687
0
    auto objectContext(formatter->MakeObjectContext("EngineeringDatum",
2688
0
                                                    !identifiers().empty()));
2689
0
    auto writer = formatter->writer();
2690
2691
0
    writer->AddObjKey("name");
2692
0
    writer->Add(nameStr());
2693
2694
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
2695
2696
0
    ObjectUsage::baseExportToJSON(formatter);
2697
0
}
2698
//! @endcond
2699
2700
// ---------------------------------------------------------------------------
2701
2702
//! @cond Doxygen_Suppress
2703
bool EngineeringDatum::_isEquivalentTo(
2704
    const util::IComparable *other, util::IComparable::Criterion criterion,
2705
0
    const io::DatabaseContextPtr &dbContext) const {
2706
0
    auto otherDatum = dynamic_cast<const EngineeringDatum *>(other);
2707
0
    if (otherDatum == nullptr) {
2708
0
        return false;
2709
0
    }
2710
0
    if (criterion != util::IComparable::Criterion::STRICT &&
2711
0
        (nameStr().empty() || nameStr() == UNKNOWN_ENGINEERING_DATUM) &&
2712
0
        (otherDatum->nameStr().empty() ||
2713
0
         otherDatum->nameStr() == UNKNOWN_ENGINEERING_DATUM)) {
2714
0
        return true;
2715
0
    }
2716
0
    return Datum::_isEquivalentTo(other, criterion, dbContext);
2717
0
}
2718
//! @endcond
2719
2720
// ---------------------------------------------------------------------------
2721
2722
//! @cond Doxygen_Suppress
2723
struct ParametricDatum::Private {};
2724
//! @endcond
2725
2726
// ---------------------------------------------------------------------------
2727
2728
0
ParametricDatum::ParametricDatum() : d(nullptr) {}
2729
2730
// ---------------------------------------------------------------------------
2731
2732
//! @cond Doxygen_Suppress
2733
0
ParametricDatum::~ParametricDatum() = default;
2734
//! @endcond
2735
2736
// ---------------------------------------------------------------------------
2737
2738
/** \brief Instantiate a ParametricDatum
2739
 *
2740
 * @param properties See \ref general_properties.
2741
 * At minimum the name should be defined.
2742
 * @param anchor the anchor definition, or empty.
2743
 * @return new ParametricDatum.
2744
 */
2745
ParametricDatumNNPtr
2746
ParametricDatum::create(const util::PropertyMap &properties,
2747
0
                        const util::optional<std::string> &anchor) {
2748
0
    auto datum(ParametricDatum::nn_make_shared<ParametricDatum>());
2749
0
    datum->setAnchor(anchor);
2750
0
    datum->setProperties(properties);
2751
0
    return datum;
2752
0
}
2753
2754
// ---------------------------------------------------------------------------
2755
2756
//! @cond Doxygen_Suppress
2757
void ParametricDatum::_exportToWKT(
2758
    io::WKTFormatter *formatter) const // throw(FormattingException)
2759
0
{
2760
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
2761
0
    if (!isWKT2) {
2762
0
        throw io::FormattingException(
2763
0
            "ParametricDatum can only be exported to WKT2");
2764
0
    }
2765
0
    formatter->startNode(io::WKTConstants::PDATUM, !identifiers().empty());
2766
0
    formatter->addQuotedString(nameStr());
2767
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
2768
0
    formatter->endNode();
2769
0
}
2770
//! @endcond
2771
2772
// ---------------------------------------------------------------------------
2773
2774
//! @cond Doxygen_Suppress
2775
void ParametricDatum::_exportToJSON(
2776
    io::JSONFormatter *formatter) const // throw(FormattingException)
2777
0
{
2778
0
    auto objectContext(formatter->MakeObjectContext("ParametricDatum",
2779
0
                                                    !identifiers().empty()));
2780
0
    auto writer = formatter->writer();
2781
2782
0
    writer->AddObjKey("name");
2783
0
    writer->Add(nameStr());
2784
2785
0
    Datum::getPrivate()->exportAnchorDefinition(formatter);
2786
2787
0
    ObjectUsage::baseExportToJSON(formatter);
2788
0
}
2789
//! @endcond
2790
2791
// ---------------------------------------------------------------------------
2792
2793
//! @cond Doxygen_Suppress
2794
bool ParametricDatum::_isEquivalentTo(
2795
    const util::IComparable *other, util::IComparable::Criterion criterion,
2796
0
    const io::DatabaseContextPtr &dbContext) const {
2797
0
    auto otherTD = dynamic_cast<const ParametricDatum *>(other);
2798
0
    if (otherTD == nullptr ||
2799
0
        !Datum::_isEquivalentTo(other, criterion, dbContext)) {
2800
0
        return false;
2801
0
    }
2802
0
    return true;
2803
0
}
2804
//! @endcond
2805
2806
} // namespace datum
2807
NS_PROJ_END