Coverage Report

Created: 2025-06-13 06:18

/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