Coverage Report

Created: 2025-10-30 06:17

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
391k
OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
72
391k
    const char *name = OperationParameter::getNameForEPSGCode(code);
73
391k
    assert(name);
74
391k
    return OperationParameter::create(createMapNameEPSGCode(name, code));
75
391k
}
76
77
// ---------------------------------------------------------------------------
78
79
259k
util::PropertyMap createMethodMapNameEPSGCode(int code) {
80
259k
    const char *name = nullptr;
81
259k
    size_t nMethodNameCodes = 0;
82
259k
    const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes);
83
13.2M
    for (size_t i = 0; i < nMethodNameCodes; ++i) {
84
13.2M
        const auto &tuple = methodNameCodes[i];
85
13.2M
        if (tuple.epsg_code == code) {
86
259k
            name = tuple.name;
87
259k
            break;
88
259k
        }
89
13.2M
    }
90
259k
    assert(name);
91
259k
    return createMapNameEPSGCode(name, code);
92
259k
}
93
94
// ---------------------------------------------------------------------------
95
96
73.9k
util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) {
97
73.9k
    return util::PropertyMap()
98
73.9k
        .set(common::IdentifiedObject::NAME_KEY, name)
99
73.9k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
100
73.9k
        .set(metadata::Identifier::CODE_KEY, code);
101
73.9k
}
102
103
// ---------------------------------------------------------------------------
104
105
651k
util::PropertyMap createMapNameEPSGCode(const char *name, int code) {
106
651k
    return util::PropertyMap()
107
651k
        .set(common::IdentifiedObject::NAME_KEY, name)
108
651k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
109
651k
        .set(metadata::Identifier::CODE_KEY, code);
110
651k
}
111
112
// ---------------------------------------------------------------------------
113
114
util::PropertyMap &addDomains(util::PropertyMap &map,
115
366k
                              const common::ObjectUsage *obj) {
116
117
366k
    auto ar = util::ArrayOfBaseObject::create();
118
366k
    for (const auto &domain : obj->domains()) {
119
259k
        ar->add(domain);
120
259k
    }
121
366k
    if (!ar->empty()) {
122
259k
        map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar);
123
259k
    }
124
366k
    return map;
125
366k
}
126
127
// ---------------------------------------------------------------------------
128
129
254k
static const char *getCRSQualifierStr(const crs::CRSPtr &crs) {
130
254k
    auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get());
131
254k
    if (geod) {
132
236k
        if (geod->isGeocentric()) {
133
77.7k
            return " (geocentric)";
134
77.7k
        }
135
158k
        auto geog = dynamic_cast<crs::GeographicCRS *>(geod);
136
158k
        if (geog) {
137
158k
            if (geog->coordinateSystem()->axisList().size() == 2) {
138
81.8k
                return " (geog2D)";
139
81.8k
            } else {
140
76.8k
                return " (geog3D)";
141
76.8k
            }
142
158k
        }
143
158k
    }
144
18.5k
    return "";
145
254k
}
146
147
// ---------------------------------------------------------------------------
148
149
std::string buildOpName(const char *opType, const crs::CRSPtr &source,
150
399k
                        const crs::CRSPtr &target) {
151
399k
    std::string res(opType);
152
399k
    const auto &srcName = source->nameStr();
153
399k
    const auto &targetName = target->nameStr();
154
399k
    const char *srcQualifier = "";
155
399k
    const char *targetQualifier = "";
156
399k
    if (srcName == targetName) {
157
127k
        srcQualifier = getCRSQualifierStr(source);
158
127k
        targetQualifier = getCRSQualifierStr(target);
159
127k
        if (strcmp(srcQualifier, targetQualifier) == 0) {
160
15.6k
            srcQualifier = "";
161
15.6k
            targetQualifier = "";
162
15.6k
        }
163
127k
    }
164
399k
    res += " from ";
165
399k
    res += srcName;
166
399k
    res += srcQualifier;
167
399k
    res += " to ";
168
399k
    res += targetName;
169
399k
    res += targetQualifier;
170
399k
    return res;
171
399k
}
172
173
// ---------------------------------------------------------------------------
174
175
void addModifiedIdentifier(util::PropertyMap &map,
176
                           const common::IdentifiedObject *obj, bool inverse,
177
461k
                           bool derivedFrom) {
178
    // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE
179
    // as identifier.
180
181
461k
    auto ar = util::ArrayOfBaseObject::create();
182
461k
    for (const auto &idSrc : obj->identifiers()) {
183
220k
        auto authName = *(idSrc->codeSpace());
184
220k
        const auto &srcCode = idSrc->code();
185
220k
        if (derivedFrom) {
186
77.7k
            authName = concat("DERIVED_FROM(", authName, ")");
187
77.7k
        }
188
220k
        if (inverse) {
189
142k
            if (starts_with(authName, "INVERSE(") && authName.back() == ')') {
190
5.09k
                authName = authName.substr(strlen("INVERSE("));
191
5.09k
                authName.resize(authName.size() - 1);
192
137k
            } else {
193
137k
                authName = concat("INVERSE(", authName, ")");
194
137k
            }
195
142k
        }
196
220k
        auto idsProp = util::PropertyMap().set(
197
220k
            metadata::Identifier::CODESPACE_KEY, authName);
198
220k
        ar->add(metadata::Identifier::create(srcCode, idsProp));
199
220k
    }
200
461k
    if (!ar->empty()) {
201
219k
        map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
202
219k
    }
203
461k
}
204
205
// ---------------------------------------------------------------------------
206
207
util::PropertyMap
208
95.4k
createPropertiesForInverse(const OperationMethodNNPtr &method) {
209
95.4k
    util::PropertyMap map;
210
211
95.4k
    const std::string &forwardName = method->nameStr();
212
95.4k
    if (!forwardName.empty()) {
213
95.4k
        if (starts_with(forwardName, INVERSE_OF)) {
214
1.65k
            map.set(common::IdentifiedObject::NAME_KEY,
215
1.65k
                    forwardName.substr(INVERSE_OF.size()));
216
93.7k
        } else {
217
93.7k
            map.set(common::IdentifiedObject::NAME_KEY,
218
93.7k
                    INVERSE_OF + forwardName);
219
93.7k
        }
220
95.4k
    }
221
222
95.4k
    addModifiedIdentifier(map, method.get(), true, false);
223
224
95.4k
    return map;
225
95.4k
}
226
227
// ---------------------------------------------------------------------------
228
229
util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op,
230
                                             bool derivedFrom,
231
288k
                                             bool approximateInversion) {
232
288k
    assert(op);
233
288k
    util::PropertyMap map;
234
235
    // The domain(s) are unchanged by the inverse operation
236
288k
    addDomains(map, op);
237
238
288k
    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
288k
    const char *opType;
243
288k
    if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) {
244
0
        opType = BALLPARK_GEOCENTRIC_TRANSLATION;
245
288k
    } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) {
246
7.74k
        opType = BALLPARK_GEOGRAPHIC_OFFSET;
247
280k
    } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) {
248
6.97k
        opType = NULL_GEOGRAPHIC_OFFSET;
249
273k
    } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) {
250
30
        opType = NULL_GEOCENTRIC_TRANSLATION;
251
273k
    } else if (dynamic_cast<const Transformation *>(op) ||
252
164k
               starts_with(forwardName, "Transformation from ")) {
253
133k
        opType = "Transformation";
254
140k
    } else if (dynamic_cast<const Conversion *>(op)) {
255
65.0k
        opType = "Conversion";
256
75.2k
    } else {
257
75.2k
        opType = "Operation";
258
75.2k
    }
259
260
288k
    auto sourceCRS = op->sourceCRS();
261
288k
    auto targetCRS = op->targetCRS();
262
288k
    std::string name;
263
288k
    if (!forwardName.empty()) {
264
288k
        if (dynamic_cast<const Transformation *>(op) == nullptr &&
265
167k
            dynamic_cast<const ConcatenatedOperation *>(op) == nullptr &&
266
124k
            (starts_with(forwardName, INVERSE_OF) ||
267
105k
             forwardName.find(" + ") != std::string::npos)) {
268
38.3k
            std::vector<std::string> tokens;
269
38.3k
            std::string curToken;
270
38.3k
            bool inString = false;
271
5.04M
            for (size_t i = 0; i < forwardName.size(); ++i) {
272
5.00M
                if (inString) {
273
34.4k
                    curToken += forwardName[i];
274
34.4k
                    if (forwardName[i] == '\'') {
275
5.67k
                        inString = false;
276
5.67k
                    }
277
4.97M
                } else if (i + 3 < forwardName.size() &&
278
4.86M
                           memcmp(&forwardName[i], " + ", 3) == 0) {
279
131k
                    tokens.push_back(curToken);
280
131k
                    curToken.clear();
281
131k
                    i += 2;
282
4.84M
                } else if (forwardName[i] == '\'') {
283
5.91k
                    inString = true;
284
5.91k
                    curToken += forwardName[i];
285
4.83M
                } else {
286
4.83M
                    curToken += forwardName[i];
287
4.83M
                }
288
5.00M
            }
289
38.3k
            if (!curToken.empty()) {
290
38.3k
                tokens.push_back(std::move(curToken));
291
38.3k
            }
292
208k
            for (size_t i = tokens.size(); i > 0;) {
293
170k
                i--;
294
170k
                if (!name.empty()) {
295
131k
                    name += " + ";
296
131k
                }
297
170k
                if (starts_with(tokens[i], INVERSE_OF)) {
298
48.8k
                    name += tokens[i].substr(INVERSE_OF.size());
299
121k
                } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME ||
300
106k
                           tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) {
301
28.2k
                    name += tokens[i];
302
93.2k
                } else {
303
93.2k
                    name += INVERSE_OF + tokens[i];
304
93.2k
                }
305
170k
            }
306
250k
        } else if (!sourceCRS || !targetCRS ||
307
250k
                   forwardName != buildOpName(opType, sourceCRS, targetCRS)) {
308
188k
            if (forwardName.find(" + ") != std::string::npos) {
309
32.8k
                name = INVERSE_OF + '\'' + forwardName + '\'';
310
156k
            } else {
311
156k
                name = INVERSE_OF + forwardName;
312
156k
            }
313
188k
        }
314
288k
    }
315
288k
    if (name.empty() && sourceCRS && targetCRS) {
316
61.2k
        name = buildOpName(opType, targetCRS, sourceCRS);
317
61.2k
    }
318
288k
    if (approximateInversion) {
319
0
        name += " (approx. inversion)";
320
0
    }
321
322
288k
    if (!name.empty()) {
323
288k
        map.set(common::IdentifiedObject::NAME_KEY, name);
324
288k
    }
325
326
288k
    const std::string &remarks = op->remarks();
327
288k
    if (!remarks.empty()) {
328
107k
        map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
329
107k
    }
330
331
288k
    addModifiedIdentifier(map, op, true, derivedFrom);
332
333
288k
    const auto so = dynamic_cast<const SingleOperation *>(op);
334
288k
    if (so) {
335
245k
        const int soMethodEPSGCode = so->method()->getEPSGCode();
336
245k
        if (soMethodEPSGCode > 0) {
337
114k
            map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode);
338
114k
        }
339
245k
    }
340
341
288k
    return map;
342
288k
}
343
344
// ---------------------------------------------------------------------------
345
346
util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties,
347
87.4k
                                         const std::string &defaultName) {
348
87.4k
    if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
349
1.49k
        return util::PropertyMap(properties)
350
1.49k
            .set(common::IdentifiedObject::NAME_KEY, defaultName);
351
85.9k
    } else {
352
85.9k
        return properties;
353
85.9k
    }
354
87.4k
}
355
356
// ---------------------------------------------------------------------------
357
358
static std::string createEntryEqParam(const std::string &a,
359
2.89k
                                      const std::string &b) {
360
2.89k
    return a < b ? a + b : b + a;
361
2.89k
}
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.84k
bool areEquivalentParameters(const std::string &a, const std::string &b) {
419
420
2.84k
    static const std::set<std::string> setEquivalentParameters =
421
2.84k
        buildSetEquivalentParameters();
422
423
2.84k
    auto a_can = metadata::Identifier::canonicalizeName(a);
424
2.84k
    auto b_can = metadata::Identifier::canonicalizeName(b);
425
2.84k
    return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) !=
426
2.84k
           setEquivalentParameters.end();
427
2.84k
}
428
429
// ---------------------------------------------------------------------------
430
431
979k
bool isTimeDependent(const std::string &methodName) {
432
979k
    return ci_find(methodName, "Time dependent") != std::string::npos ||
433
979k
           ci_find(methodName, "Time-dependent") != std::string::npos;
434
979k
}
435
436
// ---------------------------------------------------------------------------
437
438
std::string computeConcatenatedName(
439
133k
    const std::vector<CoordinateOperationNNPtr> &flattenOps) {
440
133k
    std::string name;
441
456k
    for (const auto &subOp : flattenOps) {
442
456k
        if (!name.empty()) {
443
322k
            name += " + ";
444
322k
        }
445
456k
        const auto &l_name = subOp->nameStr();
446
456k
        if (l_name.empty()) {
447
5
            name += "unnamed";
448
456k
        } else {
449
456k
            name += l_name;
450
456k
        }
451
456k
    }
452
133k
    return name;
453
133k
}
454
455
// ---------------------------------------------------------------------------
456
457
metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op,
458
                              bool conversionExtentIsWorld,
459
466k
                              bool &emptyIntersection) {
460
466k
    auto conv = dynamic_cast<const Conversion *>(op.get());
461
466k
    if (conv) {
462
153k
        emptyIntersection = false;
463
153k
        return metadata::Extent::WORLD;
464
153k
    }
465
313k
    const auto &domains = op->domains();
466
313k
    if (!domains.empty()) {
467
246k
        emptyIntersection = false;
468
246k
        return domains[0]->domainOfValidity();
469
246k
    }
470
67.5k
    auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
471
67.5k
    if (!concatenated) {
472
61.5k
        emptyIntersection = false;
473
61.5k
        return nullptr;
474
61.5k
    }
475
5.98k
    return getExtent(concatenated->operations(), conversionExtentIsWorld,
476
5.98k
                     emptyIntersection);
477
67.5k
}
478
479
// ---------------------------------------------------------------------------
480
481
static const metadata::ExtentPtr nullExtent{};
482
483
163k
const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) {
484
163k
    const auto &domains = crs->domains();
485
163k
    if (!domains.empty()) {
486
109k
        return domains[0]->domainOfValidity();
487
109k
    }
488
53.9k
    const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get());
489
53.9k
    if (boundCRS) {
490
7.49k
        return getExtent(boundCRS->baseCRS());
491
7.49k
    }
492
46.4k
    return nullExtent;
493
53.9k
}
494
495
const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs,
496
45.3k
                                                       bool &approxOut) {
497
45.3k
    const auto &rawExtent(getExtent(crs));
498
45.3k
    approxOut = false;
499
45.3k
    if (rawExtent)
500
29.4k
        return rawExtent;
501
15.8k
    const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get());
502
15.8k
    if (compoundCRS) {
503
        // For a compoundCRS, take the intersection of the extent of its
504
        // components.
505
3.13k
        const auto &components = compoundCRS->componentReferenceSystems();
506
3.13k
        metadata::ExtentPtr extent;
507
3.13k
        approxOut = true;
508
6.33k
        for (const auto &component : components) {
509
6.33k
            const auto &componentExtent(getExtent(component));
510
6.33k
            if (extent && componentExtent)
511
44
                extent = extent->intersection(NN_NO_CHECK(componentExtent));
512
6.28k
            else if (componentExtent)
513
78
                extent = componentExtent;
514
6.33k
        }
515
3.13k
        return extent;
516
3.13k
    }
517
12.7k
    return rawExtent;
518
15.8k
}
519
520
// ---------------------------------------------------------------------------
521
522
metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
523
                              bool conversionExtentIsWorld,
524
107k
                              bool &emptyIntersection) {
525
107k
    metadata::ExtentPtr res = nullptr;
526
351k
    for (const auto &subop : ops) {
527
528
351k
        const auto &subExtent =
529
351k
            getExtent(subop, conversionExtentIsWorld, emptyIntersection);
530
351k
        if (!subExtent) {
531
66.5k
            if (emptyIntersection) {
532
0
                return nullptr;
533
0
            }
534
66.5k
            continue;
535
66.5k
        }
536
285k
        if (res == nullptr) {
537
99.7k
            res = subExtent;
538
185k
        } else {
539
185k
            res = res->intersection(NN_NO_CHECK(subExtent));
540
185k
            if (!res) {
541
14.7k
                emptyIntersection = true;
542
14.7k
                return nullptr;
543
14.7k
            }
544
185k
        }
545
285k
    }
546
92.2k
    emptyIntersection = false;
547
92.2k
    return res;
548
107k
}
549
550
// ---------------------------------------------------------------------------
551
552
// Returns the accuracy of an operation, or -1 if unknown
553
395k
double getAccuracy(const CoordinateOperationNNPtr &op) {
554
555
395k
    if (dynamic_cast<const Conversion *>(op.get())) {
556
        // A conversion is perfectly accurate.
557
140k
        return 0.0;
558
140k
    }
559
560
254k
    double accuracy = -1.0;
561
254k
    const auto &accuracies = op->coordinateOperationAccuracies();
562
254k
    if (!accuracies.empty()) {
563
127k
        try {
564
127k
            accuracy = c_locale_stod(accuracies[0]->value());
565
127k
        } catch (const std::exception &) {
566
0
        }
567
127k
    } else {
568
127k
        auto concatenated =
569
127k
            dynamic_cast<const ConcatenatedOperation *>(op.get());
570
127k
        if (concatenated) {
571
36.4k
            accuracy = getAccuracy(concatenated->operations());
572
36.4k
        }
573
127k
    }
574
254k
    return accuracy;
575
254k
}
576
577
// ---------------------------------------------------------------------------
578
579
// Returns the accuracy of a set of concatenated operations, or -1 if unknown
580
122k
double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) {
581
122k
    double accuracy = -1.0;
582
306k
    for (const auto &subop : ops) {
583
306k
        const double subops_accuracy = getAccuracy(subop);
584
306k
        if (subops_accuracy < 0.0) {
585
86.5k
            return -1.0;
586
86.5k
        }
587
220k
        if (accuracy < 0.0) {
588
99.6k
            accuracy = 0.0;
589
99.6k
        }
590
220k
        accuracy += subops_accuracy;
591
220k
    }
592
36.2k
    return accuracy;
593
122k
}
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