Coverage Report

Created: 2025-08-28 06:57

/src/proj/src/iso19111/operation/oputils.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#include <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
26.6k
OperationParameterNNPtr createOpParamNameEPSGCode(int code) {
72
26.6k
    const char *name = OperationParameter::getNameForEPSGCode(code);
73
26.6k
    assert(name);
74
26.6k
    return OperationParameter::create(createMapNameEPSGCode(name, code));
75
26.6k
}
76
77
// ---------------------------------------------------------------------------
78
79
6.93k
util::PropertyMap createMethodMapNameEPSGCode(int code) {
80
6.93k
    const char *name = nullptr;
81
6.93k
    size_t nMethodNameCodes = 0;
82
6.93k
    const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes);
83
463k
    for (size_t i = 0; i < nMethodNameCodes; ++i) {
84
463k
        const auto &tuple = methodNameCodes[i];
85
463k
        if (tuple.epsg_code == code) {
86
6.93k
            name = tuple.name;
87
6.93k
            break;
88
6.93k
        }
89
463k
    }
90
6.93k
    assert(name);
91
6.93k
    return createMapNameEPSGCode(name, code);
92
6.93k
}
93
94
// ---------------------------------------------------------------------------
95
96
1.39k
util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) {
97
1.39k
    return util::PropertyMap()
98
1.39k
        .set(common::IdentifiedObject::NAME_KEY, name)
99
1.39k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
100
1.39k
        .set(metadata::Identifier::CODE_KEY, code);
101
1.39k
}
102
103
// ---------------------------------------------------------------------------
104
105
33.6k
util::PropertyMap createMapNameEPSGCode(const char *name, int code) {
106
33.6k
    return util::PropertyMap()
107
33.6k
        .set(common::IdentifiedObject::NAME_KEY, name)
108
33.6k
        .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG)
109
33.6k
        .set(metadata::Identifier::CODE_KEY, code);
110
33.6k
}
111
112
// ---------------------------------------------------------------------------
113
114
util::PropertyMap &addDomains(util::PropertyMap &map,
115
479
                              const common::ObjectUsage *obj) {
116
117
479
    auto ar = util::ArrayOfBaseObject::create();
118
507
    for (const auto &domain : obj->domains()) {
119
507
        ar->add(domain);
120
507
    }
121
479
    if (!ar->empty()) {
122
479
        map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar);
123
479
    }
124
479
    return map;
125
479
}
126
127
// ---------------------------------------------------------------------------
128
129
44
static const char *getCRSQualifierStr(const crs::CRSPtr &crs) {
130
44
    auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get());
131
44
    if (geod) {
132
44
        if (geod->isGeocentric()) {
133
22
            return " (geocentric)";
134
22
        }
135
22
        auto geog = dynamic_cast<crs::GeographicCRS *>(geod);
136
22
        if (geog) {
137
22
            if (geog->coordinateSystem()->axisList().size() == 2) {
138
8
                return " (geog2D)";
139
14
            } else {
140
14
                return " (geog3D)";
141
14
            }
142
22
        }
143
22
    }
144
0
    return "";
145
44
}
146
147
// ---------------------------------------------------------------------------
148
149
std::string buildOpName(const char *opType, const crs::CRSPtr &source,
150
78
                        const crs::CRSPtr &target) {
151
78
    std::string res(opType);
152
78
    const auto &srcName = source->nameStr();
153
78
    const auto &targetName = target->nameStr();
154
78
    const char *srcQualifier = "";
155
78
    const char *targetQualifier = "";
156
78
    if (srcName == targetName) {
157
22
        srcQualifier = getCRSQualifierStr(source);
158
22
        targetQualifier = getCRSQualifierStr(target);
159
22
        if (strcmp(srcQualifier, targetQualifier) == 0) {
160
0
            srcQualifier = "";
161
0
            targetQualifier = "";
162
0
        }
163
22
    }
164
78
    res += " from ";
165
78
    res += srcName;
166
78
    res += srcQualifier;
167
78
    res += " to ";
168
78
    res += targetName;
169
78
    res += targetQualifier;
170
78
    return res;
171
78
}
172
173
// ---------------------------------------------------------------------------
174
175
void addModifiedIdentifier(util::PropertyMap &map,
176
                           const common::IdentifiedObject *obj, bool inverse,
177
494
                           bool derivedFrom) {
178
    // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE
179
    // as identifier.
180
181
494
    auto ar = util::ArrayOfBaseObject::create();
182
494
    for (const auto &idSrc : obj->identifiers()) {
183
481
        auto authName = *(idSrc->codeSpace());
184
481
        const auto &srcCode = idSrc->code();
185
481
        if (derivedFrom) {
186
434
            authName = concat("DERIVED_FROM(", authName, ")");
187
434
        }
188
481
        if (inverse) {
189
58
            if (starts_with(authName, "INVERSE(") && authName.back() == ')') {
190
11
                authName = authName.substr(strlen("INVERSE("));
191
11
                authName.resize(authName.size() - 1);
192
47
            } else {
193
47
                authName = concat("INVERSE(", authName, ")");
194
47
            }
195
58
        }
196
481
        auto idsProp = util::PropertyMap().set(
197
481
            metadata::Identifier::CODESPACE_KEY, authName);
198
481
        ar->add(metadata::Identifier::create(srcCode, idsProp));
199
481
    }
200
494
    if (!ar->empty()) {
201
481
        map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
202
481
    }
203
494
}
204
205
// ---------------------------------------------------------------------------
206
207
util::PropertyMap
208
15
createPropertiesForInverse(const OperationMethodNNPtr &method) {
209
15
    util::PropertyMap map;
210
211
15
    const std::string &forwardName = method->nameStr();
212
15
    if (!forwardName.empty()) {
213
15
        if (starts_with(forwardName, INVERSE_OF)) {
214
0
            map.set(common::IdentifiedObject::NAME_KEY,
215
0
                    forwardName.substr(INVERSE_OF.size()));
216
15
        } else {
217
15
            map.set(common::IdentifiedObject::NAME_KEY,
218
15
                    INVERSE_OF + forwardName);
219
15
        }
220
15
    }
221
222
15
    addModifiedIdentifier(map, method.get(), true, false);
223
224
15
    return map;
225
15
}
226
227
// ---------------------------------------------------------------------------
228
229
util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op,
230
                                             bool derivedFrom,
231
56
                                             bool approximateInversion) {
232
56
    assert(op);
233
56
    util::PropertyMap map;
234
235
    // The domain(s) are unchanged by the inverse operation
236
56
    addDomains(map, op);
237
238
56
    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
56
    const char *opType;
243
56
    if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) {
244
0
        opType = BALLPARK_GEOCENTRIC_TRANSLATION;
245
56
    } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) {
246
0
        opType = BALLPARK_GEOGRAPHIC_OFFSET;
247
56
    } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) {
248
0
        opType = NULL_GEOGRAPHIC_OFFSET;
249
56
    } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) {
250
0
        opType = NULL_GEOCENTRIC_TRANSLATION;
251
56
    } else if (dynamic_cast<const Transformation *>(op) ||
252
56
               starts_with(forwardName, "Transformation from ")) {
253
26
        opType = "Transformation";
254
30
    } else if (dynamic_cast<const Conversion *>(op)) {
255
0
        opType = "Conversion";
256
30
    } else {
257
30
        opType = "Operation";
258
30
    }
259
260
56
    auto sourceCRS = op->sourceCRS();
261
56
    auto targetCRS = op->targetCRS();
262
56
    std::string name;
263
56
    if (!forwardName.empty()) {
264
56
        if (dynamic_cast<const Transformation *>(op) == nullptr &&
265
56
            dynamic_cast<const ConcatenatedOperation *>(op) == nullptr &&
266
56
            (starts_with(forwardName, INVERSE_OF) ||
267
30
             forwardName.find(" + ") != std::string::npos)) {
268
0
            std::vector<std::string> tokens;
269
0
            std::string curToken;
270
0
            bool inString = false;
271
0
            for (size_t i = 0; i < forwardName.size(); ++i) {
272
0
                if (inString) {
273
0
                    curToken += forwardName[i];
274
0
                    if (forwardName[i] == '\'') {
275
0
                        inString = false;
276
0
                    }
277
0
                } else if (i + 3 < forwardName.size() &&
278
0
                           memcmp(&forwardName[i], " + ", 3) == 0) {
279
0
                    tokens.push_back(curToken);
280
0
                    curToken.clear();
281
0
                    i += 2;
282
0
                } else if (forwardName[i] == '\'') {
283
0
                    inString = true;
284
0
                    curToken += forwardName[i];
285
0
                } else {
286
0
                    curToken += forwardName[i];
287
0
                }
288
0
            }
289
0
            if (!curToken.empty()) {
290
0
                tokens.push_back(std::move(curToken));
291
0
            }
292
0
            for (size_t i = tokens.size(); i > 0;) {
293
0
                i--;
294
0
                if (!name.empty()) {
295
0
                    name += " + ";
296
0
                }
297
0
                if (starts_with(tokens[i], INVERSE_OF)) {
298
0
                    name += tokens[i].substr(INVERSE_OF.size());
299
0
                } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME ||
300
0
                           tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) {
301
0
                    name += tokens[i];
302
0
                } else {
303
0
                    name += INVERSE_OF + tokens[i];
304
0
                }
305
0
            }
306
56
        } else if (!sourceCRS || !targetCRS ||
307
56
                   forwardName != buildOpName(opType, sourceCRS, targetCRS)) {
308
56
            if (forwardName.find(" + ") != std::string::npos) {
309
1
                name = INVERSE_OF + '\'' + forwardName + '\'';
310
55
            } else {
311
55
                name = INVERSE_OF + forwardName;
312
55
            }
313
56
        }
314
56
    }
315
56
    if (name.empty() && sourceCRS && targetCRS) {
316
0
        name = buildOpName(opType, targetCRS, sourceCRS);
317
0
    }
318
56
    if (approximateInversion) {
319
0
        name += " (approx. inversion)";
320
0
    }
321
322
56
    if (!name.empty()) {
323
56
        map.set(common::IdentifiedObject::NAME_KEY, name);
324
56
    }
325
326
56
    const std::string &remarks = op->remarks();
327
56
    if (!remarks.empty()) {
328
26
        map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
329
26
    }
330
331
56
    addModifiedIdentifier(map, op, true, derivedFrom);
332
333
56
    const auto so = dynamic_cast<const SingleOperation *>(op);
334
56
    if (so) {
335
56
        const int soMethodEPSGCode = so->method()->getEPSGCode();
336
56
        if (soMethodEPSGCode > 0) {
337
13
            map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode);
338
13
        }
339
56
    }
340
341
56
    return map;
342
56
}
343
344
// ---------------------------------------------------------------------------
345
346
util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties,
347
19.0k
                                         const std::string &defaultName) {
348
19.0k
    if (!properties.get(common::IdentifiedObject::NAME_KEY)) {
349
16.8k
        return util::PropertyMap(properties)
350
16.8k
            .set(common::IdentifiedObject::NAME_KEY, defaultName);
351
16.8k
    } else {
352
2.20k
        return properties;
353
2.20k
    }
354
19.0k
}
355
356
// ---------------------------------------------------------------------------
357
358
static std::string createEntryEqParam(const std::string &a,
359
25.5k
                                      const std::string &b) {
360
25.5k
    return a < b ? a + b : b + a;
361
25.5k
}
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
25.4k
bool areEquivalentParameters(const std::string &a, const std::string &b) {
419
420
25.4k
    static const std::set<std::string> setEquivalentParameters =
421
25.4k
        buildSetEquivalentParameters();
422
423
25.4k
    auto a_can = metadata::Identifier::canonicalizeName(a);
424
25.4k
    auto b_can = metadata::Identifier::canonicalizeName(b);
425
25.4k
    return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) !=
426
25.4k
           setEquivalentParameters.end();
427
25.4k
}
428
429
// ---------------------------------------------------------------------------
430
431
84.3k
bool isTimeDependent(const std::string &methodName) {
432
84.3k
    return ci_find(methodName, "Time dependent") != std::string::npos ||
433
84.3k
           ci_find(methodName, "Time-dependent") != std::string::npos;
434
84.3k
}
435
436
// ---------------------------------------------------------------------------
437
438
std::string computeConcatenatedName(
439
0
    const std::vector<CoordinateOperationNNPtr> &flattenOps) {
440
0
    std::string name;
441
0
    for (const auto &subOp : flattenOps) {
442
0
        if (!name.empty()) {
443
0
            name += " + ";
444
0
        }
445
0
        const auto &l_name = subOp->nameStr();
446
0
        if (l_name.empty()) {
447
0
            name += "unnamed";
448
0
        } else {
449
0
            name += l_name;
450
0
        }
451
0
    }
452
0
    return name;
453
0
}
454
455
// ---------------------------------------------------------------------------
456
457
metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op,
458
                              bool conversionExtentIsWorld,
459
0
                              bool &emptyIntersection) {
460
0
    auto conv = dynamic_cast<const Conversion *>(op.get());
461
0
    if (conv) {
462
0
        emptyIntersection = false;
463
0
        return metadata::Extent::WORLD;
464
0
    }
465
0
    const auto &domains = op->domains();
466
0
    if (!domains.empty()) {
467
0
        emptyIntersection = false;
468
0
        return domains[0]->domainOfValidity();
469
0
    }
470
0
    auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get());
471
0
    if (!concatenated) {
472
0
        emptyIntersection = false;
473
0
        return nullptr;
474
0
    }
475
0
    return getExtent(concatenated->operations(), conversionExtentIsWorld,
476
0
                     emptyIntersection);
477
0
}
478
479
// ---------------------------------------------------------------------------
480
481
static const metadata::ExtentPtr nullExtent{};
482
483
0
const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) {
484
0
    const auto &domains = crs->domains();
485
0
    if (!domains.empty()) {
486
0
        return domains[0]->domainOfValidity();
487
0
    }
488
0
    const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get());
489
0
    if (boundCRS) {
490
0
        return getExtent(boundCRS->baseCRS());
491
0
    }
492
0
    return nullExtent;
493
0
}
494
495
const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs,
496
0
                                                       bool &approxOut) {
497
0
    const auto &rawExtent(getExtent(crs));
498
0
    approxOut = false;
499
0
    if (rawExtent)
500
0
        return rawExtent;
501
0
    const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get());
502
0
    if (compoundCRS) {
503
        // For a compoundCRS, take the intersection of the extent of its
504
        // components.
505
0
        const auto &components = compoundCRS->componentReferenceSystems();
506
0
        metadata::ExtentPtr extent;
507
0
        approxOut = true;
508
0
        for (const auto &component : components) {
509
0
            const auto &componentExtent(getExtent(component));
510
0
            if (extent && componentExtent)
511
0
                extent = extent->intersection(NN_NO_CHECK(componentExtent));
512
0
            else if (componentExtent)
513
0
                extent = componentExtent;
514
0
        }
515
0
        return extent;
516
0
    }
517
0
    return rawExtent;
518
0
}
519
520
// ---------------------------------------------------------------------------
521
522
metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops,
523
                              bool conversionExtentIsWorld,
524
0
                              bool &emptyIntersection) {
525
0
    metadata::ExtentPtr res = nullptr;
526
0
    for (const auto &subop : ops) {
527
528
0
        const auto &subExtent =
529
0
            getExtent(subop, conversionExtentIsWorld, emptyIntersection);
530
0
        if (!subExtent) {
531
0
            if (emptyIntersection) {
532
0
                return nullptr;
533
0
            }
534
0
            continue;
535
0
        }
536
0
        if (res == nullptr) {
537
0
            res = subExtent;
538
0
        } else {
539
0
            res = res->intersection(NN_NO_CHECK(subExtent));
540
0
            if (!res) {
541
0
                emptyIntersection = true;
542
0
                return nullptr;
543
0
            }
544
0
        }
545
0
    }
546
0
    emptyIntersection = false;
547
0
    return res;
548
0
}
549
550
// ---------------------------------------------------------------------------
551
552
// Returns the accuracy of an operation, or -1 if unknown
553
0
double getAccuracy(const CoordinateOperationNNPtr &op) {
554
555
0
    if (dynamic_cast<const Conversion *>(op.get())) {
556
        // A conversion is perfectly accurate.
557
0
        return 0.0;
558
0
    }
559
560
0
    double accuracy = -1.0;
561
0
    const auto &accuracies = op->coordinateOperationAccuracies();
562
0
    if (!accuracies.empty()) {
563
0
        try {
564
0
            accuracy = c_locale_stod(accuracies[0]->value());
565
0
        } catch (const std::exception &) {
566
0
        }
567
0
    } else {
568
0
        auto concatenated =
569
0
            dynamic_cast<const ConcatenatedOperation *>(op.get());
570
0
        if (concatenated) {
571
0
            accuracy = getAccuracy(concatenated->operations());
572
0
        }
573
0
    }
574
0
    return accuracy;
575
0
}
576
577
// ---------------------------------------------------------------------------
578
579
// Returns the accuracy of a set of concatenated operations, or -1 if unknown
580
0
double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) {
581
0
    double accuracy = -1.0;
582
0
    for (const auto &subop : ops) {
583
0
        const double subops_accuracy = getAccuracy(subop);
584
0
        if (subops_accuracy < 0.0) {
585
0
            return -1.0;
586
0
        }
587
0
        if (accuracy < 0.0) {
588
0
            accuracy = 0.0;
589
0
        }
590
0
        accuracy += subops_accuracy;
591
0
    }
592
0
    return accuracy;
593
0
}
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