Coverage Report

Created: 2026-07-16 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/iso19111/metadata.cpp
Line
Count
Source
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
9.37M
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
2.69k
    : d(std::make_unique<Private>()) {
89
2.69k
    d->title = titleIn;
90
2.69k
}
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
9.37M
Citation::~Citation() = default;
101
102
// ---------------------------------------------------------------------------
103
104
2.69k
Citation &Citation::operator=(const Citation &other) {
105
2.69k
    if (this != &other) {
106
2.69k
        *d = *other.d;
107
2.69k
    }
108
2.69k
    return *this;
109
2.69k
}
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
683k
GeographicExtent::GeographicExtent() : d(std::make_unique<Private>()) {}
128
129
// ---------------------------------------------------------------------------
130
131
//! @cond Doxygen_Suppress
132
683k
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
711k
        : 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
683k
    : GeographicExtent(),
158
683k
      d(std::make_unique<Private>(west, south, east, north)) {}
159
160
// ---------------------------------------------------------------------------
161
162
//! @cond Doxygen_Suppress
163
683k
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
185k
double GeographicBoundingBox::westBoundLongitude() PROJ_PURE_DEFN {
177
185k
    return d->west_;
178
185k
}
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
185k
double GeographicBoundingBox::southBoundLatitude() PROJ_PURE_DEFN {
188
185k
    return d->south_;
189
185k
}
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
185k
double GeographicBoundingBox::eastBoundLongitude() PROJ_PURE_DEFN {
202
185k
    return d->east_;
203
185k
}
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
185k
double GeographicBoundingBox::northBoundLatitude() PROJ_PURE_DEFN {
213
185k
    return d->north_;
214
185k
}
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
683k
                                                         double north) {
236
683k
    if (std::isnan(west) || std::isnan(south) || std::isnan(east) ||
237
683k
        std::isnan(north)) {
238
0
        throw InvalidValueTypeException(
239
0
            "GeographicBoundingBox::create() does not accept NaN values");
240
0
    }
241
683k
    if (south > north) {
242
4
        throw InvalidValueTypeException(
243
4
            "GeographicBoundingBox::create() does not accept south > north");
244
4
    }
245
    // Avoid creating a degenerate bounding box if reduced to a point or a line
246
683k
    if (west == east) {
247
10
        if (west > -180)
248
10
            west =
249
10
                std::nextafter(west, -std::numeric_limits<double>::infinity());
250
10
        if (east < 180)
251
10
            east =
252
10
                std::nextafter(east, std::numeric_limits<double>::infinity());
253
10
    }
254
683k
    if (south == north) {
255
123
        if (south > -90)
256
123
            south =
257
123
                std::nextafter(south, -std::numeric_limits<double>::infinity());
258
123
        if (north < 90)
259
123
            north =
260
123
                std::nextafter(north, std::numeric_limits<double>::infinity());
261
123
    }
262
683k
    return GeographicBoundingBox::nn_make_shared<GeographicBoundingBox>(
263
683k
        west, south, east, north);
264
683k
}
265
266
// ---------------------------------------------------------------------------
267
268
//! @cond Doxygen_Suppress
269
bool GeographicBoundingBox::_isEquivalentTo(
270
    const util::IComparable *other, util::IComparable::Criterion,
271
12.5k
    const io::DatabaseContextPtr &) const {
272
12.5k
    auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other);
273
12.5k
    if (!otherExtent)
274
0
        return false;
275
12.5k
    return d->west_ == otherExtent->d->west_ &&
276
12.5k
           d->south_ == otherExtent->d->south_ &&
277
12.5k
           d->east_ == otherExtent->d->east_ &&
278
12.5k
           d->north_ == otherExtent->d->north_;
279
12.5k
}
280
//! @endcond
281
282
// ---------------------------------------------------------------------------
283
284
355k
bool GeographicBoundingBox::contains(const GeographicExtentNNPtr &other) const {
285
355k
    auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get());
286
355k
    if (!otherExtent) {
287
0
        return false;
288
0
    }
289
355k
    const double W = d->west_;
290
355k
    const double E = d->east_;
291
355k
    const double N = d->north_;
292
355k
    const double S = d->south_;
293
355k
    const double oW = otherExtent->d->west_;
294
355k
    const double oE = otherExtent->d->east_;
295
355k
    const double oN = otherExtent->d->north_;
296
355k
    const double oS = otherExtent->d->south_;
297
298
355k
    if (!(S <= oS && N >= oN)) {
299
145k
        return false;
300
145k
    }
301
302
210k
    if (W == -180.0 && E == 180.0) {
303
172k
        return oW != oE;
304
172k
    }
305
306
38.3k
    if (oW == -180.0 && oE == 180.0) {
307
0
        return false;
308
0
    }
309
310
    // Normal bounding box ?
311
38.3k
    if (W < E) {
312
37.7k
        if (oW < oE) {
313
37.6k
            return W <= oW && E >= oE;
314
37.6k
        } else {
315
91
            return false;
316
91
        }
317
        // No: crossing antimerian
318
37.7k
    } else {
319
583
        if (oW < oE) {
320
257
            if (oW >= W) {
321
81
                return true;
322
176
            } else if (oE <= E) {
323
170
                return true;
324
170
            } else {
325
6
                return false;
326
6
            }
327
326
        } else {
328
326
            return W <= oW && E >= oE;
329
326
        }
330
583
    }
331
38.3k
}
332
333
// ---------------------------------------------------------------------------
334
335
//! @cond Doxygen_Suppress
336
247k
bool GeographicBoundingBox::Private::intersects(const Private &other) const {
337
247k
    const double W = west_;
338
247k
    const double E = east_;
339
247k
    const double N = north_;
340
247k
    const double S = south_;
341
247k
    const double oW = other.west_;
342
247k
    const double oE = other.east_;
343
247k
    const double oN = other.north_;
344
247k
    const double oS = other.south_;
345
346
    // Check intersection along the latitude axis
347
247k
    if (N < oS || S > oN) {
348
137k
        return false;
349
137k
    }
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
110k
    if (W == -180.0 && E == 180.0 && oW > oE) {
355
80
        return true;
356
80
    }
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
110k
    if (oW == -180.0 && oE == 180.0 && W > E) {
362
80
        return true;
363
80
    }
364
365
    // Normal bounding box ?
366
110k
    if (W <= E) {
367
108k
        if (oW <= oE) {
368
97.5k
            if (std::max(W, oW) < std::min(E, oE)) {
369
49.8k
                return true;
370
49.8k
            }
371
47.6k
            return false;
372
97.5k
        }
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
11.1k
        if (oW > 180 || oE < -180) {
378
0
            return false;
379
0
        }
380
381
11.1k
        return intersects(Private(oW, oS, 180.0, oN)) ||
382
11.0k
               intersects(Private(-180.0, oS, oE, oN));
383
384
        // No: crossing antimeridian
385
11.1k
    } else {
386
1.34k
        if (oW <= oE) {
387
1.02k
            return other.intersects(*this);
388
1.02k
        }
389
390
325
        return true;
391
1.34k
    }
392
110k
}
393
//! @endcond
394
395
bool GeographicBoundingBox::intersects(
396
224k
    const GeographicExtentNNPtr &other) const {
397
224k
    auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get());
398
224k
    if (!otherExtent) {
399
0
        return false;
400
0
    }
401
224k
    return d->intersects(*(otherExtent->d));
402
224k
}
403
404
// ---------------------------------------------------------------------------
405
406
GeographicExtentPtr
407
24.9k
GeographicBoundingBox::intersection(const GeographicExtentNNPtr &other) const {
408
24.9k
    auto otherExtent = dynamic_cast<const GeographicBoundingBox *>(other.get());
409
24.9k
    if (!otherExtent) {
410
0
        return nullptr;
411
0
    }
412
24.9k
    auto ret = d->intersection(*(otherExtent->d));
413
24.9k
    if (ret) {
414
6.04k
        auto bbox = GeographicBoundingBox::create(ret->west_, ret->south_,
415
6.04k
                                                  ret->east_, ret->north_);
416
6.04k
        return bbox.as_nullable();
417
6.04k
    }
418
18.9k
    return nullptr;
419
24.9k
}
420
421
//! @cond Doxygen_Suppress
422
std::unique_ptr<GeographicBoundingBox::Private>
423
25.3k
GeographicBoundingBox::Private::intersection(const Private &otherExtent) const {
424
25.3k
    const double W = west_;
425
25.3k
    const double E = east_;
426
25.3k
    const double N = north_;
427
25.3k
    const double S = south_;
428
25.3k
    const double oW = otherExtent.west_;
429
25.3k
    const double oE = otherExtent.east_;
430
25.3k
    const double oN = otherExtent.north_;
431
25.3k
    const double oS = otherExtent.south_;
432
433
    // Check intersection along the latitude axis
434
25.3k
    if (N < oS || S > oN) {
435
15.9k
        return nullptr;
436
15.9k
    }
437
438
    // Check world coverage of this bbox, and other bbox overlapping
439
    // antimeridian (e.g. oW=175 and oE=-175)
440
9.34k
    if (W == -180.0 && E == 180.0 && oW > oE) {
441
2
        return std::make_unique<Private>(oW, std::max(S, oS), oE,
442
2
                                         std::min(N, oN));
443
2
    }
444
445
    // Check world coverage of other bbox, and this bbox overlapping
446
    // antimeridian (e.g. W=175 and E=-175)
447
9.34k
    if (oW == -180.0 && oE == 180.0 && W > E) {
448
2
        return std::make_unique<Private>(W, std::max(S, oS), E,
449
2
                                         std::min(N, oN));
450
2
    }
451
452
    // Normal bounding box ?
453
9.34k
    if (W <= E) {
454
9.23k
        if (oW <= oE) {
455
9.07k
            const double resW = std::max(W, oW);
456
9.07k
            const double resE = std::min(E, oE);
457
9.07k
            if (resW < resE) {
458
6.01k
                return std::make_unique<Private>(resW, std::max(S, oS), resE,
459
6.01k
                                                 std::min(N, oN));
460
6.01k
            }
461
3.05k
            return nullptr;
462
9.07k
        }
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
159
        if (oW > 180 || oE < -180) {
468
10
            return nullptr;
469
10
        }
470
471
        // Return larger of two parts of the multipolygon
472
149
        auto inter1 = intersection(Private(oW, oS, 180.0, oN));
473
149
        auto inter2 = intersection(Private(-180.0, oS, oE, oN));
474
149
        if (!inter1) {
475
149
            return inter2;
476
149
        }
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
112
    } else {
486
112
        if (oW <= oE) {
487
86
            return otherExtent.intersection(*this);
488
86
        }
489
490
26
        return std::make_unique<Private>(std::max(W, oW), std::max(S, oS),
491
26
                                         std::min(E, oE), std::min(N, oN));
492
112
    }
493
9.34k
}
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
15
        : 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
15
    : d(std::make_unique<Private>(minimumIn, maximumIn, unitIn)) {}
515
516
// ---------------------------------------------------------------------------
517
518
//! @cond Doxygen_Suppress
519
15
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
15
                       const common::UnitOfMeasureNNPtr &unitIn) {
554
15
    return VerticalExtent::nn_make_shared<VerticalExtent>(minimumIn, maximumIn,
555
15
                                                          unitIn);
556
15
}
557
558
// ---------------------------------------------------------------------------
559
560
//! @cond Doxygen_Suppress
561
bool VerticalExtent::_isEquivalentTo(const util::IComparable *other,
562
                                     util::IComparable::Criterion,
563
3
                                     const io::DatabaseContextPtr &) const {
564
3
    auto otherExtent = dynamic_cast<const VerticalExtent *>(other);
565
3
    if (!otherExtent)
566
0
        return false;
567
3
    return d->minimum_ == otherExtent->d->minimum_ &&
568
2
           d->maximum_ == otherExtent->d->maximum_ &&
569
2
           d->unit_ == otherExtent->d->unit_;
570
3
}
571
//! @endcond
572
573
// ---------------------------------------------------------------------------
574
575
/** \brief Returns whether this extent contains the other one.
576
 */
577
8
bool VerticalExtent::contains(const VerticalExtentNNPtr &other) const {
578
8
    const double thisUnitToSI = d->unit_->conversionToSI();
579
8
    const double otherUnitToSI = other->d->unit_->conversionToSI();
580
8
    return d->minimum_ * thisUnitToSI <= other->d->minimum_ * otherUnitToSI &&
581
4
           d->maximum_ * thisUnitToSI >= other->d->maximum_ * otherUnitToSI;
582
8
}
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
76
        : start_(start), stop_(stop) {}
604
};
605
//! @endcond
606
607
// ---------------------------------------------------------------------------
608
609
TemporalExtent::TemporalExtent(const std::string &startIn,
610
                               const std::string &stopIn)
611
76
    : d(std::make_unique<Private>(startIn, stopIn)) {}
612
613
// ---------------------------------------------------------------------------
614
615
//! @cond Doxygen_Suppress
616
76
TemporalExtent::~TemporalExtent() = default;
617
//! @endcond
618
619
// ---------------------------------------------------------------------------
620
621
/** \brief Returns the start of the temporal extent.
622
 */
623
237
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
179
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
76
                                           const std::string &stop) {
641
76
    return TemporalExtent::nn_make_shared<TemporalExtent>(start, stop);
642
76
}
643
644
// ---------------------------------------------------------------------------
645
646
//! @cond Doxygen_Suppress
647
bool TemporalExtent::_isEquivalentTo(const util::IComparable *other,
648
                                     util::IComparable::Criterion,
649
68
                                     const io::DatabaseContextPtr &) const {
650
68
    auto otherExtent = dynamic_cast<const TemporalExtent *>(other);
651
68
    if (!otherExtent)
652
0
        return false;
653
68
    return start() == otherExtent->start() && stop() == otherExtent->stop();
654
68
}
655
//! @endcond
656
657
// ---------------------------------------------------------------------------
658
659
/** \brief Returns whether this extent contains the other one.
660
 */
661
48
bool TemporalExtent::contains(const TemporalExtentNNPtr &other) const {
662
48
    return start() <= other->start() && stop() >= other->stop();
663
48
}
664
665
// ---------------------------------------------------------------------------
666
667
/** \brief Returns whether this extent intersects the other one.
668
 */
669
3
bool TemporalExtent::intersects(const TemporalExtentNNPtr &other) const {
670
3
    return start() <= other->stop() && stop() >= other->start();
671
3
}
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
289k
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
289k
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
107k
const optional<std::string> &Extent::description() PROJ_PURE_DEFN {
705
107k
    return d->description_;
706
107k
}
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
185k
Extent::geographicElements() PROJ_PURE_DEFN {
716
185k
    return d->geographicElements_;
717
185k
}
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
289k
               const std::vector<TemporalExtentNNPtr> &temporalElementsIn) {
756
289k
    auto extent = Extent::nn_make_shared<Extent>();
757
289k
    extent->assignSelf(extent);
758
289k
    extent->d->description_ = descriptionIn;
759
289k
    extent->d->geographicElements_ = geographicElementsIn;
760
289k
    extent->d->verticalElements_ = verticalElementsIn;
761
289k
    extent->d->temporalElements_ = temporalElementsIn;
762
289k
    return extent;
763
289k
}
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
47.7k
                       const util::optional<std::string> &descriptionIn) {
783
47.7k
    return create(
784
47.7k
        descriptionIn,
785
47.7k
        std::vector<GeographicExtentNNPtr>{
786
47.7k
            nn_static_pointer_cast<GeographicExtent>(
787
47.7k
                GeographicBoundingBox::create(west, south, east, north))},
788
47.7k
        std::vector<VerticalExtentNNPtr>(), std::vector<TemporalExtentNNPtr>());
789
47.7k
}
790
791
// ---------------------------------------------------------------------------
792
793
//! @cond Doxygen_Suppress
794
bool Extent::_isEquivalentTo(const util::IComparable *other,
795
                             util::IComparable::Criterion criterion,
796
21.3k
                             const io::DatabaseContextPtr &dbContext) const {
797
21.3k
    auto otherExtent = dynamic_cast<const Extent *>(other);
798
21.3k
    bool ret =
799
21.3k
        (otherExtent &&
800
21.3k
         description().has_value() == otherExtent->description().has_value() &&
801
21.3k
         *description() == *otherExtent->description() &&
802
12.5k
         d->geographicElements_.size() ==
803
12.5k
             otherExtent->d->geographicElements_.size() &&
804
12.5k
         d->verticalElements_.size() ==
805
12.5k
             otherExtent->d->verticalElements_.size() &&
806
12.5k
         d->temporalElements_.size() ==
807
12.5k
             otherExtent->d->temporalElements_.size());
808
21.3k
    if (ret) {
809
25.0k
        for (size_t i = 0; ret && i < d->geographicElements_.size(); ++i) {
810
12.5k
            ret = d->geographicElements_[i]->_isEquivalentTo(
811
12.5k
                otherExtent->d->geographicElements_[i].get(), criterion,
812
12.5k
                dbContext);
813
12.5k
        }
814
12.5k
        for (size_t i = 0; ret && i < d->verticalElements_.size(); ++i) {
815
3
            ret = d->verticalElements_[i]->_isEquivalentTo(
816
3
                otherExtent->d->verticalElements_[i].get(), criterion,
817
3
                dbContext);
818
3
        }
819
12.5k
        for (size_t i = 0; ret && i < d->temporalElements_.size(); ++i) {
820
68
            ret = d->temporalElements_[i]->_isEquivalentTo(
821
68
                otherExtent->d->temporalElements_[i].get(), criterion,
822
68
                dbContext);
823
68
        }
824
12.5k
    }
825
21.3k
    return ret;
826
21.3k
}
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
355k
bool Extent::contains(const ExtentNNPtr &other) const {
837
355k
    bool res = true;
838
355k
    if (d->geographicElements_.size() == 1 &&
839
355k
        other->d->geographicElements_.size() == 1) {
840
355k
        res = d->geographicElements_[0]->contains(
841
355k
            other->d->geographicElements_[0]);
842
355k
    }
843
355k
    if (res && d->verticalElements_.size() == 1 &&
844
18
        other->d->verticalElements_.size() == 1) {
845
8
        res = d->verticalElements_[0]->contains(other->d->verticalElements_[0]);
846
8
    }
847
355k
    if (res && d->temporalElements_.size() == 1 &&
848
56
        other->d->temporalElements_.size() == 1) {
849
48
        res = d->temporalElements_[0]->contains(other->d->temporalElements_[0]);
850
48
    }
851
355k
    return res;
852
355k
}
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
10.9k
bool Extent::intersects(const ExtentNNPtr &other) const {
862
10.9k
    bool res = true;
863
10.9k
    if (d->geographicElements_.size() == 1 &&
864
10.9k
        other->d->geographicElements_.size() == 1) {
865
10.9k
        res = d->geographicElements_[0]->intersects(
866
10.9k
            other->d->geographicElements_[0]);
867
10.9k
    }
868
10.9k
    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
10.9k
    if (res && d->temporalElements_.size() == 1 &&
874
4
        other->d->temporalElements_.size() == 1) {
875
3
        res =
876
3
            d->temporalElements_[0]->intersects(other->d->temporalElements_[0]);
877
3
    }
878
10.9k
    return res;
879
10.9k
}
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
208k
ExtentPtr Extent::intersection(const ExtentNNPtr &other) const {
890
208k
    if (d->geographicElements_.size() == 1 &&
891
208k
        other->d->geographicElements_.size() == 1) {
892
208k
        if (contains(other)) {
893
110k
            return other.as_nullable();
894
110k
        }
895
97.5k
        auto self = util::nn_static_pointer_cast<Extent>(shared_from_this());
896
97.5k
        if (other->contains(self)) {
897
72.6k
            return self.as_nullable();
898
72.6k
        }
899
24.9k
        auto geogIntersection = d->geographicElements_[0]->intersection(
900
24.9k
            other->d->geographicElements_[0]);
901
24.9k
        if (geogIntersection) {
902
6.04k
            return create(util::optional<std::string>(),
903
6.04k
                          std::vector<GeographicExtentNNPtr>{
904
6.04k
                              NN_NO_CHECK(geogIntersection)},
905
6.04k
                          std::vector<VerticalExtentNNPtr>{},
906
6.04k
                          std::vector<TemporalExtentNNPtr>{});
907
6.04k
        }
908
24.9k
    }
909
18.9k
    return nullptr;
910
208k
}
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
3.20M
    Private() = default;
924
925
    Private(const std::string &codeIn, const PropertyMap &properties)
926
5.46M
        : code_(codeIn) {
927
5.46M
        setProperties(properties);
928
5.46M
    }
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
5.46M
{
940
5.46M
    {
941
5.46M
        const auto pVal = properties.get(AUTHORITY_KEY);
942
5.46M
        if (pVal) {
943
2.69k
            if (auto genVal = dynamic_cast<const BoxedValue *>(pVal->get())) {
944
2.69k
                if (genVal->type() == BoxedValue::Type::STRING) {
945
2.69k
                    authority_ = Citation(genVal->stringValue());
946
2.69k
                } else {
947
0
                    throw InvalidValueTypeException("Invalid value type for " +
948
0
                                                    AUTHORITY_KEY);
949
0
                }
950
2.69k
            } 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
2.69k
        }
960
5.46M
    }
961
962
5.46M
    {
963
5.46M
        const auto pVal = properties.get(CODE_KEY);
964
5.46M
        if (pVal) {
965
1.90M
            if (auto genVal = dynamic_cast<const BoxedValue *>(pVal->get())) {
966
1.90M
                if (genVal->type() == BoxedValue::Type::INTEGER) {
967
964k
                    code_ = toString(genVal->integerValue());
968
964k
                } else if (genVal->type() == BoxedValue::Type::STRING) {
969
941k
                    code_ = genVal->stringValue();
970
941k
                } else {
971
0
                    throw InvalidValueTypeException("Invalid value type for " +
972
0
                                                    CODE_KEY);
973
0
                }
974
1.90M
            } else {
975
0
                throw InvalidValueTypeException("Invalid value type for " +
976
0
                                                CODE_KEY);
977
0
            }
978
1.90M
        }
979
5.46M
    }
980
981
5.46M
    properties.getStringValue(CODESPACE_KEY, codeSpace_);
982
5.46M
    properties.getStringValue(VERSION_KEY, version_);
983
5.46M
    properties.getStringValue(DESCRIPTION_KEY, description_);
984
5.46M
    properties.getStringValue(URI_KEY, uri_);
985
5.46M
}
986
987
//! @endcond
988
989
// ---------------------------------------------------------------------------
990
991
Identifier::Identifier(const std::string &codeIn,
992
                       const util::PropertyMap &properties)
993
5.46M
    : d(std::make_unique<Private>(codeIn, properties)) {}
994
995
// ---------------------------------------------------------------------------
996
997
//! @cond Doxygen_Suppress
998
999
// ---------------------------------------------------------------------------
1000
1001
3.20M
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
8.67M
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
5.46M
                                   const PropertyMap &properties) {
1024
5.46M
    return Identifier::nn_make_shared<Identifier>(codeIn, properties);
1025
5.46M
}
1026
1027
// ---------------------------------------------------------------------------
1028
1029
//! @cond Doxygen_Suppress
1030
IdentifierNNPtr
1031
3.20M
Identifier::createFromDescription(const std::string &descriptionIn) {
1032
3.20M
    auto id = Identifier::nn_make_shared<Identifier>();
1033
3.20M
    id->d->description_ = descriptionIn;
1034
3.20M
    return id;
1035
3.20M
}
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
8.64M
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
8.75M
const optional<std::string> &Identifier::codeSpace() PROJ_PURE_DEFN {
1070
8.75M
    return d->codeSpace_;
1071
8.75M
}
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
20.1M
const optional<std::string> &Identifier::description() PROJ_PURE_DEFN {
1094
20.1M
    return d->description_;
1095
20.1M
}
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
1.64G
static bool isIgnoredChar(char ch) {
1209
1.64G
    return ch == ' ' || ch == '_' || ch == '-' || ch == '/' || ch == '(' ||
1210
1.40G
           ch == ')' || ch == '.' || ch == '&' || ch == ',';
1211
1.64G
}
1212
//! @endcond
1213
1214
// ---------------------------------------------------------------------------
1215
1216
//! @cond Doxygen_Suppress
1217
4.89G
static char lower(char ch) {
1218
4.89G
    return ch >= 'A' && ch <= 'Z' ? ch - 'A' + 'a' : ch;
1219
4.89G
}
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
2.88M
static const struct utf8_to_lower *get_ascii_replacement(const char *c_str) {
1246
28.4M
    for (const auto &pair : map_utf8_to_lower) {
1247
28.4M
        if (*c_str == pair.utf8[0] &&
1248
501k
            strncmp(c_str, pair.utf8, strlen(pair.utf8)) == 0) {
1249
69.5k
            return &pair;
1250
69.5k
        }
1251
28.4M
    }
1252
2.81M
    return nullptr;
1253
2.88M
}
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
1.55G
static bool matchesLowerCase(const char *c_str, const char *needle) {
1265
1.55G
    size_t i = 0;
1266
1.68G
    for (; c_str[i] && needle[i]; ++i) {
1267
1.67G
        if (lower(c_str[i]) != lower(needle[i])) {
1268
1.53G
            return false;
1269
1.53G
        }
1270
1.67G
    }
1271
16.6M
    return needle[i] == 0;
1272
1.55G
}
1273
//! @endcond
1274
1275
// ---------------------------------------------------------------------------
1276
1277
//! @cond Doxygen_Suppress
1278
1279
150M
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
37.8M
                                         bool biggerDifferencesAllowed) {
1287
37.8M
    std::string res;
1288
37.8M
    const char *c_str = str.c_str();
1289
1.02G
    for (size_t i = 0; c_str[i] != 0; ++i) {
1290
986M
        const auto ch = lower(c_str[i]);
1291
986M
        if (ch == ' ' && c_str[i + 1] == '+' && c_str[i + 2] == ' ') {
1292
1.17M
            i += 2;
1293
1.17M
            continue;
1294
1.17M
        }
1295
1296
        // Canonicalize "19dd" (where d is a digit) as "dd"
1297
985M
        if (ch == '1' && !res.empty() && !isdigit(res.back()) &&
1298
17.3M
            c_str[i + 1] == '9' && isdigit(c_str[i + 2]) &&
1299
11.9M
            isdigit(c_str[i + 3])) {
1300
11.9M
            ++i;
1301
11.9M
            continue;
1302
11.9M
        }
1303
1304
973M
        if (biggerDifferencesAllowed) {
1305
1306
973M
            const auto skipSubstring = [](char l_ch, const char *l_str,
1307
1.93G
                                          size_t &idx, const char *substr) {
1308
1.93G
                if (l_ch == substr[0] && idx > 0 &&
1309
22.9M
                    isIgnoredChar(l_str[idx - 1]) &&
1310
13.4M
                    matchesLowerCase(l_str + idx, substr)) {
1311
9.99M
                    idx += strlen(substr) - 1;
1312
9.99M
                    return true;
1313
9.99M
                }
1314
1.92G
                return false;
1315
1.93G
            };
1316
1317
            // Skip "zone" or "height" if preceding character is a space
1318
973M
            if (skipSubstring(ch, c_str, i, "zone") ||
1319
966M
                skipSubstring(ch, c_str, i, "height")) {
1320
9.99M
                continue;
1321
9.99M
            }
1322
1323
            // Replace a substring by its first character if preceding character
1324
            // is a space or a digit
1325
963M
            const auto replaceByFirstChar = [](char l_ch, const char *l_str,
1326
963M
                                               size_t &idx, const char *substr,
1327
1.92G
                                               std::string &l_res) {
1328
1.92G
                if (l_ch == substr[0] && idx > 0 &&
1329
92.0M
                    (isIgnoredChar(l_str[idx - 1]) ||
1330
77.5M
                     isdigit(l_str[idx - 1])) &&
1331
18.0M
                    matchesLowerCase(l_str + idx, substr)) {
1332
2.02M
                    l_res.push_back(l_ch);
1333
2.02M
                    idx += strlen(substr) - 1;
1334
2.02M
                    return true;
1335
2.02M
                }
1336
1.92G
                return false;
1337
1.92G
            };
1338
1339
            // Replace "north" or "south" by its first character if preceding
1340
            // character is a space or a digit
1341
963M
            if (replaceByFirstChar(ch, c_str, i, "north", res) ||
1342
962M
                replaceByFirstChar(ch, c_str, i, "south", res)) {
1343
2.02M
                continue;
1344
2.02M
            }
1345
963M
        }
1346
1347
961M
        if (static_cast<unsigned char>(ch) > 127) {
1348
206k
            const auto *replacement = get_ascii_replacement(c_str + i);
1349
206k
            if (replacement) {
1350
60.2k
                res.push_back(replacement->ascii);
1351
60.2k
                i += strlen(replacement->utf8) - 1;
1352
60.2k
                continue;
1353
60.2k
            }
1354
206k
        }
1355
1356
961M
        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
961M
        if (!isIgnoredChar(ch)) {
1363
752M
            res.push_back(ch);
1364
752M
        }
1365
961M
    }
1366
37.8M
    return res;
1367
37.8M
}
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
189M
                                  bool biggerDifferencesAllowed) noexcept {
1385
189M
    size_t i = 0;
1386
189M
    size_t j = 0;
1387
189M
    char lastValidA = 0;
1388
189M
    char lastValidB = 0;
1389
283M
    while (a[i] != 0 || b[j] != 0) {
1390
280M
        char aCh = lower(a[i]);
1391
280M
        char bCh = lower(b[j]);
1392
280M
        if (aCh == ' ' && a[i + 1] == '+' && a[i + 2] == ' ' && a[i + 3] != 0) {
1393
2.86k
            i += 3;
1394
2.86k
            continue;
1395
2.86k
        }
1396
280M
        if (bCh == ' ' && b[j + 1] == '+' && b[j + 2] == ' ' && b[j + 3] != 0) {
1397
2.88k
            j += 3;
1398
2.88k
            continue;
1399
2.88k
        }
1400
1401
280M
        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
280M
        } 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
280M
        if (isIgnoredChar(aCh)) {
1413
7.23M
            ++i;
1414
7.23M
            continue;
1415
7.23M
        }
1416
273M
        if (isIgnoredChar(bCh)) {
1417
7.26M
            ++j;
1418
7.26M
            continue;
1419
7.26M
        }
1420
1421
        // Canonicalize "19dd" (where d is a digit) as "dd"
1422
265M
        if (aCh == '1' && !isdigit(lastValidA) && a[i + 1] == '9' &&
1423
1.18M
            isdigit(a[i + 2]) && isdigit(a[i + 3])) {
1424
1.18M
            i += 2;
1425
1.18M
            lastValidA = '9';
1426
1.18M
            continue;
1427
1.18M
        }
1428
264M
        if (bCh == '1' && !isdigit(lastValidB) && b[j + 1] == '9' &&
1429
1.18M
            isdigit(b[j + 2]) && isdigit(b[j + 3])) {
1430
1.18M
            j += 2;
1431
1.18M
            lastValidB = '9';
1432
1.18M
            continue;
1433
1.18M
        }
1434
1435
263M
        if (biggerDifferencesAllowed) {
1436
            // Skip a substring if preceding character is a space
1437
263M
            const auto skipSubString = [](char ch, const char *str, size_t &idx,
1438
1.05G
                                          const char *substr) {
1439
1.05G
                if (ch == substr[0] && idx > 0 && isIgnoredChar(str[idx - 1]) &&
1440
178k
                    matchesLowerCase(str + idx, substr)) {
1441
108k
                    idx += strlen(substr);
1442
108k
                    return true;
1443
108k
                }
1444
1.05G
                return false;
1445
1.05G
            };
1446
1447
263M
            bool skip = false;
1448
263M
            if (skipSubString(aCh, a, i, "zone"))
1449
85
                skip = true;
1450
263M
            if (skipSubString(bCh, b, j, "zone"))
1451
82
                skip = true;
1452
263M
            if (skip)
1453
85
                continue;
1454
1455
263M
            if (skipSubString(aCh, a, i, "height"))
1456
54.0k
                skip = true;
1457
263M
            if (skipSubString(bCh, b, j, "height"))
1458
53.9k
                skip = true;
1459
263M
            if (skip)
1460
54.0k
                continue;
1461
1462
            // Replace a substring by its first character if preceding character
1463
            // is a space or a digit
1464
263M
            const auto replaceByFirstChar = [](char ch, const char *str,
1465
263M
                                               size_t &idx,
1466
1.05G
                                               const char *substr) {
1467
1.05G
                if (ch == substr[0] && idx > 0 &&
1468
11.6M
                    (isIgnoredChar(str[idx - 1]) || isdigit(str[idx - 1])) &&
1469
2.20M
                    matchesLowerCase(str + idx, substr)) {
1470
13.3k
                    idx += strlen(substr) - 1;
1471
13.3k
                    return true;
1472
13.3k
                }
1473
1.05G
                return false;
1474
1.05G
            };
1475
1476
263M
            if (!replaceByFirstChar(aCh, a, i, "north"))
1477
263M
                replaceByFirstChar(aCh, a, i, "south");
1478
1479
263M
            if (!replaceByFirstChar(bCh, b, j, "north"))
1480
263M
                replaceByFirstChar(bCh, b, j, "south");
1481
263M
        }
1482
1483
263M
        if (static_cast<unsigned char>(aCh) > 127) {
1484
1.35M
            const auto *replacement = get_ascii_replacement(a + i);
1485
1.35M
            if (replacement) {
1486
5.00k
                aCh = replacement->ascii;
1487
5.00k
                i += strlen(replacement->utf8) - 1;
1488
5.00k
            }
1489
1.35M
        }
1490
263M
        if (static_cast<unsigned char>(bCh) > 127) {
1491
1.32M
            const auto *replacement = get_ascii_replacement(b + j);
1492
1.32M
            if (replacement) {
1493
4.34k
                bCh = replacement->ascii;
1494
4.34k
                j += strlen(replacement->utf8) - 1;
1495
4.34k
            }
1496
1.32M
        }
1497
1498
263M
        if (aCh != bCh) {
1499
186M
            return false;
1500
186M
        }
1501
77.2M
        lastValidA = aCh;
1502
77.2M
        lastValidB = bCh;
1503
77.2M
        if (aCh != 0)
1504
77.2M
            ++i;
1505
77.2M
        if (bCh != 0)
1506
77.2M
            ++j;
1507
77.2M
    }
1508
3.27M
    return true;
1509
189M
}
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
189M
bool Identifier::isEquivalentName(const char *a, const char *b) noexcept {
1519
189M
    return isEquivalentName(a, b, /* biggerDifferencesAllowed = */ true);
1520
189M
}
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
225k
    : d(std::make_unique<Private>()) {
1534
225k
    d->value_ = valueIn;
1535
225k
}
1536
1537
// ---------------------------------------------------------------------------
1538
1539
//! @cond Doxygen_Suppress
1540
225k
PositionalAccuracy::~PositionalAccuracy() = default;
1541
//! @endcond
1542
1543
// ---------------------------------------------------------------------------
1544
1545
/** \brief Return the value of the positional accuracy.
1546
 */
1547
120k
const std::string &PositionalAccuracy::value() PROJ_PURE_DEFN {
1548
120k
    return d->value_;
1549
120k
}
1550
1551
// ---------------------------------------------------------------------------
1552
1553
/** \brief Instantiate a PositionalAccuracy.
1554
 *
1555
 * @param valueIn positional accuracy value.
1556
 * @return a new PositionalAccuracy.
1557
 */
1558
225k
PositionalAccuracyNNPtr PositionalAccuracy::create(const std::string &valueIn) {
1559
225k
    return PositionalAccuracy::nn_make_shared<PositionalAccuracy>(valueIn);
1560
225k
}
1561
1562
} // namespace metadata
1563
NS_PROJ_END