/src/proj/src/iso19111/metadata.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/metadata.hpp" |
34 | | #include "proj/common.hpp" |
35 | | #include "proj/io.hpp" |
36 | | #include "proj/util.hpp" |
37 | | |
38 | | #include "proj/internal/internal.hpp" |
39 | | #include "proj/internal/io_internal.hpp" |
40 | | |
41 | | #include "proj_json_streaming_writer.hpp" |
42 | | |
43 | | #include <algorithm> |
44 | | #include <cmath> |
45 | | #include <limits> |
46 | | #include <memory> |
47 | | #include <string> |
48 | | #include <vector> |
49 | | |
50 | | using namespace NS_PROJ::internal; |
51 | | using namespace NS_PROJ::io; |
52 | | using namespace NS_PROJ::util; |
53 | | |
54 | | #if 0 |
55 | | namespace dropbox{ namespace oxygen { |
56 | | template<> nn<std::shared_ptr<NS_PROJ::metadata::Citation>>::~nn() = default; |
57 | | template<> nn<NS_PROJ::metadata::ExtentPtr>::~nn() = default; |
58 | | template<> nn<NS_PROJ::metadata::GeographicBoundingBoxPtr>::~nn() = default; |
59 | | template<> nn<NS_PROJ::metadata::GeographicExtentPtr>::~nn() = default; |
60 | | template<> nn<NS_PROJ::metadata::VerticalExtentPtr>::~nn() = default; |
61 | | template<> nn<NS_PROJ::metadata::TemporalExtentPtr>::~nn() = default; |
62 | | template<> nn<NS_PROJ::metadata::IdentifierPtr>::~nn() = default; |
63 | | template<> nn<NS_PROJ::metadata::PositionalAccuracyPtr>::~nn() = default; |
64 | | }} |
65 | | #endif |
66 | | |
67 | | NS_PROJ_START |
68 | | namespace metadata { |
69 | | |
70 | | // --------------------------------------------------------------------------- |
71 | | |
72 | | //! @cond Doxygen_Suppress |
73 | | struct Citation::Private { |
74 | | optional<std::string> title{}; |
75 | | }; |
76 | | //! @endcond |
77 | | |
78 | | // --------------------------------------------------------------------------- |
79 | | |
80 | | //! @cond Doxygen_Suppress |
81 | 184 | Citation::Citation() : d(std::make_unique<Private>()) {} |
82 | | //! @endcond |
83 | | |
84 | | // --------------------------------------------------------------------------- |
85 | | |
86 | | /** \brief Constructs a citation by its title. */ |
87 | | Citation::Citation(const std::string &titleIn) |
88 | 0 | : d(std::make_unique<Private>()) { |
89 | 0 | d->title = titleIn; |
90 | 0 | } |
91 | | |
92 | | // --------------------------------------------------------------------------- |
93 | | |
94 | | //! @cond Doxygen_Suppress |
95 | | Citation::Citation(const Citation &other) |
96 | 0 | : d(std::make_unique<Private>(*(other.d))) {} |
97 | | |
98 | | // --------------------------------------------------------------------------- |
99 | | |
100 | 68 | Citation::~Citation() = default; |
101 | | |
102 | | // --------------------------------------------------------------------------- |
103 | | |
104 | 0 | Citation &Citation::operator=(const Citation &other) { |
105 | 0 | if (this != &other) { |
106 | 0 | *d = *other.d; |
107 | 0 | } |
108 | 0 | return *this; |
109 | 0 | } |
110 | | //! @endcond |
111 | | |
112 | | // --------------------------------------------------------------------------- |
113 | | |
114 | | /** \brief Returns the name by which the cited resource is known. */ |
115 | 0 | const optional<std::string> &Citation::title() PROJ_PURE_DEFN { |
116 | 0 | return d->title; |
117 | 0 | } |
118 | | |
119 | | // --------------------------------------------------------------------------- |
120 | | |
121 | | //! @cond Doxygen_Suppress |
122 | | struct GeographicExtent::Private {}; |
123 | | //! @endcond |
124 | | |
125 | | // --------------------------------------------------------------------------- |
126 | | |
127 | 2 | GeographicExtent::GeographicExtent() : d(std::make_unique<Private>()) {} |
128 | | |
129 | | // --------------------------------------------------------------------------- |
130 | | |
131 | | //! @cond Doxygen_Suppress |
132 | 0 | GeographicExtent::~GeographicExtent() = default; |
133 | | //! @endcond |
134 | | |
135 | | // --------------------------------------------------------------------------- |
136 | | |
137 | | //! @cond Doxygen_Suppress |
138 | | struct GeographicBoundingBox::Private { |
139 | | double west_{}; |
140 | | double south_{}; |
141 | | double east_{}; |
142 | | double north_{}; |
143 | | |
144 | | Private(double west, double south, double east, double north) |
145 | 2 | : west_(west), south_(south), east_(east), north_(north) {} |
146 | | |
147 | | bool intersects(const Private &other) const; |
148 | | |
149 | | std::unique_ptr<Private> intersection(const Private &other) const; |
150 | | }; |
151 | | //! @endcond |
152 | | |
153 | | // --------------------------------------------------------------------------- |
154 | | |
155 | | GeographicBoundingBox::GeographicBoundingBox(double west, double south, |
156 | | double east, double north) |
157 | 2 | : GeographicExtent(), |
158 | 2 | d(std::make_unique<Private>(west, south, east, north)) {} |
159 | | |
160 | | // --------------------------------------------------------------------------- |
161 | | |
162 | | //! @cond Doxygen_Suppress |
163 | 0 | GeographicBoundingBox::~GeographicBoundingBox() = default; |
164 | | //! @endcond |
165 | | |
166 | | // --------------------------------------------------------------------------- |
167 | | |
168 | | /** \brief Returns the western-most coordinate of the limit of the dataset |
169 | | * extent. |
170 | | * |
171 | | * The unit is degrees. |
172 | | * |
173 | | * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses |
174 | | * the anti-meridian. |
175 | | */ |
176 | 0 | double GeographicBoundingBox::westBoundLongitude() PROJ_PURE_DEFN { |
177 | 0 | return d->west_; |
178 | 0 | } |
179 | | |
180 | | // --------------------------------------------------------------------------- |
181 | | |
182 | | /** \brief Returns the southern-most coordinate of the limit of the dataset |
183 | | * extent. |
184 | | * |
185 | | * The unit is degrees. |
186 | | */ |
187 | 0 | double GeographicBoundingBox::southBoundLatitude() PROJ_PURE_DEFN { |
188 | 0 | return d->south_; |
189 | 0 | } |
190 | | |
191 | | // --------------------------------------------------------------------------- |
192 | | |
193 | | /** \brief Returns the eastern-most coordinate of the limit of the dataset |
194 | | * extent. |
195 | | * |
196 | | * The unit is degrees. |
197 | | * |
198 | | * If eastBoundLongitude < westBoundLongitude(), then the bounding box crosses |
199 | | * the anti-meridian. |
200 | | */ |
201 | 0 | double GeographicBoundingBox::eastBoundLongitude() PROJ_PURE_DEFN { |
202 | 0 | return d->east_; |
203 | 0 | } |
204 | | |
205 | | // --------------------------------------------------------------------------- |
206 | | |
207 | | /** \brief Returns the northern-most coordinate of the limit of the dataset |
208 | | * extent. |
209 | | * |
210 | | * The unit is degrees. |
211 | | */ |
212 | 0 | double GeographicBoundingBox::northBoundLatitude() PROJ_PURE_DEFN { |
213 | 0 | return d->north_; |
214 | 0 | } |
215 | | |
216 | | // --------------------------------------------------------------------------- |
217 | | |
218 | | /** \brief Instantiate a GeographicBoundingBox. |
219 | | * |
220 | | * If east < west, then the bounding box crosses the anti-meridian. |
221 | | * |
222 | | * @param west Western-most coordinate of the limit of the dataset extent (in |
223 | | * degrees). |
224 | | * @param south Southern-most coordinate of the limit of the dataset extent (in |
225 | | * degrees). |
226 | | * @param east Eastern-most coordinate of the limit of the dataset extent (in |
227 | | * degrees). |
228 | | * @param north Northern-most coordinate of the limit of the dataset extent (in |
229 | | * degrees). |
230 | | * @return a new GeographicBoundingBox. |
231 | | */ |
232 | | GeographicBoundingBoxNNPtr GeographicBoundingBox::create(double west, |
233 | | double south, |
234 | | double east, |
235 | 2 | double north) { |
236 | 2 | if (std::isnan(west) || std::isnan(south) || std::isnan(east) || |
237 | 2 | std::isnan(north)) { |
238 | 0 | throw InvalidValueTypeException( |
239 | 0 | "GeographicBoundingBox::create() does not accept NaN values"); |
240 | 0 | } |
241 | 2 | if (south > north) { |
242 | 0 | throw InvalidValueTypeException( |
243 | 0 | "GeographicBoundingBox::create() does not accept south > north"); |
244 | 0 | } |
245 | | // Avoid creating a degenerate bounding box if reduced to a point or a line |
246 | 2 | if (west == east) { |
247 | 0 | if (west > -180) |
248 | 0 | west = |
249 | 0 | std::nextafter(west, -std::numeric_limits<double>::infinity()); |
250 | 0 | if (east < 180) |
251 | 0 | east = |
252 | 0 | std::nextafter(east, std::numeric_limits<double>::infinity()); |
253 | 0 | } |
254 | 2 | if (south == north) { |
255 | 0 | if (south > -90) |
256 | 0 | south = |
257 | 0 | std::nextafter(south, -std::numeric_limits<double>::infinity()); |
258 | 0 | if (north < 90) |
259 | 0 | north = |
260 | 0 | std::nextafter(north, std::numeric_limits<double>::infinity()); |
261 | 0 | } |
262 | 2 | return GeographicBoundingBox::nn_make_shared<GeographicBoundingBox>( |
263 | 2 | west, south, east, north); |
264 | 2 | } |
265 | | |
266 | | // --------------------------------------------------------------------------- |
267 | | |
268 | | //! @cond Doxygen_Suppress |
269 | | bool GeographicBoundingBox::_isEquivalentTo( |
270 | | const util::IComparable *other, util::IComparable::Criterion, |
271 | 0 | const io::DatabaseContextPtr &) const { |
272 | 0 | auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other); |
273 | 0 | if (!otherExtent) |
274 | 0 | return false; |
275 | 0 | return d->west_ == otherExtent->d->west_ && |
276 | 0 | d->south_ == otherExtent->d->south_ && |
277 | 0 | d->east_ == otherExtent->d->east_ && |
278 | 0 | d->north_ == otherExtent->d->north_; |
279 | 0 | } |
280 | | //! @endcond |
281 | | |
282 | | // --------------------------------------------------------------------------- |
283 | | |
284 | 0 | bool GeographicBoundingBox::contains(const GeographicExtentNNPtr &other) const { |
285 | 0 | auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get()); |
286 | 0 | if (!otherExtent) { |
287 | 0 | return false; |
288 | 0 | } |
289 | 0 | const double W = d->west_; |
290 | 0 | const double E = d->east_; |
291 | 0 | const double N = d->north_; |
292 | 0 | const double S = d->south_; |
293 | 0 | const double oW = otherExtent->d->west_; |
294 | 0 | const double oE = otherExtent->d->east_; |
295 | 0 | const double oN = otherExtent->d->north_; |
296 | 0 | const double oS = otherExtent->d->south_; |
297 | |
|
298 | 0 | if (!(S <= oS && N >= oN)) { |
299 | 0 | return false; |
300 | 0 | } |
301 | | |
302 | 0 | if (W == -180.0 && E == 180.0) { |
303 | 0 | return oW != oE; |
304 | 0 | } |
305 | | |
306 | 0 | if (oW == -180.0 && oE == 180.0) { |
307 | 0 | return false; |
308 | 0 | } |
309 | | |
310 | | // Normal bounding box ? |
311 | 0 | if (W < E) { |
312 | 0 | if (oW < oE) { |
313 | 0 | return W <= oW && E >= oE; |
314 | 0 | } else { |
315 | 0 | return false; |
316 | 0 | } |
317 | | // No: crossing antimerian |
318 | 0 | } else { |
319 | 0 | if (oW < oE) { |
320 | 0 | if (oW >= W) { |
321 | 0 | return true; |
322 | 0 | } else if (oE <= E) { |
323 | 0 | return true; |
324 | 0 | } else { |
325 | 0 | return false; |
326 | 0 | } |
327 | 0 | } else { |
328 | 0 | return W <= oW && E >= oE; |
329 | 0 | } |
330 | 0 | } |
331 | 0 | } |
332 | | |
333 | | // --------------------------------------------------------------------------- |
334 | | |
335 | | //! @cond Doxygen_Suppress |
336 | 0 | bool GeographicBoundingBox::Private::intersects(const Private &other) const { |
337 | 0 | const double W = west_; |
338 | 0 | const double E = east_; |
339 | 0 | const double N = north_; |
340 | 0 | const double S = south_; |
341 | 0 | const double oW = other.west_; |
342 | 0 | const double oE = other.east_; |
343 | 0 | const double oN = other.north_; |
344 | 0 | const double oS = other.south_; |
345 | | |
346 | | // Check intersection along the latitude axis |
347 | 0 | if (N < oS || S > oN) { |
348 | 0 | return false; |
349 | 0 | } |
350 | | |
351 | | // Check world coverage of this bbox, and other bbox overlapping |
352 | | // antimeridian (e.g. oW=175 and oE=-175) |
353 | | // Check oW > oE written for symmetry with the intersection() method. |
354 | 0 | if (W == -180.0 && E == 180.0 && oW > oE) { |
355 | 0 | return true; |
356 | 0 | } |
357 | | |
358 | | // Check world coverage of other bbox, and this bbox overlapping |
359 | | // antimeridian (e.g. W=175 and E=-175) |
360 | | // Check W > E written for symmetry with the intersection() method. |
361 | 0 | if (oW == -180.0 && oE == 180.0 && W > E) { |
362 | 0 | return true; |
363 | 0 | } |
364 | | |
365 | | // Normal bounding box ? |
366 | 0 | if (W <= E) { |
367 | 0 | if (oW <= oE) { |
368 | 0 | if (std::max(W, oW) < std::min(E, oE)) { |
369 | 0 | return true; |
370 | 0 | } |
371 | 0 | return false; |
372 | 0 | } |
373 | | |
374 | | // Bail out on longitudes not in [-180,180]. We could probably make |
375 | | // some sense of them, but this check at least avoid potential infinite |
376 | | // recursion. |
377 | 0 | if (oW > 180 || oE < -180) { |
378 | 0 | return false; |
379 | 0 | } |
380 | | |
381 | 0 | return intersects(Private(oW, oS, 180.0, oN)) || |
382 | 0 | intersects(Private(-180.0, oS, oE, oN)); |
383 | | |
384 | | // No: crossing antimeridian |
385 | 0 | } else { |
386 | 0 | if (oW <= oE) { |
387 | 0 | return other.intersects(*this); |
388 | 0 | } |
389 | | |
390 | 0 | return true; |
391 | 0 | } |
392 | 0 | } |
393 | | //! @endcond |
394 | | |
395 | | bool GeographicBoundingBox::intersects( |
396 | 0 | const GeographicExtentNNPtr &other) const { |
397 | 0 | auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get()); |
398 | 0 | if (!otherExtent) { |
399 | 0 | return false; |
400 | 0 | } |
401 | 0 | return d->intersects(*(otherExtent->d)); |
402 | 0 | } |
403 | | |
404 | | // --------------------------------------------------------------------------- |
405 | | |
406 | | GeographicExtentPtr |
407 | 0 | GeographicBoundingBox::intersection(const GeographicExtentNNPtr &other) const { |
408 | 0 | auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get()); |
409 | 0 | if (!otherExtent) { |
410 | 0 | return nullptr; |
411 | 0 | } |
412 | 0 | auto ret = d->intersection(*(otherExtent->d)); |
413 | 0 | if (ret) { |
414 | 0 | auto bbox = GeographicBoundingBox::create(ret->west_, ret->south_, |
415 | 0 | ret->east_, ret->north_); |
416 | 0 | return bbox.as_nullable(); |
417 | 0 | } |
418 | 0 | return nullptr; |
419 | 0 | } |
420 | | |
421 | | //! @cond Doxygen_Suppress |
422 | | std::unique_ptr<GeographicBoundingBox::Private> |
423 | 0 | GeographicBoundingBox::Private::intersection(const Private &otherExtent) const { |
424 | 0 | const double W = west_; |
425 | 0 | const double E = east_; |
426 | 0 | const double N = north_; |
427 | 0 | const double S = south_; |
428 | 0 | const double oW = otherExtent.west_; |
429 | 0 | const double oE = otherExtent.east_; |
430 | 0 | const double oN = otherExtent.north_; |
431 | 0 | const double oS = otherExtent.south_; |
432 | | |
433 | | // Check intersection along the latitude axis |
434 | 0 | if (N < oS || S > oN) { |
435 | 0 | return nullptr; |
436 | 0 | } |
437 | | |
438 | | // Check world coverage of this bbox, and other bbox overlapping |
439 | | // antimeridian (e.g. oW=175 and oE=-175) |
440 | 0 | if (W == -180.0 && E == 180.0 && oW > oE) { |
441 | 0 | return std::make_unique<Private>(oW, std::max(S, oS), oE, |
442 | 0 | std::min(N, oN)); |
443 | 0 | } |
444 | | |
445 | | // Check world coverage of other bbox, and this bbox overlapping |
446 | | // antimeridian (e.g. W=175 and E=-175) |
447 | 0 | if (oW == -180.0 && oE == 180.0 && W > E) { |
448 | 0 | return std::make_unique<Private>(W, std::max(S, oS), E, |
449 | 0 | std::min(N, oN)); |
450 | 0 | } |
451 | | |
452 | | // Normal bounding box ? |
453 | 0 | if (W <= E) { |
454 | 0 | if (oW <= oE) { |
455 | 0 | const double resW = std::max(W, oW); |
456 | 0 | const double resE = std::min(E, oE); |
457 | 0 | if (resW < resE) { |
458 | 0 | return std::make_unique<Private>(resW, std::max(S, oS), resE, |
459 | 0 | std::min(N, oN)); |
460 | 0 | } |
461 | 0 | return nullptr; |
462 | 0 | } |
463 | | |
464 | | // Bail out on longitudes not in [-180,180]. We could probably make |
465 | | // some sense of them, but this check at least avoid potential infinite |
466 | | // recursion. |
467 | 0 | if (oW > 180 || oE < -180) { |
468 | 0 | return nullptr; |
469 | 0 | } |
470 | | |
471 | | // Return larger of two parts of the multipolygon |
472 | 0 | auto inter1 = intersection(Private(oW, oS, 180.0, oN)); |
473 | 0 | auto inter2 = intersection(Private(-180.0, oS, oE, oN)); |
474 | 0 | if (!inter1) { |
475 | 0 | return inter2; |
476 | 0 | } |
477 | 0 | if (!inter2) { |
478 | 0 | return inter1; |
479 | 0 | } |
480 | 0 | if (inter1->east_ - inter1->west_ > inter2->east_ - inter2->west_) { |
481 | 0 | return inter1; |
482 | 0 | } |
483 | 0 | return inter2; |
484 | | // No: crossing antimeridian |
485 | 0 | } else { |
486 | 0 | if (oW <= oE) { |
487 | 0 | return otherExtent.intersection(*this); |
488 | 0 | } |
489 | | |
490 | 0 | return std::make_unique<Private>(std::max(W, oW), std::max(S, oS), |
491 | 0 | std::min(E, oE), std::min(N, oN)); |
492 | 0 | } |
493 | 0 | } |
494 | | //! @endcond |
495 | | |
496 | | // --------------------------------------------------------------------------- |
497 | | |
498 | | //! @cond Doxygen_Suppress |
499 | | struct VerticalExtent::Private { |
500 | | double minimum_{}; |
501 | | double maximum_{}; |
502 | | common::UnitOfMeasureNNPtr unit_; |
503 | | |
504 | | Private(double minimum, double maximum, |
505 | | const common::UnitOfMeasureNNPtr &unit) |
506 | 0 | : minimum_(minimum), maximum_(maximum), unit_(unit) {} |
507 | | }; |
508 | | //! @endcond |
509 | | |
510 | | // --------------------------------------------------------------------------- |
511 | | |
512 | | VerticalExtent::VerticalExtent(double minimumIn, double maximumIn, |
513 | | const common::UnitOfMeasureNNPtr &unitIn) |
514 | 0 | : d(std::make_unique<Private>(minimumIn, maximumIn, unitIn)) {} |
515 | | |
516 | | // --------------------------------------------------------------------------- |
517 | | |
518 | | //! @cond Doxygen_Suppress |
519 | 0 | VerticalExtent::~VerticalExtent() = default; |
520 | | //! @endcond |
521 | | |
522 | | // --------------------------------------------------------------------------- |
523 | | |
524 | | /** \brief Returns the minimum of the vertical extent. |
525 | | */ |
526 | 0 | double VerticalExtent::minimumValue() PROJ_PURE_DEFN { return d->minimum_; } |
527 | | |
528 | | // --------------------------------------------------------------------------- |
529 | | |
530 | | /** \brief Returns the maximum of the vertical extent. |
531 | | */ |
532 | 0 | double VerticalExtent::maximumValue() PROJ_PURE_DEFN { return d->maximum_; } |
533 | | |
534 | | // --------------------------------------------------------------------------- |
535 | | |
536 | | /** \brief Returns the unit of the vertical extent. |
537 | | */ |
538 | 0 | common::UnitOfMeasureNNPtr &VerticalExtent::unit() PROJ_PURE_DEFN { |
539 | 0 | return d->unit_; |
540 | 0 | } |
541 | | |
542 | | // --------------------------------------------------------------------------- |
543 | | |
544 | | /** \brief Instantiate a VerticalExtent. |
545 | | * |
546 | | * @param minimumIn minimum. |
547 | | * @param maximumIn maximum. |
548 | | * @param unitIn unit. |
549 | | * @return a new VerticalExtent. |
550 | | */ |
551 | | VerticalExtentNNPtr |
552 | | VerticalExtent::create(double minimumIn, double maximumIn, |
553 | 0 | const common::UnitOfMeasureNNPtr &unitIn) { |
554 | 0 | return VerticalExtent::nn_make_shared<VerticalExtent>(minimumIn, maximumIn, |
555 | 0 | unitIn); |
556 | 0 | } |
557 | | |
558 | | // --------------------------------------------------------------------------- |
559 | | |
560 | | //! @cond Doxygen_Suppress |
561 | | bool VerticalExtent::_isEquivalentTo(const util::IComparable *other, |
562 | | util::IComparable::Criterion, |
563 | 0 | const io::DatabaseContextPtr &) const { |
564 | 0 | auto otherExtent = dynamic_cast<const VerticalExtent *>(other); |
565 | 0 | if (!otherExtent) |
566 | 0 | return false; |
567 | 0 | return d->minimum_ == otherExtent->d->minimum_ && |
568 | 0 | d->maximum_ == otherExtent->d->maximum_ && |
569 | 0 | d->unit_ == otherExtent->d->unit_; |
570 | 0 | } |
571 | | //! @endcond |
572 | | |
573 | | // --------------------------------------------------------------------------- |
574 | | |
575 | | /** \brief Returns whether this extent contains the other one. |
576 | | */ |
577 | 0 | bool VerticalExtent::contains(const VerticalExtentNNPtr &other) const { |
578 | 0 | const double thisUnitToSI = d->unit_->conversionToSI(); |
579 | 0 | const double otherUnitToSI = other->d->unit_->conversionToSI(); |
580 | 0 | return d->minimum_ * thisUnitToSI <= other->d->minimum_ * otherUnitToSI && |
581 | 0 | d->maximum_ * thisUnitToSI >= other->d->maximum_ * otherUnitToSI; |
582 | 0 | } |
583 | | |
584 | | // --------------------------------------------------------------------------- |
585 | | |
586 | | /** \brief Returns whether this extent intersects the other one. |
587 | | */ |
588 | 0 | bool VerticalExtent::intersects(const VerticalExtentNNPtr &other) const { |
589 | 0 | const double thisUnitToSI = d->unit_->conversionToSI(); |
590 | 0 | const double otherUnitToSI = other->d->unit_->conversionToSI(); |
591 | 0 | return d->minimum_ * thisUnitToSI <= other->d->maximum_ * otherUnitToSI && |
592 | 0 | d->maximum_ * thisUnitToSI >= other->d->minimum_ * otherUnitToSI; |
593 | 0 | } |
594 | | |
595 | | // --------------------------------------------------------------------------- |
596 | | |
597 | | //! @cond Doxygen_Suppress |
598 | | struct TemporalExtent::Private { |
599 | | std::string start_{}; |
600 | | std::string stop_{}; |
601 | | |
602 | | Private(const std::string &start, const std::string &stop) |
603 | 0 | : start_(start), stop_(stop) {} |
604 | | }; |
605 | | //! @endcond |
606 | | |
607 | | // --------------------------------------------------------------------------- |
608 | | |
609 | | TemporalExtent::TemporalExtent(const std::string &startIn, |
610 | | const std::string &stopIn) |
611 | 0 | : d(std::make_unique<Private>(startIn, stopIn)) {} |
612 | | |
613 | | // --------------------------------------------------------------------------- |
614 | | |
615 | | //! @cond Doxygen_Suppress |
616 | 0 | TemporalExtent::~TemporalExtent() = default; |
617 | | //! @endcond |
618 | | |
619 | | // --------------------------------------------------------------------------- |
620 | | |
621 | | /** \brief Returns the start of the temporal extent. |
622 | | */ |
623 | 0 | const std::string &TemporalExtent::start() PROJ_PURE_DEFN { return d->start_; } |
624 | | |
625 | | // --------------------------------------------------------------------------- |
626 | | |
627 | | /** \brief Returns the end of the temporal extent. |
628 | | */ |
629 | 0 | const std::string &TemporalExtent::stop() PROJ_PURE_DEFN { return d->stop_; } |
630 | | |
631 | | // --------------------------------------------------------------------------- |
632 | | |
633 | | /** \brief Instantiate a TemporalExtent. |
634 | | * |
635 | | * @param start start. |
636 | | * @param stop stop. |
637 | | * @return a new TemporalExtent. |
638 | | */ |
639 | | TemporalExtentNNPtr TemporalExtent::create(const std::string &start, |
640 | 0 | const std::string &stop) { |
641 | 0 | return TemporalExtent::nn_make_shared<TemporalExtent>(start, stop); |
642 | 0 | } |
643 | | |
644 | | // --------------------------------------------------------------------------- |
645 | | |
646 | | //! @cond Doxygen_Suppress |
647 | | bool TemporalExtent::_isEquivalentTo(const util::IComparable *other, |
648 | | util::IComparable::Criterion, |
649 | 0 | const io::DatabaseContextPtr &) const { |
650 | 0 | auto otherExtent = dynamic_cast<const TemporalExtent *>(other); |
651 | 0 | if (!otherExtent) |
652 | 0 | return false; |
653 | 0 | return start() == otherExtent->start() && stop() == otherExtent->stop(); |
654 | 0 | } |
655 | | //! @endcond |
656 | | |
657 | | // --------------------------------------------------------------------------- |
658 | | |
659 | | /** \brief Returns whether this extent contains the other one. |
660 | | */ |
661 | 0 | bool TemporalExtent::contains(const TemporalExtentNNPtr &other) const { |
662 | 0 | return start() <= other->start() && stop() >= other->stop(); |
663 | 0 | } |
664 | | |
665 | | // --------------------------------------------------------------------------- |
666 | | |
667 | | /** \brief Returns whether this extent intersects the other one. |
668 | | */ |
669 | 0 | bool TemporalExtent::intersects(const TemporalExtentNNPtr &other) const { |
670 | 0 | return start() <= other->stop() && stop() >= other->start(); |
671 | 0 | } |
672 | | |
673 | | // --------------------------------------------------------------------------- |
674 | | |
675 | | //! @cond Doxygen_Suppress |
676 | | struct Extent::Private { |
677 | | optional<std::string> description_{}; |
678 | | std::vector<GeographicExtentNNPtr> geographicElements_{}; |
679 | | std::vector<VerticalExtentNNPtr> verticalElements_{}; |
680 | | std::vector<TemporalExtentNNPtr> temporalElements_{}; |
681 | | }; |
682 | | //! @endcond |
683 | | |
684 | | // --------------------------------------------------------------------------- |
685 | | |
686 | | //! @cond Doxygen_Suppress |
687 | 2 | Extent::Extent() : d(std::make_unique<Private>()) {} |
688 | | |
689 | | // --------------------------------------------------------------------------- |
690 | | |
691 | 0 | Extent::Extent(const Extent &other) : d(std::make_unique<Private>(*other.d)) {} |
692 | | |
693 | | // --------------------------------------------------------------------------- |
694 | | |
695 | 0 | Extent::~Extent() = default; |
696 | | //! @endcond |
697 | | |
698 | | // --------------------------------------------------------------------------- |
699 | | |
700 | | /** Return a textual description of the extent. |
701 | | * |
702 | | * @return the description, or empty. |
703 | | */ |
704 | 0 | const optional<std::string> &Extent::description() PROJ_PURE_DEFN { |
705 | 0 | return d->description_; |
706 | 0 | } |
707 | | |
708 | | // --------------------------------------------------------------------------- |
709 | | |
710 | | /** Return the geographic element(s) of the extent |
711 | | * |
712 | | * @return the geographic element(s), or empty. |
713 | | */ |
714 | | const std::vector<GeographicExtentNNPtr> & |
715 | 0 | Extent::geographicElements() PROJ_PURE_DEFN { |
716 | 0 | return d->geographicElements_; |
717 | 0 | } |
718 | | |
719 | | // --------------------------------------------------------------------------- |
720 | | |
721 | | /** Return the vertical element(s) of the extent |
722 | | * |
723 | | * @return the vertical element(s), or empty. |
724 | | */ |
725 | | const std::vector<VerticalExtentNNPtr> & |
726 | 0 | Extent::verticalElements() PROJ_PURE_DEFN { |
727 | 0 | return d->verticalElements_; |
728 | 0 | } |
729 | | |
730 | | // --------------------------------------------------------------------------- |
731 | | |
732 | | /** Return the temporal element(s) of the extent |
733 | | * |
734 | | * @return the temporal element(s), or empty. |
735 | | */ |
736 | | const std::vector<TemporalExtentNNPtr> & |
737 | 0 | Extent::temporalElements() PROJ_PURE_DEFN { |
738 | 0 | return d->temporalElements_; |
739 | 0 | } |
740 | | |
741 | | // --------------------------------------------------------------------------- |
742 | | |
743 | | /** \brief Instantiate a Extent. |
744 | | * |
745 | | * @param descriptionIn Textual description, or empty. |
746 | | * @param geographicElementsIn Geographic element(s), or empty. |
747 | | * @param verticalElementsIn Vertical element(s), or empty. |
748 | | * @param temporalElementsIn Temporal element(s), or empty. |
749 | | * @return a new Extent. |
750 | | */ |
751 | | ExtentNNPtr |
752 | | Extent::create(const optional<std::string> &descriptionIn, |
753 | | const std::vector<GeographicExtentNNPtr> &geographicElementsIn, |
754 | | const std::vector<VerticalExtentNNPtr> &verticalElementsIn, |
755 | 2 | const std::vector<TemporalExtentNNPtr> &temporalElementsIn) { |
756 | 2 | auto extent = Extent::nn_make_shared<Extent>(); |
757 | 2 | extent->assignSelf(extent); |
758 | 2 | extent->d->description_ = descriptionIn; |
759 | 2 | extent->d->geographicElements_ = geographicElementsIn; |
760 | 2 | extent->d->verticalElements_ = verticalElementsIn; |
761 | 2 | extent->d->temporalElements_ = temporalElementsIn; |
762 | 2 | return extent; |
763 | 2 | } |
764 | | |
765 | | // --------------------------------------------------------------------------- |
766 | | |
767 | | /** \brief Instantiate a Extent from a bounding box |
768 | | * |
769 | | * @param west Western-most coordinate of the limit of the dataset extent (in |
770 | | * degrees). |
771 | | * @param south Southern-most coordinate of the limit of the dataset extent (in |
772 | | * degrees). |
773 | | * @param east Eastern-most coordinate of the limit of the dataset extent (in |
774 | | * degrees). |
775 | | * @param north Northern-most coordinate of the limit of the dataset extent (in |
776 | | * degrees). |
777 | | * @param descriptionIn Textual description, or empty. |
778 | | * @return a new Extent. |
779 | | */ |
780 | | ExtentNNPtr |
781 | | Extent::createFromBBOX(double west, double south, double east, double north, |
782 | 2 | const util::optional<std::string> &descriptionIn) { |
783 | 2 | return create( |
784 | 2 | descriptionIn, |
785 | 2 | std::vector<GeographicExtentNNPtr>{ |
786 | 2 | nn_static_pointer_cast<GeographicExtent>( |
787 | 2 | GeographicBoundingBox::create(west, south, east, north))}, |
788 | 2 | std::vector<VerticalExtentNNPtr>(), std::vector<TemporalExtentNNPtr>()); |
789 | 2 | } |
790 | | |
791 | | // --------------------------------------------------------------------------- |
792 | | |
793 | | //! @cond Doxygen_Suppress |
794 | | bool Extent::_isEquivalentTo(const util::IComparable *other, |
795 | | util::IComparable::Criterion criterion, |
796 | 0 | const io::DatabaseContextPtr &dbContext) const { |
797 | 0 | auto otherExtent = dynamic_cast<const Extent *>(other); |
798 | 0 | bool ret = |
799 | 0 | (otherExtent && |
800 | 0 | description().has_value() == otherExtent->description().has_value() && |
801 | 0 | *description() == *otherExtent->description() && |
802 | 0 | d->geographicElements_.size() == |
803 | 0 | otherExtent->d->geographicElements_.size() && |
804 | 0 | d->verticalElements_.size() == |
805 | 0 | otherExtent->d->verticalElements_.size() && |
806 | 0 | d->temporalElements_.size() == |
807 | 0 | otherExtent->d->temporalElements_.size()); |
808 | 0 | if (ret) { |
809 | 0 | for (size_t i = 0; ret && i < d->geographicElements_.size(); ++i) { |
810 | 0 | ret = d->geographicElements_[i]->_isEquivalentTo( |
811 | 0 | otherExtent->d->geographicElements_[i].get(), criterion, |
812 | 0 | dbContext); |
813 | 0 | } |
814 | 0 | for (size_t i = 0; ret && i < d->verticalElements_.size(); ++i) { |
815 | 0 | ret = d->verticalElements_[i]->_isEquivalentTo( |
816 | 0 | otherExtent->d->verticalElements_[i].get(), criterion, |
817 | 0 | dbContext); |
818 | 0 | } |
819 | 0 | for (size_t i = 0; ret && i < d->temporalElements_.size(); ++i) { |
820 | 0 | ret = d->temporalElements_[i]->_isEquivalentTo( |
821 | 0 | otherExtent->d->temporalElements_[i].get(), criterion, |
822 | 0 | dbContext); |
823 | 0 | } |
824 | 0 | } |
825 | 0 | return ret; |
826 | 0 | } |
827 | | //! @endcond |
828 | | |
829 | | // --------------------------------------------------------------------------- |
830 | | |
831 | | /** \brief Returns whether this extent contains the other one. |
832 | | * |
833 | | * Behavior only well specified if each sub-extent category as at most |
834 | | * one element. |
835 | | */ |
836 | 0 | bool Extent::contains(const ExtentNNPtr &other) const { |
837 | 0 | bool res = true; |
838 | 0 | if (d->geographicElements_.size() == 1 && |
839 | 0 | other->d->geographicElements_.size() == 1) { |
840 | 0 | res = d->geographicElements_[0]->contains( |
841 | 0 | other->d->geographicElements_[0]); |
842 | 0 | } |
843 | 0 | if (res && d->verticalElements_.size() == 1 && |
844 | 0 | other->d->verticalElements_.size() == 1) { |
845 | 0 | res = d->verticalElements_[0]->contains(other->d->verticalElements_[0]); |
846 | 0 | } |
847 | 0 | if (res && d->temporalElements_.size() == 1 && |
848 | 0 | other->d->temporalElements_.size() == 1) { |
849 | 0 | res = d->temporalElements_[0]->contains(other->d->temporalElements_[0]); |
850 | 0 | } |
851 | 0 | return res; |
852 | 0 | } |
853 | | |
854 | | // --------------------------------------------------------------------------- |
855 | | |
856 | | /** \brief Returns whether this extent intersects the other one. |
857 | | * |
858 | | * Behavior only well specified if each sub-extent category as at most |
859 | | * one element. |
860 | | */ |
861 | 0 | bool Extent::intersects(const ExtentNNPtr &other) const { |
862 | 0 | bool res = true; |
863 | 0 | if (d->geographicElements_.size() == 1 && |
864 | 0 | other->d->geographicElements_.size() == 1) { |
865 | 0 | res = d->geographicElements_[0]->intersects( |
866 | 0 | other->d->geographicElements_[0]); |
867 | 0 | } |
868 | 0 | if (res && d->verticalElements_.size() == 1 && |
869 | 0 | other->d->verticalElements_.size() == 1) { |
870 | 0 | res = |
871 | 0 | d->verticalElements_[0]->intersects(other->d->verticalElements_[0]); |
872 | 0 | } |
873 | 0 | if (res && d->temporalElements_.size() == 1 && |
874 | 0 | other->d->temporalElements_.size() == 1) { |
875 | 0 | res = |
876 | 0 | d->temporalElements_[0]->intersects(other->d->temporalElements_[0]); |
877 | 0 | } |
878 | 0 | return res; |
879 | 0 | } |
880 | | |
881 | | // --------------------------------------------------------------------------- |
882 | | |
883 | | /** \brief Returns the intersection of this extent with another one. |
884 | | * |
885 | | * Behavior only well specified if there is one single GeographicExtent |
886 | | * in each object. |
887 | | * Returns nullptr otherwise. |
888 | | */ |
889 | 0 | ExtentPtr Extent::intersection(const ExtentNNPtr &other) const { |
890 | 0 | if (d->geographicElements_.size() == 1 && |
891 | 0 | other->d->geographicElements_.size() == 1) { |
892 | 0 | if (contains(other)) { |
893 | 0 | return other.as_nullable(); |
894 | 0 | } |
895 | 0 | auto self = util::nn_static_pointer_cast<Extent>(shared_from_this()); |
896 | 0 | if (other->contains(self)) { |
897 | 0 | return self.as_nullable(); |
898 | 0 | } |
899 | 0 | auto geogIntersection = d->geographicElements_[0]->intersection( |
900 | 0 | other->d->geographicElements_[0]); |
901 | 0 | if (geogIntersection) { |
902 | 0 | return create(util::optional<std::string>(), |
903 | 0 | std::vector<GeographicExtentNNPtr>{ |
904 | 0 | NN_NO_CHECK(geogIntersection)}, |
905 | 0 | std::vector<VerticalExtentNNPtr>{}, |
906 | 0 | std::vector<TemporalExtentNNPtr>{}); |
907 | 0 | } |
908 | 0 | } |
909 | 0 | return nullptr; |
910 | 0 | } |
911 | | |
912 | | // --------------------------------------------------------------------------- |
913 | | |
914 | | //! @cond Doxygen_Suppress |
915 | | struct Identifier::Private { |
916 | | optional<Citation> authority_{}; |
917 | | std::string code_{}; |
918 | | optional<std::string> codeSpace_{}; |
919 | | optional<std::string> version_{}; |
920 | | optional<std::string> description_{}; |
921 | | optional<std::string> uri_{}; |
922 | | |
923 | 68 | Private() = default; |
924 | | |
925 | | Private(const std::string &codeIn, const PropertyMap &properties) |
926 | 116 | : code_(codeIn) { |
927 | 116 | setProperties(properties); |
928 | 116 | } |
929 | | |
930 | | private: |
931 | | // cppcheck-suppress functionStatic |
932 | | void setProperties(const PropertyMap &properties); |
933 | | }; |
934 | | |
935 | | // --------------------------------------------------------------------------- |
936 | | |
937 | | void Identifier::Private::setProperties( |
938 | | const PropertyMap &properties) // throw(InvalidValueTypeException) |
939 | 116 | { |
940 | 116 | { |
941 | 116 | const auto pVal = properties.get(AUTHORITY_KEY); |
942 | 116 | if (pVal) { |
943 | 0 | if (auto genVal = dynamic_cast<const BoxedValue *>(pVal->get())) { |
944 | 0 | if (genVal->type() == BoxedValue::Type::STRING) { |
945 | 0 | authority_ = Citation(genVal->stringValue()); |
946 | 0 | } else { |
947 | 0 | throw InvalidValueTypeException("Invalid value type for " + |
948 | 0 | AUTHORITY_KEY); |
949 | 0 | } |
950 | 0 | } else { |
951 | 0 | auto citation = dynamic_cast<const Citation *>(pVal->get()); |
952 | 0 | if (citation) { |
953 | 0 | authority_ = *citation; |
954 | 0 | } else { |
955 | 0 | throw InvalidValueTypeException("Invalid value type for " + |
956 | 0 | AUTHORITY_KEY); |
957 | 0 | } |
958 | 0 | } |
959 | 0 | } |
960 | 116 | } |
961 | | |
962 | 116 | { |
963 | 116 | const auto pVal = properties.get(CODE_KEY); |
964 | 116 | if (pVal) { |
965 | 34 | if (auto genVal = dynamic_cast<const BoxedValue *>(pVal->get())) { |
966 | 34 | if (genVal->type() == BoxedValue::Type::INTEGER) { |
967 | 32 | code_ = toString(genVal->integerValue()); |
968 | 32 | } else if (genVal->type() == BoxedValue::Type::STRING) { |
969 | 2 | code_ = genVal->stringValue(); |
970 | 2 | } else { |
971 | 0 | throw InvalidValueTypeException("Invalid value type for " + |
972 | 0 | CODE_KEY); |
973 | 0 | } |
974 | 34 | } else { |
975 | 0 | throw InvalidValueTypeException("Invalid value type for " + |
976 | 0 | CODE_KEY); |
977 | 0 | } |
978 | 34 | } |
979 | 116 | } |
980 | | |
981 | 116 | properties.getStringValue(CODESPACE_KEY, codeSpace_); |
982 | 116 | properties.getStringValue(VERSION_KEY, version_); |
983 | 116 | properties.getStringValue(DESCRIPTION_KEY, description_); |
984 | 116 | properties.getStringValue(URI_KEY, uri_); |
985 | 116 | } |
986 | | |
987 | | //! @endcond |
988 | | |
989 | | // --------------------------------------------------------------------------- |
990 | | |
991 | | Identifier::Identifier(const std::string &codeIn, |
992 | | const util::PropertyMap &properties) |
993 | 116 | : d(std::make_unique<Private>(codeIn, properties)) {} |
994 | | |
995 | | // --------------------------------------------------------------------------- |
996 | | |
997 | | //! @cond Doxygen_Suppress |
998 | | |
999 | | // --------------------------------------------------------------------------- |
1000 | | |
1001 | 68 | Identifier::Identifier() : d(std::make_unique<Private>()) {} |
1002 | | |
1003 | | // --------------------------------------------------------------------------- |
1004 | | |
1005 | | Identifier::Identifier(const Identifier &other) |
1006 | 0 | : d(std::make_unique<Private>(*(other.d))) {} |
1007 | | |
1008 | | // --------------------------------------------------------------------------- |
1009 | | |
1010 | 68 | Identifier::~Identifier() = default; |
1011 | | //! @endcond |
1012 | | |
1013 | | // --------------------------------------------------------------------------- |
1014 | | |
1015 | | /** \brief Instantiate a Identifier. |
1016 | | * |
1017 | | * @param codeIn Alphanumeric value identifying an instance in the codespace |
1018 | | * @param properties See \ref general_properties. |
1019 | | * Generally, the Identifier::CODESPACE_KEY should be set. |
1020 | | * @return a new Identifier. |
1021 | | */ |
1022 | | IdentifierNNPtr Identifier::create(const std::string &codeIn, |
1023 | 116 | const PropertyMap &properties) { |
1024 | 116 | return Identifier::nn_make_shared<Identifier>(codeIn, properties); |
1025 | 116 | } |
1026 | | |
1027 | | // --------------------------------------------------------------------------- |
1028 | | |
1029 | | //! @cond Doxygen_Suppress |
1030 | | IdentifierNNPtr |
1031 | 68 | Identifier::createFromDescription(const std::string &descriptionIn) { |
1032 | 68 | auto id = Identifier::nn_make_shared<Identifier>(); |
1033 | 68 | id->d->description_ = descriptionIn; |
1034 | 68 | return id; |
1035 | 68 | } |
1036 | | //! @endcond |
1037 | | |
1038 | | // --------------------------------------------------------------------------- |
1039 | | |
1040 | | /** \brief Return a citation for the organization responsible for definition and |
1041 | | * maintenance of the code. |
1042 | | * |
1043 | | * @return the citation for the authority, or empty. |
1044 | | */ |
1045 | 0 | const optional<Citation> &Identifier::authority() PROJ_PURE_DEFN { |
1046 | 0 | return d->authority_; |
1047 | 0 | } |
1048 | | |
1049 | | // --------------------------------------------------------------------------- |
1050 | | |
1051 | | /** \brief Return the alphanumeric value identifying an instance in the |
1052 | | * codespace. |
1053 | | * |
1054 | | * e.g. "4326" (for EPSG:4326 WGS 84 GeographicCRS) |
1055 | | * |
1056 | | * @return the code. |
1057 | | */ |
1058 | 0 | const std::string &Identifier::code() PROJ_PURE_DEFN { return d->code_; } |
1059 | | |
1060 | | // --------------------------------------------------------------------------- |
1061 | | |
1062 | | /** \brief Return the organization responsible for definition and maintenance of |
1063 | | * the code. |
1064 | | * |
1065 | | * e.g "EPSG" |
1066 | | * |
1067 | | * @return the authority codespace, or empty. |
1068 | | */ |
1069 | 0 | const optional<std::string> &Identifier::codeSpace() PROJ_PURE_DEFN { |
1070 | 0 | return d->codeSpace_; |
1071 | 0 | } |
1072 | | |
1073 | | // --------------------------------------------------------------------------- |
1074 | | |
1075 | | /** \brief Return the version identifier for the namespace. |
1076 | | * |
1077 | | * When appropriate, the edition is identified by the effective date, coded |
1078 | | * using ISO 8601 date format. |
1079 | | * |
1080 | | * @return the version or empty. |
1081 | | */ |
1082 | 0 | const optional<std::string> &Identifier::version() PROJ_PURE_DEFN { |
1083 | 0 | return d->version_; |
1084 | 0 | } |
1085 | | |
1086 | | // --------------------------------------------------------------------------- |
1087 | | |
1088 | | /** \brief Return the natural language description of the meaning of the code |
1089 | | * value. |
1090 | | * |
1091 | | * @return the description or empty. |
1092 | | */ |
1093 | 0 | const optional<std::string> &Identifier::description() PROJ_PURE_DEFN { |
1094 | 0 | return d->description_; |
1095 | 0 | } |
1096 | | |
1097 | | // --------------------------------------------------------------------------- |
1098 | | |
1099 | | /** \brief Return the URI of the identifier. |
1100 | | * |
1101 | | * @return the URI or empty. |
1102 | | */ |
1103 | 0 | const optional<std::string> &Identifier::uri() PROJ_PURE_DEFN { |
1104 | 0 | return d->uri_; |
1105 | 0 | } |
1106 | | |
1107 | | // --------------------------------------------------------------------------- |
1108 | | |
1109 | | //! @cond Doxygen_Suppress |
1110 | 0 | void Identifier::_exportToWKT(WKTFormatter *formatter) const { |
1111 | 0 | const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2; |
1112 | 0 | const std::string &l_code = code(); |
1113 | 0 | std::string l_codeSpace = *codeSpace(); |
1114 | 0 | std::string l_version = *version(); |
1115 | 0 | const auto &dbContext = formatter->databaseContext(); |
1116 | 0 | if (dbContext) { |
1117 | 0 | dbContext->getAuthorityAndVersion(*codeSpace(), l_codeSpace, l_version); |
1118 | 0 | } |
1119 | 0 | if (!l_codeSpace.empty() && !l_code.empty()) { |
1120 | 0 | if (isWKT2) { |
1121 | 0 | formatter->startNode(WKTConstants::ID, false); |
1122 | 0 | formatter->addQuotedString(l_codeSpace); |
1123 | 0 | try { |
1124 | 0 | (void)std::stoi(l_code); |
1125 | 0 | formatter->add(l_code); |
1126 | 0 | } catch (const std::exception &) { |
1127 | 0 | formatter->addQuotedString(l_code); |
1128 | 0 | } |
1129 | 0 | if (!l_version.empty()) { |
1130 | 0 | bool isDouble = false; |
1131 | 0 | (void)c_locale_stod(l_version, isDouble); |
1132 | 0 | if (isDouble) { |
1133 | 0 | formatter->add(l_version); |
1134 | 0 | } else { |
1135 | 0 | formatter->addQuotedString(l_version); |
1136 | 0 | } |
1137 | 0 | } |
1138 | 0 | if (authority().has_value() && |
1139 | 0 | *(authority()->title()) != *codeSpace()) { |
1140 | 0 | formatter->startNode(WKTConstants::CITATION, false); |
1141 | 0 | formatter->addQuotedString(*(authority()->title())); |
1142 | 0 | formatter->endNode(); |
1143 | 0 | } |
1144 | 0 | if (uri().has_value()) { |
1145 | 0 | formatter->startNode(WKTConstants::URI, false); |
1146 | 0 | formatter->addQuotedString(*(uri())); |
1147 | 0 | formatter->endNode(); |
1148 | 0 | } |
1149 | 0 | formatter->endNode(); |
1150 | 0 | } else { |
1151 | 0 | formatter->startNode(WKTConstants::AUTHORITY, false); |
1152 | 0 | formatter->addQuotedString(l_codeSpace); |
1153 | 0 | formatter->addQuotedString(l_code); |
1154 | 0 | formatter->endNode(); |
1155 | 0 | } |
1156 | 0 | } |
1157 | 0 | } |
1158 | | |
1159 | | // --------------------------------------------------------------------------- |
1160 | | |
1161 | 0 | void Identifier::_exportToJSON(JSONFormatter *formatter) const { |
1162 | 0 | const std::string &l_code = code(); |
1163 | 0 | std::string l_codeSpace = *codeSpace(); |
1164 | 0 | std::string l_version = *version(); |
1165 | 0 | const auto &dbContext = formatter->databaseContext(); |
1166 | 0 | if (dbContext) { |
1167 | 0 | dbContext->getAuthorityAndVersion(*codeSpace(), l_codeSpace, l_version); |
1168 | 0 | } |
1169 | 0 | if (!l_codeSpace.empty() && !l_code.empty()) { |
1170 | 0 | auto writer = formatter->writer(); |
1171 | 0 | auto objContext(formatter->MakeObjectContext(nullptr, false)); |
1172 | 0 | writer->AddObjKey("authority"); |
1173 | 0 | writer->Add(l_codeSpace); |
1174 | 0 | writer->AddObjKey("code"); |
1175 | 0 | try { |
1176 | 0 | writer->Add(std::stoi(l_code)); |
1177 | 0 | } catch (const std::exception &) { |
1178 | 0 | writer->Add(l_code); |
1179 | 0 | } |
1180 | |
|
1181 | 0 | if (!l_version.empty()) { |
1182 | 0 | writer->AddObjKey("version"); |
1183 | 0 | bool isDouble = false; |
1184 | 0 | (void)c_locale_stod(l_version, isDouble); |
1185 | 0 | if (isDouble) { |
1186 | 0 | writer->AddUnquoted(l_version.c_str()); |
1187 | 0 | } else { |
1188 | 0 | writer->Add(l_version); |
1189 | 0 | } |
1190 | 0 | } |
1191 | 0 | if (authority().has_value() && |
1192 | 0 | *(authority()->title()) != *codeSpace()) { |
1193 | 0 | writer->AddObjKey("authority_citation"); |
1194 | 0 | writer->Add(*(authority()->title())); |
1195 | 0 | } |
1196 | 0 | if (uri().has_value()) { |
1197 | 0 | writer->AddObjKey("uri"); |
1198 | 0 | writer->Add(*(uri())); |
1199 | 0 | } |
1200 | 0 | } |
1201 | 0 | } |
1202 | | |
1203 | | //! @endcond |
1204 | | |
1205 | | // --------------------------------------------------------------------------- |
1206 | | |
1207 | | //! @cond Doxygen_Suppress |
1208 | 0 | static bool isIgnoredChar(char ch) { |
1209 | 0 | return ch == ' ' || ch == '_' || ch == '-' || ch == '/' || ch == '(' || |
1210 | 0 | ch == ')' || ch == '.' || ch == '&' || ch == ','; |
1211 | 0 | } |
1212 | | //! @endcond |
1213 | | |
1214 | | // --------------------------------------------------------------------------- |
1215 | | |
1216 | | //! @cond Doxygen_Suppress |
1217 | 0 | static char lower(char ch) { |
1218 | 0 | return ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch; |
1219 | 0 | } |
1220 | | //! @endcond |
1221 | | |
1222 | | // --------------------------------------------------------------------------- |
1223 | | |
1224 | | //! @cond Doxygen_Suppress |
1225 | | static const struct utf8_to_lower { |
1226 | | const char *utf8; |
1227 | | char ascii; |
1228 | | } map_utf8_to_lower[] = { |
1229 | | {"\xc3\xa1", 'a'}, // a acute |
1230 | | {"\xc3\xa4", 'a'}, // a tremma |
1231 | | |
1232 | | {"\xc4\x9b", 'e'}, // e reverse circumflex |
1233 | | {"\xc3\xa8", 'e'}, // e grave |
1234 | | {"\xc3\xa9", 'e'}, // e acute |
1235 | | {"\xc3\xab", 'e'}, // e tremma |
1236 | | |
1237 | | {"\xc3\xad", 'i'}, // i grave |
1238 | | |
1239 | | {"\xc3\xb4", 'o'}, // o circumflex |
1240 | | {"\xc3\xb6", 'o'}, // o tremma |
1241 | | |
1242 | | {"\xc3\xa7", 'c'}, // c cedilla |
1243 | | }; |
1244 | | |
1245 | 0 | static const struct utf8_to_lower *get_ascii_replacement(const char *c_str) { |
1246 | 0 | for (const auto &pair : map_utf8_to_lower) { |
1247 | 0 | if (*c_str == pair.utf8[0] && |
1248 | 0 | strncmp(c_str, pair.utf8, strlen(pair.utf8)) == 0) { |
1249 | 0 | return &pair; |
1250 | 0 | } |
1251 | 0 | } |
1252 | 0 | return nullptr; |
1253 | 0 | } |
1254 | | //! @endcond |
1255 | | |
1256 | | // --------------------------------------------------------------------------- |
1257 | | |
1258 | | //! @cond Doxygen_Suppress |
1259 | | |
1260 | | /** Checks if needle is a substring of c_str. |
1261 | | * |
1262 | | * e.g matchesLowerCase("JavaScript", "java") returns true |
1263 | | */ |
1264 | 0 | static bool matchesLowerCase(const char *c_str, const char *needle) { |
1265 | 0 | size_t i = 0; |
1266 | 0 | for (; c_str[i] && needle[i]; ++i) { |
1267 | 0 | if (lower(c_str[i]) != lower(needle[i])) { |
1268 | 0 | return false; |
1269 | 0 | } |
1270 | 0 | } |
1271 | 0 | return needle[i] == 0; |
1272 | 0 | } |
1273 | | //! @endcond |
1274 | | |
1275 | | // --------------------------------------------------------------------------- |
1276 | | |
1277 | | //! @cond Doxygen_Suppress |
1278 | | |
1279 | 0 | static inline bool isdigit(char ch) { return ch >= '0' && ch <= '9'; } |
1280 | | //! @endcond |
1281 | | |
1282 | | // --------------------------------------------------------------------------- |
1283 | | |
1284 | | //! @cond Doxygen_Suppress |
1285 | | std::string Identifier::canonicalizeName(const std::string &str, |
1286 | 0 | bool biggerDifferencesAllowed) { |
1287 | 0 | std::string res; |
1288 | 0 | const char *c_str = str.c_str(); |
1289 | 0 | for (size_t i = 0; c_str[i] != 0; ++i) { |
1290 | 0 | const auto ch = lower(c_str[i]); |
1291 | 0 | if (ch == ' ' && c_str[i + 1] == '+' && c_str[i + 2] == ' ') { |
1292 | 0 | i += 2; |
1293 | 0 | continue; |
1294 | 0 | } |
1295 | | |
1296 | | // Canonicalize "19dd" (where d is a digit) as "dd" |
1297 | 0 | if (ch == '1' && !res.empty() && !isdigit(res.back()) && |
1298 | 0 | c_str[i + 1] == '9' && isdigit(c_str[i + 2]) && |
1299 | 0 | isdigit(c_str[i + 3])) { |
1300 | 0 | ++i; |
1301 | 0 | continue; |
1302 | 0 | } |
1303 | | |
1304 | 0 | if (biggerDifferencesAllowed) { |
1305 | |
|
1306 | 0 | const auto skipSubstring = [](char l_ch, const char *l_str, |
1307 | 0 | size_t &idx, const char *substr) { |
1308 | 0 | if (l_ch == substr[0] && idx > 0 && |
1309 | 0 | isIgnoredChar(l_str[idx - 1]) && |
1310 | 0 | matchesLowerCase(l_str + idx, substr)) { |
1311 | 0 | idx += strlen(substr) - 1; |
1312 | 0 | return true; |
1313 | 0 | } |
1314 | 0 | return false; |
1315 | 0 | }; |
1316 | | |
1317 | | // Skip "zone" or "height" if preceding character is a space |
1318 | 0 | if (skipSubstring(ch, c_str, i, "zone") || |
1319 | 0 | skipSubstring(ch, c_str, i, "height")) { |
1320 | 0 | continue; |
1321 | 0 | } |
1322 | | |
1323 | | // Replace a substring by its first character if preceding character |
1324 | | // is a space or a digit |
1325 | 0 | const auto replaceByFirstChar = [](char l_ch, const char *l_str, |
1326 | 0 | size_t &idx, const char *substr, |
1327 | 0 | std::string &l_res) { |
1328 | 0 | if (l_ch == substr[0] && idx > 0 && |
1329 | 0 | (isIgnoredChar(l_str[idx - 1]) || |
1330 | 0 | isdigit(l_str[idx - 1])) && |
1331 | 0 | matchesLowerCase(l_str + idx, substr)) { |
1332 | 0 | l_res.push_back(l_ch); |
1333 | 0 | idx += strlen(substr) - 1; |
1334 | 0 | return true; |
1335 | 0 | } |
1336 | 0 | return false; |
1337 | 0 | }; |
1338 | | |
1339 | | // Replace "north" or "south" by its first character if preceding |
1340 | | // character is a space or a digit |
1341 | 0 | if (replaceByFirstChar(ch, c_str, i, "north", res) || |
1342 | 0 | replaceByFirstChar(ch, c_str, i, "south", res)) { |
1343 | 0 | continue; |
1344 | 0 | } |
1345 | 0 | } |
1346 | | |
1347 | 0 | if (static_cast<unsigned char>(ch) > 127) { |
1348 | 0 | const auto *replacement = get_ascii_replacement(c_str + i); |
1349 | 0 | if (replacement) { |
1350 | 0 | res.push_back(replacement->ascii); |
1351 | 0 | i += strlen(replacement->utf8) - 1; |
1352 | 0 | continue; |
1353 | 0 | } |
1354 | 0 | } |
1355 | | |
1356 | 0 | if (matchesLowerCase(c_str + i, "_IntlFeet") && |
1357 | 0 | c_str[i + strlen("_IntlFeet")] == 0) { |
1358 | 0 | res += "feet"; |
1359 | 0 | break; |
1360 | 0 | } |
1361 | | |
1362 | 0 | if (!isIgnoredChar(ch)) { |
1363 | 0 | res.push_back(ch); |
1364 | 0 | } |
1365 | 0 | } |
1366 | 0 | return res; |
1367 | 0 | } |
1368 | | //! @endcond |
1369 | | |
1370 | | // --------------------------------------------------------------------------- |
1371 | | |
1372 | | /** \brief Returns whether two names are considered equivalent. |
1373 | | * |
1374 | | * Two names are equivalent by removing any space, underscore, dash, slash, |
1375 | | * { or } character from them, and comparing in a case insensitive way. |
1376 | | * |
1377 | | * @param a first string |
1378 | | * @param b second string |
1379 | | * @param biggerDifferencesAllowed if true, "height" and "zone" words are |
1380 | | * ignored, and "north" is shortened as "n" and "south" as "n". |
1381 | | * @since 9.6 |
1382 | | */ |
1383 | | bool Identifier::isEquivalentName(const char *a, const char *b, |
1384 | 0 | bool biggerDifferencesAllowed) noexcept { |
1385 | 0 | size_t i = 0; |
1386 | 0 | size_t j = 0; |
1387 | 0 | char lastValidA = 0; |
1388 | 0 | char lastValidB = 0; |
1389 | 0 | while (a[i] != 0 || b[j] != 0) { |
1390 | 0 | char aCh = lower(a[i]); |
1391 | 0 | char bCh = lower(b[j]); |
1392 | 0 | if (aCh == ' ' && a[i + 1] == '+' && a[i + 2] == ' ' && a[i + 3] != 0) { |
1393 | 0 | i += 3; |
1394 | 0 | continue; |
1395 | 0 | } |
1396 | 0 | if (bCh == ' ' && b[j + 1] == '+' && b[j + 2] == ' ' && b[j + 3] != 0) { |
1397 | 0 | j += 3; |
1398 | 0 | continue; |
1399 | 0 | } |
1400 | | |
1401 | 0 | if (matchesLowerCase(a + i, "_IntlFeet") && |
1402 | 0 | a[i + strlen("_IntlFeet")] == 0 && |
1403 | 0 | matchesLowerCase(b + j, "_Feet") && b[j + strlen("_Feet")] == 0) { |
1404 | 0 | return true; |
1405 | 0 | } else if (matchesLowerCase(a + i, "_Feet") && |
1406 | 0 | a[i + strlen("_Feet")] == 0 && |
1407 | 0 | matchesLowerCase(b + j, "_IntlFeet") && |
1408 | 0 | b[j + strlen("_IntlFeet")] == 0) { |
1409 | 0 | return true; |
1410 | 0 | } |
1411 | | |
1412 | 0 | if (isIgnoredChar(aCh)) { |
1413 | 0 | ++i; |
1414 | 0 | continue; |
1415 | 0 | } |
1416 | 0 | if (isIgnoredChar(bCh)) { |
1417 | 0 | ++j; |
1418 | 0 | continue; |
1419 | 0 | } |
1420 | | |
1421 | | // Canonicalize "19dd" (where d is a digit) as "dd" |
1422 | 0 | if (aCh == '1' && !isdigit(lastValidA) && a[i + 1] == '9' && |
1423 | 0 | isdigit(a[i + 2]) && isdigit(a[i + 3])) { |
1424 | 0 | i += 2; |
1425 | 0 | lastValidA = '9'; |
1426 | 0 | continue; |
1427 | 0 | } |
1428 | 0 | if (bCh == '1' && !isdigit(lastValidB) && b[j + 1] == '9' && |
1429 | 0 | isdigit(b[j + 2]) && isdigit(b[j + 3])) { |
1430 | 0 | j += 2; |
1431 | 0 | lastValidB = '9'; |
1432 | 0 | continue; |
1433 | 0 | } |
1434 | | |
1435 | 0 | if (biggerDifferencesAllowed) { |
1436 | | // Skip a substring if preceding character is a space |
1437 | 0 | const auto skipSubString = [](char ch, const char *str, size_t &idx, |
1438 | 0 | const char *substr) { |
1439 | 0 | if (ch == substr[0] && idx > 0 && isIgnoredChar(str[idx - 1]) && |
1440 | 0 | matchesLowerCase(str + idx, substr)) { |
1441 | 0 | idx += strlen(substr); |
1442 | 0 | return true; |
1443 | 0 | } |
1444 | 0 | return false; |
1445 | 0 | }; |
1446 | |
|
1447 | 0 | bool skip = false; |
1448 | 0 | if (skipSubString(aCh, a, i, "zone")) |
1449 | 0 | skip = true; |
1450 | 0 | if (skipSubString(bCh, b, j, "zone")) |
1451 | 0 | skip = true; |
1452 | 0 | if (skip) |
1453 | 0 | continue; |
1454 | | |
1455 | 0 | if (skipSubString(aCh, a, i, "height")) |
1456 | 0 | skip = true; |
1457 | 0 | if (skipSubString(bCh, b, j, "height")) |
1458 | 0 | skip = true; |
1459 | 0 | if (skip) |
1460 | 0 | continue; |
1461 | | |
1462 | | // Replace a substring by its first character if preceding character |
1463 | | // is a space or a digit |
1464 | 0 | const auto replaceByFirstChar = [](char ch, const char *str, |
1465 | 0 | size_t &idx, |
1466 | 0 | const char *substr) { |
1467 | 0 | if (ch == substr[0] && idx > 0 && |
1468 | 0 | (isIgnoredChar(str[idx - 1]) || isdigit(str[idx - 1])) && |
1469 | 0 | matchesLowerCase(str + idx, substr)) { |
1470 | 0 | idx += strlen(substr) - 1; |
1471 | 0 | return true; |
1472 | 0 | } |
1473 | 0 | return false; |
1474 | 0 | }; |
1475 | |
|
1476 | 0 | if (!replaceByFirstChar(aCh, a, i, "north")) |
1477 | 0 | replaceByFirstChar(aCh, a, i, "south"); |
1478 | |
|
1479 | 0 | if (!replaceByFirstChar(bCh, b, j, "north")) |
1480 | 0 | replaceByFirstChar(bCh, b, j, "south"); |
1481 | 0 | } |
1482 | | |
1483 | 0 | if (static_cast<unsigned char>(aCh) > 127) { |
1484 | 0 | const auto *replacement = get_ascii_replacement(a + i); |
1485 | 0 | if (replacement) { |
1486 | 0 | aCh = replacement->ascii; |
1487 | 0 | i += strlen(replacement->utf8) - 1; |
1488 | 0 | } |
1489 | 0 | } |
1490 | 0 | if (static_cast<unsigned char>(bCh) > 127) { |
1491 | 0 | const auto *replacement = get_ascii_replacement(b + j); |
1492 | 0 | if (replacement) { |
1493 | 0 | bCh = replacement->ascii; |
1494 | 0 | j += strlen(replacement->utf8) - 1; |
1495 | 0 | } |
1496 | 0 | } |
1497 | |
|
1498 | 0 | if (aCh != bCh) { |
1499 | 0 | return false; |
1500 | 0 | } |
1501 | 0 | lastValidA = aCh; |
1502 | 0 | lastValidB = bCh; |
1503 | 0 | if (aCh != 0) |
1504 | 0 | ++i; |
1505 | 0 | if (bCh != 0) |
1506 | 0 | ++j; |
1507 | 0 | } |
1508 | 0 | return true; |
1509 | 0 | } |
1510 | | |
1511 | | // --------------------------------------------------------------------------- |
1512 | | |
1513 | | /** \brief Returns whether two names are considered equivalent. |
1514 | | * |
1515 | | * Two names are equivalent by removing any space, underscore, dash, slash, |
1516 | | * { or } character from them, and comparing in a case insensitive way. |
1517 | | */ |
1518 | 0 | bool Identifier::isEquivalentName(const char *a, const char *b) noexcept { |
1519 | 0 | return isEquivalentName(a, b, /* biggerDifferencesAllowed = */ true); |
1520 | 0 | } |
1521 | | |
1522 | | // --------------------------------------------------------------------------- |
1523 | | |
1524 | | //! @cond Doxygen_Suppress |
1525 | | struct PositionalAccuracy::Private { |
1526 | | std::string value_{}; |
1527 | | }; |
1528 | | //! @endcond |
1529 | | |
1530 | | // --------------------------------------------------------------------------- |
1531 | | |
1532 | | PositionalAccuracy::PositionalAccuracy(const std::string &valueIn) |
1533 | 0 | : d(std::make_unique<Private>()) { |
1534 | 0 | d->value_ = valueIn; |
1535 | 0 | } |
1536 | | |
1537 | | // --------------------------------------------------------------------------- |
1538 | | |
1539 | | //! @cond Doxygen_Suppress |
1540 | 0 | PositionalAccuracy::~PositionalAccuracy() = default; |
1541 | | //! @endcond |
1542 | | |
1543 | | // --------------------------------------------------------------------------- |
1544 | | |
1545 | | /** \brief Return the value of the positional accuracy. |
1546 | | */ |
1547 | 0 | const std::string &PositionalAccuracy::value() PROJ_PURE_DEFN { |
1548 | 0 | return d->value_; |
1549 | 0 | } |
1550 | | |
1551 | | // --------------------------------------------------------------------------- |
1552 | | |
1553 | | /** \brief Instantiate a PositionalAccuracy. |
1554 | | * |
1555 | | * @param valueIn positional accuracy value. |
1556 | | * @return a new PositionalAccuracy. |
1557 | | */ |
1558 | 0 | PositionalAccuracyNNPtr PositionalAccuracy::create(const std::string &valueIn) { |
1559 | 0 | return PositionalAccuracy::nn_make_shared<PositionalAccuracy>(valueIn); |
1560 | 0 | } |
1561 | | |
1562 | | } // namespace metadata |
1563 | | NS_PROJ_END |