Coverage Report

Created: 2025-11-09 06:51

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/iso19111/operation/oputils.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 <string.h>
34
35
#include "proj/coordinateoperation.hpp"
36
#include "proj/crs.hpp"
37
#include "proj/util.hpp"
38
39
#include "proj/internal/internal.hpp"
40
#include "proj/internal/io_internal.hpp"
41
42
#include "oputils.hpp"
43
#include "parammappings.hpp"
44
45
#include "proj_constants.h"
46
47
// ---------------------------------------------------------------------------
48
49
NS_PROJ_START
50
51
using namespace internal;
52
53
namespace operation {
54
55
// ---------------------------------------------------------------------------
56
57
//! @cond Doxygen_Suppress
58
59
const char *BALLPARK_GEOCENTRIC_TRANSLATION = "Ballpark geocentric translation";
60
const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset";
61
const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation";
62
const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset";
63
const char *BALLPARK_VERTICAL_TRANSFORMATION =
64
    "ballpark vertical transformation";
65
const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT =
66
    "ballpark vertical transformation, without ellipsoid height to vertical "
67
    "height correction";
68
69
// ---------------------------------------------------------------------------
70
71
364k
OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
72
364k
    const char *name = OperationParameter::getNameForEPSGCode(code);
73
364k
    assert(name);
74
364k
    return OperationParameter::create(createMapNameEPSGCode(name, code));
75
364k
}
76
77
// ---------------------------------------------------------------------------
78
79
240k
util::PropertyMap createMethodMapNameEPSGCode(int code) {
80
240k
    const char *name = nullptr;
81
240k
    size_t nMethodNameCodes = 0;
82
240k
    const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes);
83
12.1M
    for (size_t i = 0; i < nMethodNameCodes; ++i) {
84
12.1M
        const auto &tuple = methodNameCodes[i];
85
12.1M
        if (tuple.epsg_code == code) {
86
240k
            name = tuple.name;
87
240k
            break;
88
240k
        }
89
12.1M
    }
90
240k
    assert(name);
91
240k
    return createMapNameEPSGCode(name, code);
92
240k
}
93
94
// ---------------------------------------------------------------------------
95
96
72.0k
util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) {
97
72.0k
    return util::PropertyMap()
98
72.0k
        .set(common::IdentifiedObject::NAME_KEY, name)
99
72.0k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
100
72.0k
        .set(metadata::Identifier::CODE_KEY, code);
101
72.0k
}
102
103
// ---------------------------------------------------------------------------
104
105
604k
util::PropertyMap createMapNameEPSGCode(const char *name, int code) {
106
604k
    return util::PropertyMap()
107
604k
        .set(common::IdentifiedObject::NAME_KEY, name)
108
604k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
109
604k
        .set(metadata::Identifier::CODE_KEY, code);
110
604k
}
111
112
// ---------------------------------------------------------------------------
113
114
util::PropertyMap &addDomains(util::PropertyMap &map,
115
364k
                              const common::ObjectUsage *obj) {
116
117
364k
    auto ar = util::ArrayOfBaseObject::create();
118
364k
    for (const auto &domain : obj->domains()) {
119
261k
        ar->add(domain);
120
261k
    }
121
364k
    if (!ar->empty()) {
122
261k
        map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar);
123
261k
    }
124
364k
    return map;
125
364k
}
126
127
// ---------------------------------------------------------------------------
128
129
230k
static const char *getCRSQualifierStr(const crs::CRSPtr &crs) {
130
230k
    auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get());
131
230k
    if (geod) {
132
213k
        if (geod->isGeocentric()) {
133
70.6k
            return " (geocentric)";
134
70.6k
        }
135
142k
        auto geog = dynamic_cast<crs::GeographicCRS *>(geod);
136
142k
        if (geog) {
137
142k
            if (geog->coordinateSystem()->axisList().size() == 2) {
138
74.8k
                return " (geog2D)";
139
74.8k
            } else {
140
67.2k
                return " (geog3D)";
141
67.2k
            }
142
142k
        }
143
142k
    }
144
17.4k
    return "";
145
230k
}
146
147
// ---------------------------------------------------------------------------
148
149
std::string buildOpName(const char *opType, const crs::CRSPtr &source,
150
376k
                        const crs::CRSPtr &target) {
151
376k
    std::string res(opType);
152
376k
    const auto &srcName = source->nameStr();
153
376k
    const auto &targetName = target->nameStr();
154
376k
    const char *srcQualifier = "";
155
376k
    const char *targetQualifier = "";
156
376k
    if (srcName == targetName) {
157
115k
        srcQualifier = getCRSQualifierStr(source);
158
115k
        targetQualifier = getCRSQualifierStr(target);
159
115k
        if (strcmp(srcQualifier, targetQualifier) == 0) {
160
14.1k
            srcQualifier = "";
161
14.1k
            targetQualifier = "";
162
14.1k
        }
163
115k
    }
164
376k
    res += " from ";
165
376k
    res += srcName;
166
376k
    res += srcQualifier;
167
376k
    res += " to ";
168
376k
    res += targetName;
169
376k
    res += targetQualifier;
170
376k
    return res;
171
376k
}
172
173
// ---------------------------------------------------------------------------
174
175
void addModifiedIdentifier(util::PropertyMap &map,
176
                           const common::IdentifiedObject *obj, bool inverse,
177
463k
                           bool derivedFrom) {
178
    // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE
179
    // as identifier.
180
181
463k
    auto ar = util::ArrayOfBaseObject::create();
182
463k
    for (const auto &idSrc : obj->identifiers()) {
183
225k
        auto authName = *(idSrc->codeSpace());
184
225k
        const auto &srcCode = idSrc->code();
185
225k
        if (derivedFrom) {
186
78.0k
            authName = concat("DERIVED_FROM(", authName, ")");
187
78.0k
        }
188
225k
        if (inverse) {
189
147k
            if (starts_with(authName, "INVERSE(") && authName.back() == ')') {
190
4.52k
                authName = authName.substr(strlen("INVERSE("));
191
4.52k
                authName.resize(authName.size() - 1);
192
142k
            } else {
193
142k
                authName = concat("INVERSE(", authName, ")");
194
142k
            }
195
147k
        }
196
225k
        auto idsProp = util::PropertyMap().set(
197
225k
            metadata::Identifier::CODESPACE_KEY, authName);
198
225k
        ar->add(metadata::Identifier::create(srcCode, idsProp));
199
225k
    }
200
463k
    if (!ar->empty()) {
201
225k
        map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
202
225k
    }
203
463k
}
204
205
// ---------------------------------------------------------------------------
206
207
util::PropertyMap
208
97.9k
createPropertiesForInverse(const OperationMethodNNPtr &method) {
209
97.9k
    util::PropertyMap map;
210
211
97.9k
    const std::string &forwardName = method->nameStr();
212
97.9k
    if (!forwardName.empty()) {
213
97.9k
        if (starts_with(forwardName, INVERSE_OF)) {
214
1.43k
            map.set(common::IdentifiedObject::NAME_KEY,
215
1.43k
                    forwardName.substr(INVERSE_OF.size()));
216
96.4k
        } else {
217
96.4k
            map.set(common::IdentifiedObject::NAME_KEY,
218
96.4k
                    INVERSE_OF + forwardName);
219
96.4k
        }
220
97.9k
    }
221
222
97.9k
    addModifiedIdentifier(map, method.get(), true, false);
223
224
97.9k
    return map;
225
97.9k
}
226
227
// ---------------------------------------------------------------------------
228
229
util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op,
230
                                             bool derivedFrom,
231
287k
                                             bool approximateInversion) {
232
287k
    assert(op);
233
287k
    util::PropertyMap map;
234
235
    // The domain(s) are unchanged by the inverse operation
236
287k
    addDomains(map, op);
237
238
287k
    const std::string &forwardName = op->nameStr();
239
240
    // Forge a name for the inverse, either from the forward name, or
241
    // from the source and target CRS names
242
287k
    const char *opType;
243
287k
    if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) {
244
0
        opType = BALLPARK_GEOCENTRIC_TRANSLATION;
245
287k
    } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) {
246
6.85k
        opType = BALLPARK_GEOGRAPHIC_OFFSET;
247
280k
    } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) {
248
6.13k
        opType = NULL_GEOGRAPHIC_OFFSET;
249
274k
    } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) {
250
30
        opType = NULL_GEOCENTRIC_TRANSLATION;
251
274k
    } else if (dynamic_cast<const Transformation *>(op) ||
252
159k
               starts_with(forwardName, "Transformation from ")) {
253
137k
        opType = "Transformation";
254
137k
    } else if (dynamic_cast<const Conversion *>(op)) {
255
59.9k
        opType = "Conversion";
256
76.9k
    } else {
257
76.9k
        opType = "Operation";
258
76.9k
    }
259
260
287k
    auto sourceCRS = op->sourceCRS();
261
287k
    auto targetCRS = op->targetCRS();
262
287k
    std::string name;
263
287k
    if (!forwardName.empty()) {
264
287k
        if (dynamic_cast<const Transformation *>(op) == nullptr &&
265
162k
            dynamic_cast<const ConcatenatedOperation *>(op) == nullptr &&
266
121k
            (starts_with(forwardName, INVERSE_OF) ||
267
99.8k
             forwardName.find(" + ") != std::string::npos)) {
268
41.3k
            std::vector<std::string> tokens;
269
41.3k
            std::string curToken;
270
41.3k
            bool inString = false;
271
5.10M
            for (size_t i = 0; i < forwardName.size(); ++i) {
272
5.06M
                if (inString) {
273
35.7k
                    curToken += forwardName[i];
274
35.7k
                    if (forwardName[i] == '\'') {
275
4.85k
                        inString = false;
276
4.85k
                    }
277
5.02M
                } else if (i + 3 < forwardName.size() &&
278
4.90M
                           memcmp(&forwardName[i], " + ", 3) == 0) {
279
128k
                    tokens.push_back(curToken);
280
128k
                    curToken.clear();
281
128k
                    i += 2;
282
4.90M
                } else if (forwardName[i] == '\'') {
283
5.09k
                    inString = true;
284
5.09k
                    curToken += forwardName[i];
285
4.89M
                } else {
286
4.89M
                    curToken += forwardName[i];
287
4.89M
                }
288
5.06M
            }
289
41.3k
            if (!curToken.empty()) {
290
41.3k
                tokens.push_back(std::move(curToken));
291
41.3k
            }
292
211k
            for (size_t i = tokens.size(); i > 0;) {
293
169k
                i--;
294
169k
                if (!name.empty()) {
295
128k
                    name += " + ";
296
128k
                }
297
169k
                if (starts_with(tokens[i], INVERSE_OF)) {
298
49.8k
                    name += tokens[i].substr(INVERSE_OF.size());
299
119k
                } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME ||
300
103k
                           tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) {
301
30.8k
                    name += tokens[i];
302
89.0k
                } else {
303
89.0k
                    name += INVERSE_OF + tokens[i];
304
89.0k
                }
305
169k
            }
306
245k
        } else if (!sourceCRS || !targetCRS ||
307
245k
                   forwardName != buildOpName(opType, sourceCRS, targetCRS)) {
308
189k
            if (forwardName.find(" + ") != std::string::npos) {
309
29.5k
                name = INVERSE_OF + '\'' + forwardName + '\'';
310
159k
            } else {
311
159k
                name = INVERSE_OF + forwardName;
312
159k
            }
313
189k
        }
314
287k
    }
315
287k
    if (name.empty() && sourceCRS && targetCRS) {
316
56.3k
        name = buildOpName(opType, targetCRS, sourceCRS);
317
56.3k
    }
318
287k
    if (approximateInversion) {
319
0
        name += " (approx. inversion)";
320
0
    }
321
322
287k
    if (!name.empty()) {
323
287k
        map.set(common::IdentifiedObject::NAME_KEY, name);
324
287k
    }
325
326
287k
    const std::string &remarks = op->remarks();
327
287k
    if (!remarks.empty()) {
328
113k
        map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
329
113k
    }
330
331
287k
    addModifiedIdentifier(map, op, true, derivedFrom);
332
333
287k
    const auto so = dynamic_cast<const SingleOperation *>(op);
334
287k
    if (so) {
335
246k
        const int soMethodEPSGCode = so->method()->getEPSGCode();
336
246k
        if (soMethodEPSGCode > 0) {
337
110k
            map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode);
338
110k
        }
339
246k
    }
340
341
287k
    return map;
342
287k
}
343
344
// ---------------------------------------------------------------------------
345
346
util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties,
347
86.6k
                                         const std::string &defaultName) {
348
86.6k
    if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
349
1.42k
        return util::PropertyMap(properties)
350
1.42k
            .set(common::IdentifiedObject::NAME_KEY, defaultName);
351
85.2k
    } else {
352
85.2k
        return properties;
353
85.2k
    }
354
86.6k
}
355
356
// ---------------------------------------------------------------------------
357
358
static std::string createEntryEqParam(const std::string &a,
359
2.72k
                                      const std::string &b) {
360
2.72k
    return a < b ? a + b : b + a;
361
2.72k
}
362
363
1
static std::set<std::string> buildSetEquivalentParameters() {
364
365
1
    std::set<std::string> set;
366
367
1
    const char *const listOfEquivalentParameterNames[][7] = {
368
1
        {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr},
369
1
        {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr},
370
1
        {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr},
371
1
        {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr},
372
373
1
        {"satellite_height", "height", nullptr},
374
375
1
        {EPSG_NAME_PARAMETER_FALSE_EASTING,
376
1
         EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN,
377
1
         EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr},
378
379
1
        {EPSG_NAME_PARAMETER_FALSE_NORTHING,
380
1
         EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN,
381
1
         EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr},
382
383
1
        {EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR,
384
1
         EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE,
385
1
         EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE,
386
1
         EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, nullptr},
387
388
1
        {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER,
389
1
         EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN,
390
1
         EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN,
391
1
         EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, "Central_Parallel",
392
1
         nullptr},
393
394
1
        {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER,
395
1
         EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN,
396
1
         EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN,
397
1
         EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE,
398
1
         EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr},
399
400
1
        {EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE,
401
1
         EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, nullptr},
402
403
1
        {"pseudo_standard_parallel_1", WKT1_STANDARD_PARALLEL_1, nullptr},
404
1
    };
405
406
12
    for (const auto &paramList : listOfEquivalentParameterNames) {
407
49
        for (size_t i = 0; paramList[i]; i++) {
408
37
            auto a = metadata::Identifier::canonicalizeName(paramList[i]);
409
90
            for (size_t j = i + 1; paramList[j]; j++) {
410
53
                auto b = metadata::Identifier::canonicalizeName(paramList[j]);
411
53
                set.insert(createEntryEqParam(a, b));
412
53
            }
413
37
        }
414
12
    }
415
1
    return set;
416
1
}
417
418
2.66k
bool areEquivalentParameters(const std::string &a, const std::string &b) {
419
420
2.66k
    static const std::set<std::string> setEquivalentParameters =
421
2.66k
        buildSetEquivalentParameters();
422
423
2.66k
    auto a_can = metadata::Identifier::canonicalizeName(a);
424
2.66k
    auto b_can = metadata::Identifier::canonicalizeName(b);
425
2.66k
    return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) !=
426
2.66k
           setEquivalentParameters.end();
427
2.66k
}
428
429
// ---------------------------------------------------------------------------
430
431
961k
bool isTimeDependent(const std::string &methodName) {
432
961k
    return ci_find(methodName, "Time dependent") != std::string::npos ||
433
961k
           ci_find(methodName, "Time-dependent") != std::string::npos;
434
961k
}
435
436
// ---------------------------------------------------------------------------
437
438
std::string computeConcatenatedName(
439
125k
    const std::vector<CoordinateOperationNNPtr> &flattenOps) {
440
125k
    std::string name;
441
444k
    for (const auto &subOp : flattenOps) {
442
444k
        if (!name.empty()) {
443
319k
            name += " + ";
444
319k
        }
445
444k
        const auto &l_name = subOp->nameStr();
446
444k
        if (l_name.empty()) {
447
7
            name += "unnamed";
448
444k
        } else {
449
444k
            name += l_name;
450
444k
        }
451
444k
    }
452
125k
    return name;
453
125k
}
454
455
// ---------------------------------------------------------------------------
456
457
metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op,
458
                              bool conversionExtentIsWorld,
459
448k
                              bool &emptyIntersection) {
460
448k
    auto conv = dynamic_cast<const Conversion *>(op.get());
461
448k
    if (conv) {
462
145k
        emptyIntersection = false;
463
145k
        return metadata::Extent::WORLD;
464
145k
    }
465
303k
    const auto &domains = op->domains();
466
303k
    if (!domains.empty()) {
467
239k
        emptyIntersection = false;
468
239k
        return domains[0]->domainOfValidity();
469
239k
    }
470
63.5k
    auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
471
63.5k
    if (!concatenated) {
472
57.8k
        emptyIntersection = false;
473
57.8k
        return nullptr;
474
57.8k
    }
475
5.77k
    return getExtent(concatenated->operations(), conversionExtentIsWorld,
476
5.77k
                     emptyIntersection);
477
63.5k
}
478
479
// ---------------------------------------------------------------------------
480
481
static const metadata::ExtentPtr nullExtent{};
482
483
141k
const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) {
484
141k
    const auto &domains = crs->domains();
485
141k
    if (!domains.empty()) {
486
92.1k
        return domains[0]->domainOfValidity();
487
92.1k
    }
488
49.3k
    const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get());
489
49.3k
    if (boundCRS) {
490
7.00k
        return getExtent(boundCRS->baseCRS());
491
7.00k
    }
492
42.3k
    return nullExtent;
493
49.3k
}
494
495
const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs,
496
40.8k
                                                       bool &approxOut) {
497
40.8k
    const auto &rawExtent(getExtent(crs));
498
40.8k
    approxOut = false;
499
40.8k
    if (rawExtent)
500
26.1k
        return rawExtent;
501
14.6k
    const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get());
502
14.6k
    if (compoundCRS) {
503
        // For a compoundCRS, take the intersection of the extent of its
504
        // components.
505
2.93k
        const auto &components = compoundCRS->componentReferenceSystems();
506
2.93k
        metadata::ExtentPtr extent;
507
2.93k
        approxOut = true;
508
5.91k
        for (const auto &component : components) {
509
5.91k
            const auto &componentExtent(getExtent(component));
510
5.91k
            if (extent && componentExtent)
511
29
                extent = extent->intersection(NN_NO_CHECK(componentExtent));
512
5.88k
            else if (componentExtent)
513
63
                extent = componentExtent;
514
5.91k
        }
515
2.93k
        return extent;
516
2.93k
    }
517
11.7k
    return rawExtent;
518
14.6k
}
519
520
// ---------------------------------------------------------------------------
521
522
metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
523
                              bool conversionExtentIsWorld,
524
101k
                              bool &emptyIntersection) {
525
101k
    metadata::ExtentPtr res = nullptr;
526
343k
    for (const auto &subop : ops) {
527
528
343k
        const auto &subExtent =
529
343k
            getExtent(subop, conversionExtentIsWorld, emptyIntersection);
530
343k
        if (!subExtent) {
531
62.6k
            if (emptyIntersection) {
532
0
                return nullptr;
533
0
            }
534
62.6k
            continue;
535
62.6k
        }
536
280k
        if (res == nullptr) {
537
94.8k
            res = subExtent;
538
185k
        } else {
539
185k
            res = res->intersection(NN_NO_CHECK(subExtent));
540
185k
            if (!res) {
541
15.6k
                emptyIntersection = true;
542
15.6k
                return nullptr;
543
15.6k
            }
544
185k
        }
545
280k
    }
546
86.3k
    emptyIntersection = false;
547
86.3k
    return res;
548
101k
}
549
550
// ---------------------------------------------------------------------------
551
552
// Returns the accuracy of an operation, or -1 if unknown
553
378k
double getAccuracy(const CoordinateOperationNNPtr &op) {
554
555
378k
    if (dynamic_cast<const Conversion *>(op.get())) {
556
        // A conversion is perfectly accurate.
557
132k
        return 0.0;
558
132k
    }
559
560
245k
    double accuracy = -1.0;
561
245k
    const auto &accuracies = op->coordinateOperationAccuracies();
562
245k
    if (!accuracies.empty()) {
563
125k
        try {
564
125k
            accuracy = c_locale_stod(accuracies[0]->value());
565
125k
        } catch (const std::exception &) {
566
0
        }
567
125k
    } else {
568
120k
        auto concatenated =
569
120k
            dynamic_cast<const ConcatenatedOperation *>(op.get());
570
120k
        if (concatenated) {
571
35.1k
            accuracy = getAccuracy(concatenated->operations());
572
35.1k
        }
573
120k
    }
574
245k
    return accuracy;
575
245k
}
576
577
// ---------------------------------------------------------------------------
578
579
// Returns the accuracy of a set of concatenated operations, or -1 if unknown
580
115k
double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) {
581
115k
    double accuracy = -1.0;
582
296k
    for (const auto &subop : ops) {
583
296k
        const double subops_accuracy = getAccuracy(subop);
584
296k
        if (subops_accuracy < 0.0) {
585
80.7k
            return -1.0;
586
80.7k
        }
587
215k
        if (accuracy < 0.0) {
588
96.2k
            accuracy = 0.0;
589
96.2k
        }
590
215k
        accuracy += subops_accuracy;
591
215k
    }
592
34.9k
    return accuracy;
593
115k
}
594
595
// ---------------------------------------------------------------------------
596
597
void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co,
598
0
                                      io::WKTFormatter *formatter) {
599
0
    auto l_sourceCRS = co->sourceCRS();
600
0
    assert(l_sourceCRS);
601
0
    auto l_targetCRS = co->targetCRS();
602
0
    assert(l_targetCRS);
603
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
604
0
    const bool canExportCRSId =
605
0
        (isWKT2 && formatter->use2019Keywords() &&
606
0
         !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()));
607
608
0
    const bool hasDomains = !co->domains().empty();
609
0
    if (hasDomains) {
610
0
        formatter->pushDisableUsage();
611
0
    }
612
613
0
    formatter->startNode(io::WKTConstants::SOURCECRS, false);
614
0
    if (canExportCRSId && !l_sourceCRS->identifiers().empty()) {
615
        // fake that top node has no id, so that the sourceCRS id is
616
        // considered
617
0
        formatter->pushHasId(false);
618
0
        l_sourceCRS->_exportToWKT(formatter);
619
0
        formatter->popHasId();
620
0
    } else {
621
0
        l_sourceCRS->_exportToWKT(formatter);
622
0
    }
623
0
    formatter->endNode();
624
625
0
    formatter->startNode(io::WKTConstants::TARGETCRS, false);
626
0
    if (canExportCRSId && !l_targetCRS->identifiers().empty()) {
627
        // fake that top node has no id, so that the targetCRS id is
628
        // considered
629
0
        formatter->pushHasId(false);
630
0
        l_targetCRS->_exportToWKT(formatter);
631
0
        formatter->popHasId();
632
0
    } else {
633
0
        l_targetCRS->_exportToWKT(formatter);
634
0
    }
635
0
    formatter->endNode();
636
637
0
    if (hasDomains) {
638
0
        formatter->popDisableUsage();
639
0
    }
640
0
}
641
642
//! @endcond
643
644
// ---------------------------------------------------------------------------
645
646
} // namespace operation
647
NS_PROJ_END