/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 |