Coverage Report

Created: 2024-02-25 06:14

/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
2.22M
Citation::Citation() : d(internal::make_unique<Private>()) {}
82
//! @endcond
83
84
// ---------------------------------------------------------------------------
85
86
/** \brief Constructs a citation by its title. */
87
Citation::Citation(const std::string &titleIn)
88
6.79k
    : d(internal::make_unique<Private>()) {
89
6.79k
    d->title = titleIn;
90
6.79k
}
91
92
// ---------------------------------------------------------------------------
93
94
//! @cond Doxygen_Suppress
95
Citation::Citation(const Citation &other)
96
0
    : d(internal::make_unique<Private>(*(other.d))) {}
97
98
// ---------------------------------------------------------------------------
99
100
2.22M
Citation::~Citation() = default;
101
102
// ---------------------------------------------------------------------------
103
104
6.79k
Citation &Citation::operator=(const Citation &other) {
105
6.79k
    if (this != &other) {
106
6.79k
        *d = *other.d;
107
6.79k
    }
108
6.79k
    return *this;
109
6.79k
}
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
1.45k
GeographicExtent::GeographicExtent() : d(internal::make_unique<Private>()) {}
128
129
// ---------------------------------------------------------------------------
130
131
//! @cond Doxygen_Suppress
132
1.45k
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
1.89k
        : 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
    : GeographicExtent(),
158
1.45k
      d(internal::make_unique<Private>(west, south, east, north)) {}
159
160
// ---------------------------------------------------------------------------
161
162
//! @cond Doxygen_Suppress
163
1.45k
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
7.02k
double GeographicBoundingBox::westBoundLongitude() PROJ_PURE_DEFN {
177
7.02k
    return d->west_;
178
7.02k
}
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
7.02k
double GeographicBoundingBox::southBoundLatitude() PROJ_PURE_DEFN {
188
7.02k
    return d->south_;
189
7.02k
}
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
7.02k
double GeographicBoundingBox::eastBoundLongitude() PROJ_PURE_DEFN {
202
7.02k
    return d->east_;
203
7.02k
}
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
7.02k
double GeographicBoundingBox::northBoundLatitude() PROJ_PURE_DEFN {
213
7.02k
    return d->north_;
214
7.02k
}
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
1.45k
                                                         double north) {
236
1.45k
    if (std::isnan(west) || std::isnan(south) || std::isnan(east) ||
237
1.45k
        std::isnan(north)) {
238
0
        throw InvalidValueTypeException(
239
0
            "GeographicBoundingBox::create() does not accept NaN values");
240
0
    }
241
1.45k
    return GeographicBoundingBox::nn_make_shared<GeographicBoundingBox>(
242
1.45k
        west, south, east, north);
243
1.45k
}
244
245
// ---------------------------------------------------------------------------
246
247
//! @cond Doxygen_Suppress
248
bool GeographicBoundingBox::_isEquivalentTo(
249
    const util::IComparable *other, util::IComparable::Criterion,
250
587
    const io::DatabaseContextPtr &) const {
251
587
    auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other);
252
587
    if (!otherExtent)
253
0
        return false;
254
587
    return d->west_ == otherExtent->d->west_ &&
255
587
           d->south_ == otherExtent->d->south_ &&
256
587
           d->east_ == otherExtent->d->east_ &&
257
587
           d->north_ == otherExtent->d->north_;
258
587
}
259
//! @endcond
260
261
// ---------------------------------------------------------------------------
262
263
9.98k
bool GeographicBoundingBox::contains(const GeographicExtentNNPtr &other) const {
264
9.98k
    auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get());
265
9.98k
    if (!otherExtent) {
266
0
        return false;
267
0
    }
268
9.98k
    const double W = d->west_;
269
9.98k
    const double E = d->east_;
270
9.98k
    const double N = d->north_;
271
9.98k
    const double S = d->south_;
272
9.98k
    const double oW = otherExtent->d->west_;
273
9.98k
    const double oE = otherExtent->d->east_;
274
9.98k
    const double oN = otherExtent->d->north_;
275
9.98k
    const double oS = otherExtent->d->south_;
276
277
9.98k
    if (!(S <= oS && N >= oN)) {
278
651
        return false;
279
651
    }
280
281
9.33k
    if (W == -180.0 && E == 180.0) {
282
8.44k
        return oW != oE;
283
8.44k
    }
284
285
886
    if (oW == -180.0 && oE == 180.0) {
286
2
        return false;
287
2
    }
288
289
    // Normal bounding box ?
290
884
    if (W < E) {
291
223
        if (oW < oE) {
292
60
            return W <= oW && E >= oE;
293
163
        } else {
294
163
            return false;
295
163
        }
296
        // No: crossing antimerian
297
661
    } else {
298
661
        if (oW < oE) {
299
146
            if (oW >= W) {
300
36
                return true;
301
110
            } else if (oE <= E) {
302
70
                return true;
303
70
            } else {
304
40
                return false;
305
40
            }
306
515
        } else {
307
515
            return W <= oW && E >= oE;
308
515
        }
309
661
    }
310
884
}
311
312
// ---------------------------------------------------------------------------
313
314
//! @cond Doxygen_Suppress
315
353
bool GeographicBoundingBox::Private::intersects(const Private &other) const {
316
353
    const double W = west_;
317
353
    const double E = east_;
318
353
    const double N = north_;
319
353
    const double S = south_;
320
353
    const double oW = other.west_;
321
353
    const double oE = other.east_;
322
353
    const double oN = other.north_;
323
353
    const double oS = other.south_;
324
325
353
    if (N < oS || S > oN) {
326
18
        return false;
327
18
    }
328
329
335
    if (W == -180.0 && E == 180.0 && oW > oE) {
330
176
        return true;
331
176
    }
332
333
159
    if (oW == -180.0 && oE == 180.0 && W > E) {
334
0
        return true;
335
0
    }
336
337
    // Normal bounding box ?
338
159
    if (W <= E) {
339
48
        if (oW <= oE) {
340
40
            if (std::max(W, oW) < std::min(E, oE)) {
341
34
                return true;
342
34
            }
343
6
            return false;
344
40
        }
345
346
        // Bail out on longitudes not in [-180,180]. We could probably make
347
        // some sense of them, but this check at least avoid potential infinite
348
        // recursion.
349
8
        if (oW > 180 || oE < -180) {
350
2
            return false;
351
2
        }
352
353
6
        return intersects(Private(oW, oS, 180.0, oN)) ||
354
6
               intersects(Private(-180.0, oS, oE, oN));
355
356
        // No: crossing antimerian
357
111
    } else {
358
111
        if (oW <= oE) {
359
7
            return other.intersects(*this);
360
7
        }
361
362
104
        return true;
363
111
    }
364
159
}
365
//! @endcond
366
367
bool GeographicBoundingBox::intersects(
368
337
    const GeographicExtentNNPtr &other) const {
369
337
    auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get());
370
337
    if (!otherExtent) {
371
0
        return false;
372
0
    }
373
337
    return d->intersects(*(otherExtent->d));
374
337
}
375
376
// ---------------------------------------------------------------------------
377
378
GeographicExtentPtr
379
296
GeographicBoundingBox::intersection(const GeographicExtentNNPtr &other) const {
380
296
    auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get());
381
296
    if (!otherExtent) {
382
0
        return nullptr;
383
0
    }
384
296
    auto ret = d->intersection(*(otherExtent->d));
385
296
    if (ret) {
386
216
        auto bbox = GeographicBoundingBox::create(ret->west_, ret->south_,
387
216
                                                  ret->east_, ret->north_);
388
216
        return bbox.as_nullable();
389
216
    }
390
80
    return nullptr;
391
296
}
392
393
//! @cond Doxygen_Suppress
394
std::unique_ptr<GeographicBoundingBox::Private>
395
528
GeographicBoundingBox::Private::intersection(const Private &otherExtent) const {
396
528
    const double W = west_;
397
528
    const double E = east_;
398
528
    const double N = north_;
399
528
    const double S = south_;
400
528
    const double oW = otherExtent.west_;
401
528
    const double oE = otherExtent.east_;
402
528
    const double oN = otherExtent.north_;
403
528
    const double oS = otherExtent.south_;
404
405
528
    if (N < oS || S > oN) {
406
32
        return nullptr;
407
32
    }
408
409
496
    if (W == -180.0 && E == 180.0 && oW > oE) {
410
6
        return internal::make_unique<Private>(oW, std::max(S, oS), oE,
411
6
                                              std::min(N, oN));
412
6
    }
413
414
490
    if (oW == -180.0 && oE == 180.0 && W > E) {
415
21
        return internal::make_unique<Private>(W, std::max(S, oS), E,
416
21
                                              std::min(N, oN));
417
21
    }
418
419
    // Normal bounding box ?
420
469
    if (W <= E) {
421
354
        if (oW <= oE) {
422
231
            const double resW = std::max(W, oW);
423
231
            const double resE = std::min(E, oE);
424
231
            if (resW < resE) {
425
142
                return internal::make_unique<Private>(resW, std::max(S, oS),
426
142
                                                      resE, std::min(N, oN));
427
142
            }
428
89
            return nullptr;
429
231
        }
430
431
        // Bail out on longitudes not in [-180,180]. We could probably make
432
        // some sense of them, but this check at least avoid potential infinite
433
        // recursion.
434
123
        if (oW > 180 || oE < -180) {
435
28
            return nullptr;
436
28
        }
437
438
        // Return larger of two parts of the multipolygon
439
95
        auto inter1 = intersection(Private(oW, oS, 180.0, oN));
440
95
        auto inter2 = intersection(Private(-180.0, oS, oE, oN));
441
95
        if (!inter1) {
442
28
            return inter2;
443
28
        }
444
67
        if (!inter2) {
445
41
            return inter1;
446
41
        }
447
26
        if (inter1->east_ - inter1->west_ > inter2->east_ - inter2->west_) {
448
2
            return inter1;
449
2
        }
450
24
        return inter2;
451
        // No: crossing antimerian
452
115
    } else {
453
115
        if (oW <= oE) {
454
42
            return otherExtent.intersection(*this);
455
42
        }
456
457
73
        return internal::make_unique<Private>(std::max(W, oW), std::max(S, oS),
458
73
                                              std::min(E, oE), std::min(N, oN));
459
115
    }
460
469
}
461
//! @endcond
462
463
// ---------------------------------------------------------------------------
464
465
//! @cond Doxygen_Suppress
466
struct VerticalExtent::Private {
467
    double minimum_{};
468
    double maximum_{};
469
    common::UnitOfMeasureNNPtr unit_;
470
471
    Private(double minimum, double maximum,
472
            const common::UnitOfMeasureNNPtr &unit)
473
91
        : minimum_(minimum), maximum_(maximum), unit_(unit) {}
474
};
475
//! @endcond
476
477
// ---------------------------------------------------------------------------
478
479
VerticalExtent::VerticalExtent(double minimumIn, double maximumIn,
480
                               const common::UnitOfMeasureNNPtr &unitIn)
481
91
    : d(internal::make_unique<Private>(minimumIn, maximumIn, unitIn)) {}
482
483
// ---------------------------------------------------------------------------
484
485
//! @cond Doxygen_Suppress
486
91
VerticalExtent::~VerticalExtent() = default;
487
//! @endcond
488
489
// ---------------------------------------------------------------------------
490
491
/** \brief Returns the minimum of the vertical extent.
492
 */
493
0
double VerticalExtent::minimumValue() PROJ_PURE_DEFN { return d->minimum_; }
494
495
// ---------------------------------------------------------------------------
496
497
/** \brief Returns the maximum of the vertical extent.
498
 */
499
0
double VerticalExtent::maximumValue() PROJ_PURE_DEFN { return d->maximum_; }
500
501
// ---------------------------------------------------------------------------
502
503
/** \brief Returns the unit of the vertical extent.
504
 */
505
0
common::UnitOfMeasureNNPtr &VerticalExtent::unit() PROJ_PURE_DEFN {
506
0
    return d->unit_;
507
0
}
508
509
// ---------------------------------------------------------------------------
510
511
/** \brief Instantiate a VerticalExtent.
512
 *
513
 * @param minimumIn minimum.
514
 * @param maximumIn maximum.
515
 * @param unitIn unit.
516
 * @return a new VerticalExtent.
517
 */
518
VerticalExtentNNPtr
519
VerticalExtent::create(double minimumIn, double maximumIn,
520
91
                       const common::UnitOfMeasureNNPtr &unitIn) {
521
91
    return VerticalExtent::nn_make_shared<VerticalExtent>(minimumIn, maximumIn,
522
91
                                                          unitIn);
523
91
}
524
525
// ---------------------------------------------------------------------------
526
527
//! @cond Doxygen_Suppress
528
bool VerticalExtent::_isEquivalentTo(const util::IComparable *other,
529
                                     util::IComparable::Criterion,
530
94
                                     const io::DatabaseContextPtr &) const {
531
94
    auto otherExtent = dynamic_cast<const VerticalExtent *>(other);
532
94
    if (!otherExtent)
533
0
        return false;
534
94
    return d->minimum_ == otherExtent->d->minimum_ &&
535
94
           d->maximum_ == otherExtent->d->maximum_ &&
536
94
           d->unit_ == otherExtent->d->unit_;
537
94
}
538
//! @endcond
539
540
// ---------------------------------------------------------------------------
541
542
/** \brief Returns whether this extent contains the other one.
543
 */
544
71
bool VerticalExtent::contains(const VerticalExtentNNPtr &other) const {
545
71
    const double thisUnitToSI = d->unit_->conversionToSI();
546
71
    const double otherUnitToSI = other->d->unit_->conversionToSI();
547
71
    return d->minimum_ * thisUnitToSI <= other->d->minimum_ * otherUnitToSI &&
548
71
           d->maximum_ * thisUnitToSI >= other->d->maximum_ * otherUnitToSI;
549
71
}
550
551
// ---------------------------------------------------------------------------
552
553
/** \brief Returns whether this extent intersects the other one.
554
 */
555
16
bool VerticalExtent::intersects(const VerticalExtentNNPtr &other) const {
556
16
    const double thisUnitToSI = d->unit_->conversionToSI();
557
16
    const double otherUnitToSI = other->d->unit_->conversionToSI();
558
16
    return d->minimum_ * thisUnitToSI <= other->d->maximum_ * otherUnitToSI &&
559
16
           d->maximum_ * thisUnitToSI >= other->d->minimum_ * otherUnitToSI;
560
16
}
561
562
// ---------------------------------------------------------------------------
563
564
//! @cond Doxygen_Suppress
565
struct TemporalExtent::Private {
566
    std::string start_{};
567
    std::string stop_{};
568
569
    Private(const std::string &start, const std::string &stop)
570
426
        : start_(start), stop_(stop) {}
571
};
572
//! @endcond
573
574
// ---------------------------------------------------------------------------
575
576
TemporalExtent::TemporalExtent(const std::string &startIn,
577
                               const std::string &stopIn)
578
426
    : d(internal::make_unique<Private>(startIn, stopIn)) {}
579
580
// ---------------------------------------------------------------------------
581
582
//! @cond Doxygen_Suppress
583
426
TemporalExtent::~TemporalExtent() = default;
584
//! @endcond
585
586
// ---------------------------------------------------------------------------
587
588
/** \brief Returns the start of the temporal extent.
589
 */
590
937
const std::string &TemporalExtent::start() PROJ_PURE_DEFN { return d->start_; }
591
592
// ---------------------------------------------------------------------------
593
594
/** \brief Returns the end of the temporal extent.
595
 */
596
827
const std::string &TemporalExtent::stop() PROJ_PURE_DEFN { return d->stop_; }
597
598
// ---------------------------------------------------------------------------
599
600
/** \brief Instantiate a TemporalExtent.
601
 *
602
 * @param start start.
603
 * @param stop stop.
604
 * @return a new TemporalExtent.
605
 */
606
TemporalExtentNNPtr TemporalExtent::create(const std::string &start,
607
426
                                           const std::string &stop) {
608
426
    return TemporalExtent::nn_make_shared<TemporalExtent>(start, stop);
609
426
}
610
611
// ---------------------------------------------------------------------------
612
613
//! @cond Doxygen_Suppress
614
bool TemporalExtent::_isEquivalentTo(const util::IComparable *other,
615
                                     util::IComparable::Criterion,
616
248
                                     const io::DatabaseContextPtr &) const {
617
248
    auto otherExtent = dynamic_cast<const TemporalExtent *>(other);
618
248
    if (!otherExtent)
619
0
        return false;
620
248
    return start() == otherExtent->start() && stop() == otherExtent->stop();
621
248
}
622
//! @endcond
623
624
// ---------------------------------------------------------------------------
625
626
/** \brief Returns whether this extent contains the other one.
627
 */
628
182
bool TemporalExtent::contains(const TemporalExtentNNPtr &other) const {
629
182
    return start() <= other->start() && stop() >= other->stop();
630
182
}
631
632
// ---------------------------------------------------------------------------
633
634
/** \brief Returns whether this extent intersects the other one.
635
 */
636
51
bool TemporalExtent::intersects(const TemporalExtentNNPtr &other) const {
637
51
    return start() <= other->stop() && stop() >= other->start();
638
51
}
639
640
// ---------------------------------------------------------------------------
641
642
//! @cond Doxygen_Suppress
643
struct Extent::Private {
644
    optional<std::string> description_{};
645
    std::vector<GeographicExtentNNPtr> geographicElements_{};
646
    std::vector<VerticalExtentNNPtr> verticalElements_{};
647
    std::vector<TemporalExtentNNPtr> temporalElements_{};
648
};
649
//! @endcond
650
651
// ---------------------------------------------------------------------------
652
653
//! @cond Doxygen_Suppress
654
1.93k
Extent::Extent() : d(internal::make_unique<Private>()) {}
655
656
// ---------------------------------------------------------------------------
657
658
Extent::Extent(const Extent &other)
659
0
    : d(internal::make_unique<Private>(*other.d)) {}
660
661
// ---------------------------------------------------------------------------
662
663
1.93k
Extent::~Extent() = default;
664
//! @endcond
665
666
// ---------------------------------------------------------------------------
667
668
/** Return a textual description of the extent.
669
 *
670
 * @return the description, or empty.
671
 */
672
2.97k
const optional<std::string> &Extent::description() PROJ_PURE_DEFN {
673
2.97k
    return d->description_;
674
2.97k
}
675
676
// ---------------------------------------------------------------------------
677
678
/** Return the geographic element(s) of the extent
679
 *
680
 * @return the geographic element(s), or empty.
681
 */
682
const std::vector<GeographicExtentNNPtr> &
683
7.05k
Extent::geographicElements() PROJ_PURE_DEFN {
684
7.05k
    return d->geographicElements_;
685
7.05k
}
686
687
// ---------------------------------------------------------------------------
688
689
/** Return the vertical element(s) of the extent
690
 *
691
 * @return the vertical element(s), or empty.
692
 */
693
const std::vector<VerticalExtentNNPtr> &
694
0
Extent::verticalElements() PROJ_PURE_DEFN {
695
0
    return d->verticalElements_;
696
0
}
697
698
// ---------------------------------------------------------------------------
699
700
/** Return the temporal element(s) of the extent
701
 *
702
 * @return the temporal element(s), or empty.
703
 */
704
const std::vector<TemporalExtentNNPtr> &
705
0
Extent::temporalElements() PROJ_PURE_DEFN {
706
0
    return d->temporalElements_;
707
0
}
708
709
// ---------------------------------------------------------------------------
710
711
/** \brief Instantiate a Extent.
712
 *
713
 * @param descriptionIn Textual description, or empty.
714
 * @param geographicElementsIn Geographic element(s), or empty.
715
 * @param verticalElementsIn Vertical element(s), or empty.
716
 * @param temporalElementsIn Temporal element(s), or empty.
717
 * @return a new Extent.
718
 */
719
ExtentNNPtr
720
Extent::create(const optional<std::string> &descriptionIn,
721
               const std::vector<GeographicExtentNNPtr> &geographicElementsIn,
722
               const std::vector<VerticalExtentNNPtr> &verticalElementsIn,
723
1.93k
               const std::vector<TemporalExtentNNPtr> &temporalElementsIn) {
724
1.93k
    auto extent = Extent::nn_make_shared<Extent>();
725
1.93k
    extent->assignSelf(extent);
726
1.93k
    extent->d->description_ = descriptionIn;
727
1.93k
    extent->d->geographicElements_ = geographicElementsIn;
728
1.93k
    extent->d->verticalElements_ = verticalElementsIn;
729
1.93k
    extent->d->temporalElements_ = temporalElementsIn;
730
1.93k
    return extent;
731
1.93k
}
732
733
// ---------------------------------------------------------------------------
734
735
/** \brief Instantiate a Extent from a bounding box
736
 *
737
 * @param west Western-most coordinate of the limit of the dataset extent (in
738
 * degrees).
739
 * @param south Southern-most coordinate of the limit of the dataset extent (in
740
 * degrees).
741
 * @param east Eastern-most coordinate of the limit of the dataset extent (in
742
 * degrees).
743
 * @param north Northern-most coordinate of the limit of the dataset extent (in
744
 * degrees).
745
 * @param descriptionIn Textual description, or empty.
746
 * @return a new Extent.
747
 */
748
ExtentNNPtr
749
Extent::createFromBBOX(double west, double south, double east, double north,
750
2
                       const util::optional<std::string> &descriptionIn) {
751
2
    return create(
752
2
        descriptionIn,
753
2
        std::vector<GeographicExtentNNPtr>{
754
2
            nn_static_pointer_cast<GeographicExtent>(
755
2
                GeographicBoundingBox::create(west, south, east, north))},
756
2
        std::vector<VerticalExtentNNPtr>(), std::vector<TemporalExtentNNPtr>());
757
2
}
758
759
// ---------------------------------------------------------------------------
760
761
//! @cond Doxygen_Suppress
762
bool Extent::_isEquivalentTo(const util::IComparable *other,
763
                             util::IComparable::Criterion criterion,
764
756
                             const io::DatabaseContextPtr &dbContext) const {
765
756
    auto otherExtent = dynamic_cast<const Extent *>(other);
766
756
    bool ret =
767
756
        (otherExtent &&
768
756
         description().has_value() == otherExtent->description().has_value() &&
769
756
         *description() == *otherExtent->description() &&
770
756
         d->geographicElements_.size() ==
771
694
             otherExtent->d->geographicElements_.size() &&
772
756
         d->verticalElements_.size() ==
773
691
             otherExtent->d->verticalElements_.size() &&
774
756
         d->temporalElements_.size() ==
775
669
             otherExtent->d->temporalElements_.size());
776
756
    if (ret) {
777
1.22k
        for (size_t i = 0; ret && i < d->geographicElements_.size(); ++i) {
778
587
            ret = d->geographicElements_[i]->_isEquivalentTo(
779
587
                otherExtent->d->geographicElements_[i].get(), criterion,
780
587
                dbContext);
781
587
        }
782
735
        for (size_t i = 0; ret && i < d->verticalElements_.size(); ++i) {
783
94
            ret = d->verticalElements_[i]->_isEquivalentTo(
784
94
                otherExtent->d->verticalElements_[i].get(), criterion,
785
94
                dbContext);
786
94
        }
787
889
        for (size_t i = 0; ret && i < d->temporalElements_.size(); ++i) {
788
248
            ret = d->temporalElements_[i]->_isEquivalentTo(
789
248
                otherExtent->d->temporalElements_[i].get(), criterion,
790
248
                dbContext);
791
248
        }
792
641
    }
793
756
    return ret;
794
756
}
795
//! @endcond
796
797
// ---------------------------------------------------------------------------
798
799
/** \brief Returns whether this extent contains the other one.
800
 *
801
 * Behavior only well specified if each sub-extent category as at most
802
 * one element.
803
 */
804
10.0k
bool Extent::contains(const ExtentNNPtr &other) const {
805
10.0k
    bool res = true;
806
10.0k
    if (d->geographicElements_.size() == 1 &&
807
10.0k
        other->d->geographicElements_.size() == 1) {
808
9.98k
        res = d->geographicElements_[0]->contains(
809
9.98k
            other->d->geographicElements_[0]);
810
9.98k
    }
811
10.0k
    if (res && d->verticalElements_.size() == 1 &&
812
10.0k
        other->d->verticalElements_.size() == 1) {
813
71
        res = d->verticalElements_[0]->contains(other->d->verticalElements_[0]);
814
71
    }
815
10.0k
    if (res && d->temporalElements_.size() == 1 &&
816
10.0k
        other->d->temporalElements_.size() == 1) {
817
182
        res = d->temporalElements_[0]->contains(other->d->temporalElements_[0]);
818
182
    }
819
10.0k
    return res;
820
10.0k
}
821
822
// ---------------------------------------------------------------------------
823
824
/** \brief Returns whether this extent intersects the other one.
825
 *
826
 * Behavior only well specified if each sub-extent category as at most
827
 * one element.
828
 */
829
358
bool Extent::intersects(const ExtentNNPtr &other) const {
830
358
    bool res = true;
831
358
    if (d->geographicElements_.size() == 1 &&
832
358
        other->d->geographicElements_.size() == 1) {
833
337
        res = d->geographicElements_[0]->intersects(
834
337
            other->d->geographicElements_[0]);
835
337
    }
836
358
    if (res && d->verticalElements_.size() == 1 &&
837
358
        other->d->verticalElements_.size() == 1) {
838
16
        res =
839
16
            d->verticalElements_[0]->intersects(other->d->verticalElements_[0]);
840
16
    }
841
358
    if (res && d->temporalElements_.size() == 1 &&
842
358
        other->d->temporalElements_.size() == 1) {
843
51
        res =
844
51
            d->temporalElements_[0]->intersects(other->d->temporalElements_[0]);
845
51
    }
846
358
    return res;
847
358
}
848
849
// ---------------------------------------------------------------------------
850
851
/** \brief Returns the intersection of this extent with another one.
852
 *
853
 * Behavior only well specified if there is one single GeographicExtent
854
 * in each object.
855
 * Returns nullptr otherwise.
856
 */
857
8.96k
ExtentPtr Extent::intersection(const ExtentNNPtr &other) const {
858
8.96k
    if (d->geographicElements_.size() == 1 &&
859
8.96k
        other->d->geographicElements_.size() == 1) {
860
8.91k
        if (contains(other)) {
861
8.18k
            return other.as_nullable();
862
8.18k
        }
863
730
        auto self = util::nn_static_pointer_cast<Extent>(shared_from_this());
864
730
        if (other->contains(self)) {
865
434
            return self.as_nullable();
866
434
        }
867
296
        auto geogIntersection = d->geographicElements_[0]->intersection(
868
296
            other->d->geographicElements_[0]);
869
296
        if (geogIntersection) {
870
216
            return create(util::optional<std::string>(),
871
216
                          std::vector<GeographicExtentNNPtr>{
872
216
                              NN_NO_CHECK(geogIntersection)},
873
216
                          std::vector<VerticalExtentNNPtr>{},
874
216
                          std::vector<TemporalExtentNNPtr>{});
875
216
        }
876
296
    }
877
125
    return nullptr;
878
8.96k
}
879
880
// ---------------------------------------------------------------------------
881
882
//! @cond Doxygen_Suppress
883
struct Identifier::Private {
884
    optional<Citation> authority_{};
885
    std::string code_{};
886
    optional<std::string> codeSpace_{};
887
    optional<std::string> version_{};
888
    optional<std::string> description_{};
889
    optional<std::string> uri_{};
890
891
909k
    Private() = default;
892
893
    Private(const std::string &codeIn, const PropertyMap &properties)
894
1.19M
        : code_(codeIn) {
895
1.19M
        setProperties(properties);
896
1.19M
    }
897
898
  private:
899
    // cppcheck-suppress functionStatic
900
    void setProperties(const PropertyMap &properties);
901
};
902
903
// ---------------------------------------------------------------------------
904
905
void Identifier::Private::setProperties(
906
    const PropertyMap &properties) // throw(InvalidValueTypeException)
907
1.19M
{
908
1.19M
    {
909
1.19M
        const auto pVal = properties.get(AUTHORITY_KEY);
910
1.19M
        if (pVal) {
911
6.79k
            if (auto genVal = dynamic_cast<const BoxedValue *>(pVal->get())) {
912
6.79k
                if (genVal->type() == BoxedValue::Type::STRING) {
913
6.79k
                    authority_ = Citation(genVal->stringValue());
914
6.79k
                } else {
915
0
                    throw InvalidValueTypeException("Invalid value type for " +
916
0
                                                    AUTHORITY_KEY);
917
0
                }
918
6.79k
            } else {
919
0
                if (auto citation =
920
0
                        dynamic_cast<const Citation *>(pVal->get())) {
921
0
                    authority_ = *citation;
922
0
                } else {
923
0
                    throw InvalidValueTypeException("Invalid value type for " +
924
0
                                                    AUTHORITY_KEY);
925
0
                }
926
0
            }
927
6.79k
        }
928
1.19M
    }
929
930
1.19M
    {
931
1.19M
        const auto pVal = properties.get(CODE_KEY);
932
1.19M
        if (pVal) {
933
144k
            if (auto genVal = dynamic_cast<const BoxedValue *>(pVal->get())) {
934
144k
                if (genVal->type() == BoxedValue::Type::INTEGER) {
935
144k
                    code_ = toString(genVal->integerValue());
936
144k
                } else if (genVal->type() == BoxedValue::Type::STRING) {
937
5
                    code_ = genVal->stringValue();
938
5
                } else {
939
0
                    throw InvalidValueTypeException("Invalid value type for " +
940
0
                                                    CODE_KEY);
941
0
                }
942
144k
            } else {
943
0
                throw InvalidValueTypeException("Invalid value type for " +
944
0
                                                CODE_KEY);
945
0
            }
946
144k
        }
947
1.19M
    }
948
949
1.19M
    properties.getStringValue(CODESPACE_KEY, codeSpace_);
950
1.19M
    properties.getStringValue(VERSION_KEY, version_);
951
1.19M
    properties.getStringValue(DESCRIPTION_KEY, description_);
952
1.19M
    properties.getStringValue(URI_KEY, uri_);
953
1.19M
}
954
955
//! @endcond
956
957
// ---------------------------------------------------------------------------
958
959
Identifier::Identifier(const std::string &codeIn,
960
                       const util::PropertyMap &properties)
961
1.19M
    : d(internal::make_unique<Private>(codeIn, properties)) {}
962
963
// ---------------------------------------------------------------------------
964
965
//! @cond Doxygen_Suppress
966
967
// ---------------------------------------------------------------------------
968
969
909k
Identifier::Identifier() : d(internal::make_unique<Private>()) {}
970
971
// ---------------------------------------------------------------------------
972
973
Identifier::Identifier(const Identifier &other)
974
0
    : d(internal::make_unique<Private>(*(other.d))) {}
975
976
// ---------------------------------------------------------------------------
977
978
2.10M
Identifier::~Identifier() = default;
979
//! @endcond
980
981
// ---------------------------------------------------------------------------
982
983
/** \brief Instantiate a Identifier.
984
 *
985
 * @param codeIn Alphanumeric value identifying an instance in the codespace
986
 * @param properties See \ref general_properties.
987
 * Generally, the Identifier::CODESPACE_KEY should be set.
988
 * @return a new Identifier.
989
 */
990
IdentifierNNPtr Identifier::create(const std::string &codeIn,
991
1.19M
                                   const PropertyMap &properties) {
992
1.19M
    return Identifier::nn_make_shared<Identifier>(codeIn, properties);
993
1.19M
}
994
995
// ---------------------------------------------------------------------------
996
997
//! @cond Doxygen_Suppress
998
IdentifierNNPtr
999
909k
Identifier::createFromDescription(const std::string &descriptionIn) {
1000
909k
    auto id = Identifier::nn_make_shared<Identifier>();
1001
909k
    id->d->description_ = descriptionIn;
1002
909k
    return id;
1003
909k
}
1004
//! @endcond
1005
1006
// ---------------------------------------------------------------------------
1007
1008
/** \brief Return a citation for the organization responsible for definition and
1009
 * maintenance of the code.
1010
 *
1011
 * @return the citation for the authority, or empty.
1012
 */
1013
0
const optional<Citation> &Identifier::authority() PROJ_PURE_DEFN {
1014
0
    return d->authority_;
1015
0
}
1016
1017
// ---------------------------------------------------------------------------
1018
1019
/** \brief Return the alphanumeric value identifying an instance in the
1020
 * codespace.
1021
 *
1022
 * e.g. "4326" (for EPSG:4326 WGS 84 GeographicCRS)
1023
 *
1024
 * @return the code.
1025
 */
1026
460k
const std::string &Identifier::code() PROJ_PURE_DEFN { return d->code_; }
1027
1028
// ---------------------------------------------------------------------------
1029
1030
/** \brief Return the organization responsible for definition and maintenance of
1031
 * the code.
1032
 *
1033
 * e.g "EPSG"
1034
 *
1035
 * @return the authority codespace, or empty.
1036
 */
1037
461k
const optional<std::string> &Identifier::codeSpace() PROJ_PURE_DEFN {
1038
461k
    return d->codeSpace_;
1039
461k
}
1040
1041
// ---------------------------------------------------------------------------
1042
1043
/** \brief Return the version identifier for the namespace.
1044
 *
1045
 * When appropriate, the edition is identified by the effective date, coded
1046
 * using ISO 8601 date format.
1047
 *
1048
 * @return the version or empty.
1049
 */
1050
0
const optional<std::string> &Identifier::version() PROJ_PURE_DEFN {
1051
0
    return d->version_;
1052
0
}
1053
1054
// ---------------------------------------------------------------------------
1055
1056
/** \brief Return the natural language description of the meaning of the code
1057
 * value.
1058
 *
1059
 * @return the description or empty.
1060
 */
1061
2.65M
const optional<std::string> &Identifier::description() PROJ_PURE_DEFN {
1062
2.65M
    return d->description_;
1063
2.65M
}
1064
1065
// ---------------------------------------------------------------------------
1066
1067
/** \brief Return the URI of the identifier.
1068
 *
1069
 * @return the URI or empty.
1070
 */
1071
0
const optional<std::string> &Identifier::uri() PROJ_PURE_DEFN {
1072
0
    return d->uri_;
1073
0
}
1074
1075
// ---------------------------------------------------------------------------
1076
1077
//! @cond Doxygen_Suppress
1078
0
void Identifier::_exportToWKT(WKTFormatter *formatter) const {
1079
0
    const bool isWKT2 = formatter->version() == WKTFormatter::Version::WKT2;
1080
0
    const std::string &l_code = code();
1081
0
    std::string l_codeSpace = *codeSpace();
1082
0
    std::string l_version = *version();
1083
0
    const auto &dbContext = formatter->databaseContext();
1084
0
    if (dbContext) {
1085
0
        dbContext->getAuthorityAndVersion(*codeSpace(), l_codeSpace, l_version);
1086
0
    }
1087
0
    if (!l_codeSpace.empty() && !l_code.empty()) {
1088
0
        if (isWKT2) {
1089
0
            formatter->startNode(WKTConstants::ID, false);
1090
0
            formatter->addQuotedString(l_codeSpace);
1091
0
            try {
1092
0
                (void)std::stoi(l_code);
1093
0
                formatter->add(l_code);
1094
0
            } catch (const std::exception &) {
1095
0
                formatter->addQuotedString(l_code);
1096
0
            }
1097
0
            if (!l_version.empty()) {
1098
0
                bool isDouble = false;
1099
0
                (void)c_locale_stod(l_version, isDouble);
1100
0
                if (isDouble) {
1101
0
                    formatter->add(l_version);
1102
0
                } else {
1103
0
                    formatter->addQuotedString(l_version);
1104
0
                }
1105
0
            }
1106
0
            if (authority().has_value() &&
1107
0
                *(authority()->title()) != *codeSpace()) {
1108
0
                formatter->startNode(WKTConstants::CITATION, false);
1109
0
                formatter->addQuotedString(*(authority()->title()));
1110
0
                formatter->endNode();
1111
0
            }
1112
0
            if (uri().has_value()) {
1113
0
                formatter->startNode(WKTConstants::URI, false);
1114
0
                formatter->addQuotedString(*(uri()));
1115
0
                formatter->endNode();
1116
0
            }
1117
0
            formatter->endNode();
1118
0
        } else {
1119
0
            formatter->startNode(WKTConstants::AUTHORITY, false);
1120
0
            formatter->addQuotedString(l_codeSpace);
1121
0
            formatter->addQuotedString(l_code);
1122
0
            formatter->endNode();
1123
0
        }
1124
0
    }
1125
0
}
1126
1127
// ---------------------------------------------------------------------------
1128
1129
0
void Identifier::_exportToJSON(JSONFormatter *formatter) const {
1130
0
    const std::string &l_code = code();
1131
0
    std::string l_codeSpace = *codeSpace();
1132
0
    std::string l_version = *version();
1133
0
    const auto &dbContext = formatter->databaseContext();
1134
0
    if (dbContext) {
1135
0
        dbContext->getAuthorityAndVersion(*codeSpace(), l_codeSpace, l_version);
1136
0
    }
1137
0
    if (!l_codeSpace.empty() && !l_code.empty()) {
1138
0
        auto writer = formatter->writer();
1139
0
        auto objContext(formatter->MakeObjectContext(nullptr, false));
1140
0
        writer->AddObjKey("authority");
1141
0
        writer->Add(l_codeSpace);
1142
0
        writer->AddObjKey("code");
1143
0
        try {
1144
0
            writer->Add(std::stoi(l_code));
1145
0
        } catch (const std::exception &) {
1146
0
            writer->Add(l_code);
1147
0
        }
1148
1149
0
        if (!l_version.empty()) {
1150
0
            writer->AddObjKey("version");
1151
0
            bool isDouble = false;
1152
0
            (void)c_locale_stod(l_version, isDouble);
1153
0
            if (isDouble) {
1154
0
                writer->AddUnquoted(l_version.c_str());
1155
0
            } else {
1156
0
                writer->Add(l_version);
1157
0
            }
1158
0
        }
1159
0
        if (authority().has_value() &&
1160
0
            *(authority()->title()) != *codeSpace()) {
1161
0
            writer->AddObjKey("authority_citation");
1162
0
            writer->Add(*(authority()->title()));
1163
0
        }
1164
0
        if (uri().has_value()) {
1165
0
            writer->AddObjKey("uri");
1166
0
            writer->Add(*(uri()));
1167
0
        }
1168
0
    }
1169
0
}
1170
1171
//! @endcond
1172
1173
// ---------------------------------------------------------------------------
1174
1175
//! @cond Doxygen_Suppress
1176
62.5M
static bool isIgnoredChar(char ch) {
1177
62.5M
    return ch == ' ' || ch == '_' || ch == '-' || ch == '/' || ch == '(' ||
1178
62.5M
           ch == ')' || ch == '.' || ch == '&' || ch == ',';
1179
62.5M
}
1180
//! @endcond
1181
1182
// ---------------------------------------------------------------------------
1183
1184
//! @cond Doxygen_Suppress
1185
static const struct utf8_to_lower {
1186
    const char *utf8;
1187
    char ascii;
1188
} map_utf8_to_lower[] = {
1189
    {"\xc3\xa1", 'a'}, // a acute
1190
    {"\xc3\xa4", 'a'}, // a tremma
1191
1192
    {"\xc4\x9b", 'e'}, // e reverse circumflex
1193
    {"\xc3\xa8", 'e'}, // e grave
1194
    {"\xc3\xa9", 'e'}, // e acute
1195
    {"\xc3\xab", 'e'}, // e tremma
1196
1197
    {"\xc3\xad", 'i'}, // i grave
1198
1199
    {"\xc3\xb4", 'o'}, // o circumflex
1200
    {"\xc3\xb6", 'o'}, // o tremma
1201
1202
    {"\xc3\xa7", 'c'}, // c cedilla
1203
};
1204
1205
977k
static const struct utf8_to_lower *get_ascii_replacement(const char *c_str) {
1206
9.73M
    for (const auto &pair : map_utf8_to_lower) {
1207
9.73M
        if (*c_str == pair.utf8[0] &&
1208
9.73M
            strncmp(c_str, pair.utf8, strlen(pair.utf8)) == 0) {
1209
8.91k
            return &pair;
1210
8.91k
        }
1211
9.73M
    }
1212
968k
    return nullptr;
1213
977k
}
1214
//! @endcond
1215
1216
// ---------------------------------------------------------------------------
1217
1218
//! @cond Doxygen_Suppress
1219
23.5k
std::string Identifier::canonicalizeName(const std::string &str) {
1220
23.5k
    std::string res;
1221
23.5k
    const char *c_str = str.c_str();
1222
417k
    for (size_t i = 0; c_str[i] != 0; ++i) {
1223
394k
        const auto ch = c_str[i];
1224
394k
        if (ch == ' ' && c_str[i + 1] == '+' && c_str[i + 2] == ' ') {
1225
507
            i += 2;
1226
507
            continue;
1227
507
        }
1228
393k
        if (ch == '1' && !res.empty() &&
1229
393k
            !(res.back() >= '0' && res.back() <= '9') && c_str[i + 1] == '9' &&
1230
393k
            c_str[i + 2] >= '0' && c_str[i + 2] <= '9') {
1231
678
            ++i;
1232
678
            continue;
1233
678
        }
1234
393k
        if (static_cast<unsigned char>(ch) > 127) {
1235
36.4k
            const auto *replacement = get_ascii_replacement(c_str + i);
1236
36.4k
            if (replacement) {
1237
134
                res.push_back(replacement->ascii);
1238
134
                i += strlen(replacement->utf8) - 1;
1239
134
                continue;
1240
134
            }
1241
36.4k
        }
1242
392k
        if (!isIgnoredChar(ch)) {
1243
353k
            res.push_back(static_cast<char>(::tolower(ch)));
1244
353k
        }
1245
392k
    }
1246
23.5k
    return res;
1247
23.5k
}
1248
//! @endcond
1249
1250
// ---------------------------------------------------------------------------
1251
1252
/** \brief Returns whether two names are considered equivalent.
1253
 *
1254
 * Two names are equivalent by removing any space, underscore, dash, slash,
1255
 * { or } character from them, and comparing in a case insensitive way.
1256
 */
1257
18.4M
bool Identifier::isEquivalentName(const char *a, const char *b) noexcept {
1258
18.4M
    size_t i = 0;
1259
18.4M
    size_t j = 0;
1260
18.4M
    char lastValidA = 0;
1261
18.4M
    char lastValidB = 0;
1262
32.2M
    while (a[i] != 0 || b[j] != 0) {
1263
31.7M
        char aCh = a[i];
1264
31.7M
        char bCh = b[j];
1265
31.7M
        if (aCh == ' ' && a[i + 1] == '+' && a[i + 2] == ' ' && a[i + 3] != 0) {
1266
2.02k
            i += 3;
1267
2.02k
            continue;
1268
2.02k
        }
1269
31.7M
        if (bCh == ' ' && b[j + 1] == '+' && b[j + 2] == ' ' && b[j + 3] != 0) {
1270
490
            j += 3;
1271
490
            continue;
1272
490
        }
1273
31.7M
        if (isIgnoredChar(aCh)) {
1274
1.35M
            ++i;
1275
1.35M
            continue;
1276
1.35M
        }
1277
30.3M
        if (isIgnoredChar(bCh)) {
1278
1.11M
            ++j;
1279
1.11M
            continue;
1280
1.11M
        }
1281
29.2M
        if (aCh == '1' && !(lastValidA >= '0' && lastValidA <= '9') &&
1282
29.2M
            a[i + 1] == '9' && a[i + 2] >= '0' && a[i + 2] <= '9') {
1283
117k
            i += 2;
1284
117k
            lastValidA = '9';
1285
117k
            continue;
1286
117k
        }
1287
29.1M
        if (bCh == '1' && !(lastValidB >= '0' && lastValidB <= '9') &&
1288
29.1M
            b[j + 1] == '9' && b[j + 2] >= '0' && b[j + 2] <= '9') {
1289
116k
            j += 2;
1290
116k
            lastValidB = '9';
1291
116k
            continue;
1292
116k
        }
1293
29.0M
        if (static_cast<unsigned char>(aCh) > 127) {
1294
524k
            const auto *replacement = get_ascii_replacement(a + i);
1295
524k
            if (replacement) {
1296
7.65k
                aCh = replacement->ascii;
1297
7.65k
                i += strlen(replacement->utf8) - 1;
1298
7.65k
            }
1299
524k
        }
1300
29.0M
        if (static_cast<unsigned char>(bCh) > 127) {
1301
416k
            const auto *replacement = get_ascii_replacement(b + j);
1302
416k
            if (replacement) {
1303
1.12k
                bCh = replacement->ascii;
1304
1.12k
                j += strlen(replacement->utf8) - 1;
1305
1.12k
            }
1306
416k
        }
1307
29.0M
        if ((aCh == 0 && bCh != 0) || (aCh != 0 && bCh == 0) ||
1308
29.0M
            ::tolower(aCh) != ::tolower(bCh)) {
1309
17.9M
            return false;
1310
17.9M
        }
1311
11.0M
        lastValidA = aCh;
1312
11.0M
        lastValidB = bCh;
1313
11.0M
        if (aCh != 0)
1314
11.0M
            ++i;
1315
11.0M
        if (bCh != 0)
1316
11.0M
            ++j;
1317
11.0M
    }
1318
475k
    return true;
1319
18.4M
}
1320
1321
// ---------------------------------------------------------------------------
1322
1323
//! @cond Doxygen_Suppress
1324
struct PositionalAccuracy::Private {
1325
    std::string value_{};
1326
};
1327
//! @endcond
1328
1329
// ---------------------------------------------------------------------------
1330
1331
PositionalAccuracy::PositionalAccuracy(const std::string &valueIn)
1332
9.40k
    : d(internal::make_unique<Private>()) {
1333
9.40k
    d->value_ = valueIn;
1334
9.40k
}
1335
1336
// ---------------------------------------------------------------------------
1337
1338
//! @cond Doxygen_Suppress
1339
9.40k
PositionalAccuracy::~PositionalAccuracy() = default;
1340
//! @endcond
1341
1342
// ---------------------------------------------------------------------------
1343
1344
/** \brief Return the value of the positional accuracy.
1345
 */
1346
2.71k
const std::string &PositionalAccuracy::value() PROJ_PURE_DEFN {
1347
2.71k
    return d->value_;
1348
2.71k
}
1349
1350
// ---------------------------------------------------------------------------
1351
1352
/** \brief Instantiate a PositionalAccuracy.
1353
 *
1354
 * @param valueIn positional accuracy value.
1355
 * @return a new PositionalAccuracy.
1356
 */
1357
9.40k
PositionalAccuracyNNPtr PositionalAccuracy::create(const std::string &valueIn) {
1358
9.40k
    return PositionalAccuracy::nn_make_shared<PositionalAccuracy>(valueIn);
1359
9.40k
}
1360
1361
} // namespace metadata
1362
NS_PROJ_END