Coverage Report

Created: 2026-07-14 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proj/src/iso19111/operation/singleoperation.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/common.hpp"
34
#include "proj/coordinateoperation.hpp"
35
#include "proj/crs.hpp"
36
#include "proj/io.hpp"
37
#include "proj/metadata.hpp"
38
#include "proj/util.hpp"
39
40
#include "proj/internal/crs_internal.hpp"
41
#include "proj/internal/internal.hpp"
42
#include "proj/internal/io_internal.hpp"
43
44
#include "coordinateoperation_internal.hpp"
45
#include "coordinateoperation_private.hpp"
46
#include "operationmethod_private.hpp"
47
#include "oputils.hpp"
48
#include "parammappings.hpp"
49
#include "vectorofvaluesparams.hpp"
50
51
// PROJ include order is sensitive
52
// clang-format off
53
#include "proj.h"
54
#include "proj_internal.h" // M_PI
55
// clang-format on
56
#include "proj_constants.h"
57
#include "proj_json_streaming_writer.hpp"
58
59
#include <algorithm>
60
#include <cassert>
61
#include <cmath>
62
#include <cstring>
63
#include <memory>
64
#include <set>
65
#include <string>
66
#include <vector>
67
68
using namespace NS_PROJ::internal;
69
70
// ---------------------------------------------------------------------------
71
72
NS_PROJ_START
73
namespace operation {
74
75
// ---------------------------------------------------------------------------
76
77
//! @cond Doxygen_Suppress
78
79
InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
80
    const std::string &message)
81
0
    : InvalidOperation(message) {}
82
83
InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection(
84
0
    const InvalidOperationEmptyIntersection &) = default;
85
86
0
InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() =
87
    default;
88
89
//! @endcond
90
91
// ---------------------------------------------------------------------------
92
93
//! @cond Doxygen_Suppress
94
95
// ---------------------------------------------------------------------------
96
97
GridDescription::GridDescription()
98
0
    : shortName{}, fullName{}, packageName{}, url{}, directDownload(false),
99
0
      openLicense(false), available(false) {}
100
101
0
GridDescription::~GridDescription() = default;
102
103
0
GridDescription::GridDescription(const GridDescription &) = default;
104
105
GridDescription::GridDescription(GridDescription &&other) noexcept
106
0
    : shortName(std::move(other.shortName)),
107
0
      fullName(std::move(other.fullName)),
108
0
      packageName(std::move(other.packageName)), url(std::move(other.url)),
109
0
      directDownload(other.directDownload), openLicense(other.openLicense),
110
0
      available(other.available) {}
111
112
//! @endcond
113
114
// ---------------------------------------------------------------------------
115
116
0
CoordinateOperation::CoordinateOperation() : d(std::make_unique<Private>()) {}
117
118
// ---------------------------------------------------------------------------
119
120
CoordinateOperation::CoordinateOperation(const CoordinateOperation &other)
121
0
    : ObjectUsage(other), d(std::make_unique<Private>(*other.d)) {}
122
123
// ---------------------------------------------------------------------------
124
125
//! @cond Doxygen_Suppress
126
0
CoordinateOperation::~CoordinateOperation() = default;
127
//! @endcond
128
129
// ---------------------------------------------------------------------------
130
131
/** \brief Return the version of the coordinate transformation (i.e.
132
 * instantiation
133
 * due to the stochastic nature of the parameters).
134
 *
135
 * Mandatory when describing a coordinate transformation or point motion
136
 * operation, and should not be supplied for a coordinate conversion.
137
 *
138
 * @return version or empty.
139
 */
140
const util::optional<std::string> &
141
0
CoordinateOperation::operationVersion() const {
142
0
    return d->operationVersion_;
143
0
}
144
145
// ---------------------------------------------------------------------------
146
147
/** \brief Return estimate(s) of the impact of this coordinate operation on
148
 * point accuracy.
149
 *
150
 * Gives position error estimates for target coordinates of this coordinate
151
 * operation, assuming no errors in source coordinates.
152
 *
153
 * @return estimate(s) or empty vector.
154
 */
155
const std::vector<metadata::PositionalAccuracyNNPtr> &
156
0
CoordinateOperation::coordinateOperationAccuracies() const {
157
0
    return d->coordinateOperationAccuracies_;
158
0
}
159
160
// ---------------------------------------------------------------------------
161
162
/** \brief Return the source CRS of this coordinate operation.
163
 *
164
 * This should not be null, expect for of a derivingConversion of a DerivedCRS
165
 * when the owning DerivedCRS has been destroyed.
166
 *
167
 * @return source CRS, or null.
168
 */
169
0
const crs::CRSPtr CoordinateOperation::sourceCRS() const {
170
0
    return d->sourceCRSWeak_.lock();
171
0
}
172
173
// ---------------------------------------------------------------------------
174
175
/** \brief Return the target CRS of this coordinate operation.
176
 *
177
 * This should not be null, expect for of a derivingConversion of a DerivedCRS
178
 * when the owning DerivedCRS has been destroyed.
179
 *
180
 * @return target CRS, or null.
181
 */
182
0
const crs::CRSPtr CoordinateOperation::targetCRS() const {
183
0
    return d->targetCRSWeak_.lock();
184
0
}
185
186
// ---------------------------------------------------------------------------
187
188
/** \brief Return the interpolation CRS of this coordinate operation.
189
 *
190
 * @return interpolation CRS, or null.
191
 */
192
0
const crs::CRSPtr &CoordinateOperation::interpolationCRS() const {
193
0
    return d->interpolationCRS_;
194
0
}
195
196
// ---------------------------------------------------------------------------
197
198
/** \brief Return the source epoch of coordinates.
199
 *
200
 * @return source epoch of coordinates, or empty.
201
 */
202
const util::optional<common::DataEpoch> &
203
0
CoordinateOperation::sourceCoordinateEpoch() const {
204
0
    return *(d->sourceCoordinateEpoch_);
205
0
}
206
207
// ---------------------------------------------------------------------------
208
209
/** \brief Return the target epoch of coordinates.
210
 *
211
 * @return target epoch of coordinates, or empty.
212
 */
213
const util::optional<common::DataEpoch> &
214
0
CoordinateOperation::targetCoordinateEpoch() const {
215
0
    return *(d->targetCoordinateEpoch_);
216
0
}
217
218
// ---------------------------------------------------------------------------
219
220
void CoordinateOperation::setWeakSourceTargetCRS(
221
0
    std::weak_ptr<crs::CRS> sourceCRSIn, std::weak_ptr<crs::CRS> targetCRSIn) {
222
0
    d->sourceCRSWeak_ = std::move(sourceCRSIn);
223
0
    d->targetCRSWeak_ = std::move(targetCRSIn);
224
0
}
225
226
// ---------------------------------------------------------------------------
227
228
void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn,
229
                                  const crs::CRSNNPtr &targetCRSIn,
230
0
                                  const crs::CRSPtr &interpolationCRSIn) {
231
0
    d->strongRef_ =
232
0
        std::make_unique<Private::CRSStrongRef>(sourceCRSIn, targetCRSIn);
233
0
    d->sourceCRSWeak_ = sourceCRSIn.as_nullable();
234
0
    d->targetCRSWeak_ = targetCRSIn.as_nullable();
235
0
    d->interpolationCRS_ = interpolationCRSIn;
236
0
}
237
238
// ---------------------------------------------------------------------------
239
240
void CoordinateOperation::setCRSsUpdateInverse(
241
    const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
242
0
    const crs::CRSPtr &interpolationCRSIn) {
243
0
    setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
244
245
0
    auto invCO = dynamic_cast<InverseCoordinateOperation *>(this);
246
0
    if (invCO) {
247
0
        invCO->forwardOperation()->setCRSs(targetCRSIn, sourceCRSIn,
248
0
                                           interpolationCRSIn);
249
0
    }
250
251
0
    auto transf = dynamic_cast<Transformation *>(this);
252
0
    if (transf) {
253
0
        transf->inverseAsTransformation()->setCRSs(targetCRSIn, sourceCRSIn,
254
0
                                                   interpolationCRSIn);
255
0
    }
256
257
0
    auto concat = dynamic_cast<ConcatenatedOperation *>(this);
258
0
    if (concat) {
259
0
        auto first = concat->operations().front().get();
260
0
        auto &firstTarget(first->targetCRS());
261
0
        if (firstTarget) {
262
0
            first->setCRSsUpdateInverse(sourceCRSIn, NN_NO_CHECK(firstTarget),
263
0
                                        first->interpolationCRS());
264
0
        }
265
0
        auto last = concat->operations().back().get();
266
0
        auto &lastSource(last->sourceCRS());
267
0
        if (lastSource) {
268
0
            last->setCRSsUpdateInverse(NN_NO_CHECK(lastSource), targetCRSIn,
269
0
                                       last->interpolationCRS());
270
0
        }
271
0
    }
272
0
}
273
274
// ---------------------------------------------------------------------------
275
276
void CoordinateOperation::setInterpolationCRS(
277
0
    const crs::CRSPtr &interpolationCRSIn) {
278
0
    d->interpolationCRS_ = interpolationCRSIn;
279
0
}
280
281
// ---------------------------------------------------------------------------
282
283
void CoordinateOperation::setCRSs(const CoordinateOperation *in,
284
0
                                  bool inverseSourceTarget) {
285
0
    auto l_sourceCRS = in->sourceCRS();
286
0
    auto l_targetCRS = in->targetCRS();
287
0
    if (l_sourceCRS && l_targetCRS) {
288
0
        auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS);
289
0
        auto nn_targetCRS = NN_NO_CHECK(l_targetCRS);
290
0
        if (inverseSourceTarget) {
291
0
            setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS());
292
0
        } else {
293
0
            setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS());
294
0
        }
295
0
    }
296
0
}
297
298
// ---------------------------------------------------------------------------
299
300
void CoordinateOperation::setSourceCoordinateEpoch(
301
0
    const util::optional<common::DataEpoch> &epoch) {
302
0
    d->sourceCoordinateEpoch_ =
303
0
        std::make_shared<util::optional<common::DataEpoch>>(epoch);
304
305
0
    if (epoch) {
306
0
        auto invOp = dynamic_cast<InverseCoordinateOperation *>(this);
307
0
        if (invOp)
308
0
            invOp->forwardOperation()->setTargetCoordinateEpoch(epoch);
309
0
    }
310
0
}
311
312
// ---------------------------------------------------------------------------
313
314
void CoordinateOperation::setTargetCoordinateEpoch(
315
0
    const util::optional<common::DataEpoch> &epoch) {
316
0
    d->targetCoordinateEpoch_ =
317
0
        std::make_shared<util::optional<common::DataEpoch>>(epoch);
318
319
0
    if (epoch) {
320
0
        auto invOp = dynamic_cast<InverseCoordinateOperation *>(this);
321
0
        if (invOp)
322
0
            invOp->forwardOperation()->setSourceCoordinateEpoch(epoch);
323
0
    }
324
0
}
325
326
// ---------------------------------------------------------------------------
327
328
void CoordinateOperation::setAccuracies(
329
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
330
0
    d->coordinateOperationAccuracies_ = accuracies;
331
0
}
332
333
// ---------------------------------------------------------------------------
334
335
/** \brief Return whether a coordinate operation can be instantiated as
336
 * a PROJ pipeline, checking in particular that referenced grids are
337
 * available.
338
 */
339
bool CoordinateOperation::isPROJInstantiable(
340
    const io::DatabaseContextPtr &databaseContext,
341
0
    bool considerKnownGridsAsAvailable) const {
342
0
    try {
343
0
        exportToPROJString(io::PROJStringFormatter::create().get());
344
0
    } catch (const std::exception &) {
345
0
        return false;
346
0
    }
347
0
    for (const auto &gridDesc :
348
0
         gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) {
349
        // Grid name starting with @ are considered as optional.
350
0
        if (!gridDesc.available &&
351
0
            (gridDesc.shortName.empty() || gridDesc.shortName[0] != '@')) {
352
0
            return false;
353
0
        }
354
0
    }
355
0
    return true;
356
0
}
357
358
// ---------------------------------------------------------------------------
359
360
/** \brief Return whether a coordinate operation has a "ballpark"
361
 * transformation,
362
 * that is a very approximate one, due to lack of more accurate transformations.
363
 *
364
 * Typically a null geographic offset between two horizontal datum, or a
365
 * null vertical offset (or limited to unit changes) between two vertical
366
 * datum. Errors of several tens to one hundred meters might be expected,
367
 * compared to more accurate transformations.
368
 */
369
0
bool CoordinateOperation::hasBallparkTransformation() const {
370
0
    return d->hasBallparkTransformation_;
371
0
}
372
373
// ---------------------------------------------------------------------------
374
375
0
void CoordinateOperation::setHasBallparkTransformation(bool b) {
376
0
    d->hasBallparkTransformation_ = b;
377
0
}
378
379
// ---------------------------------------------------------------------------
380
381
/** \brief Return whether a coordinate operation requires coordinate tuples
382
 * to have a valid input time for the coordinate transformation to succeed.
383
 * (this applies for the forward direction)
384
 *
385
 * Note: in the case of a time-dependent Helmert transformation, this function
386
 * will return true, but when executing proj_trans(), execution will still
387
 * succeed if the time information is missing, due to the transformation central
388
 * epoch being used as a fallback.
389
 *
390
 * @since 9.5
391
 */
392
0
bool CoordinateOperation::requiresPerCoordinateInputTime() const {
393
0
    return d->requiresPerCoordinateInputTime_ &&
394
0
           !d->sourceCoordinateEpoch_->has_value();
395
0
}
396
397
// ---------------------------------------------------------------------------
398
399
0
void CoordinateOperation::setRequiresPerCoordinateInputTime(bool b) {
400
0
    d->requiresPerCoordinateInputTime_ = b;
401
0
}
402
403
// ---------------------------------------------------------------------------
404
405
void CoordinateOperation::setProperties(
406
    const util::PropertyMap &properties) // throw(InvalidValueTypeException)
407
0
{
408
0
    ObjectUsage::setProperties(properties);
409
0
    properties.getStringValue(OPERATION_VERSION_KEY, d->operationVersion_);
410
0
}
411
412
// ---------------------------------------------------------------------------
413
414
/** \brief Return a variation of the current coordinate operation whose axis
415
 * order is the one expected for visualization purposes.
416
 */
417
CoordinateOperationNNPtr
418
0
CoordinateOperation::normalizeForVisualization() const {
419
0
    auto l_sourceCRS = sourceCRS();
420
0
    auto l_targetCRS = targetCRS();
421
0
    if (!l_sourceCRS || !l_targetCRS) {
422
0
        throw util::UnsupportedOperationException(
423
0
            "Cannot retrieve source or target CRS");
424
0
    }
425
0
    const bool swapSource =
426
0
        l_sourceCRS->mustAxisOrderBeSwitchedForVisualization();
427
0
    const bool swapTarget =
428
0
        l_targetCRS->mustAxisOrderBeSwitchedForVisualization();
429
0
    auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>(
430
0
        shared_from_this().as_nullable()));
431
0
    if (!swapSource && !swapTarget) {
432
0
        return l_this;
433
0
    }
434
0
    std::vector<CoordinateOperationNNPtr> subOps;
435
0
    if (swapSource) {
436
0
        auto op = Conversion::createAxisOrderReversal(false);
437
0
        op->setCRSs(l_sourceCRS->normalizeForVisualization(),
438
0
                    NN_NO_CHECK(l_sourceCRS), nullptr);
439
0
        subOps.emplace_back(op);
440
0
    }
441
0
    subOps.emplace_back(l_this);
442
0
    if (swapTarget) {
443
0
        auto op = Conversion::createAxisOrderReversal(false);
444
0
        op->setCRSs(NN_NO_CHECK(l_targetCRS),
445
0
                    l_targetCRS->normalizeForVisualization(), nullptr);
446
0
        subOps.emplace_back(op);
447
0
    }
448
449
0
    util::PropertyMap properties;
450
    // The domain(s) are unchanged
451
0
    addDomains(properties, this);
452
0
    addModifiedIdentifier(properties, this, /* inverse = */ false,
453
0
                          /* derivedFrom = */ true);
454
455
0
    std::string name = nameStr();
456
0
    if (!name.empty()) {
457
0
        name += NORMALIZED_AXIS_ORDER_SUFFIX_STR;
458
0
        properties.set(common::IdentifiedObject::NAME_KEY, name);
459
0
    }
460
461
0
    const std::string &l_remarks = remarks();
462
0
    if (!l_remarks.empty()) {
463
0
        properties.set(common::IdentifiedObject::REMARKS_KEY, l_remarks);
464
0
    }
465
466
0
    std::vector<CoordinateOperationNNPtr> flattenOps;
467
0
    for (const auto &subOp : subOps) {
468
0
        auto subOpConcat =
469
0
            dynamic_cast<const ConcatenatedOperation *>(subOp.get());
470
0
        if (subOpConcat) {
471
0
            auto subSubOps = subOpConcat->operations();
472
0
            for (const auto &subSubOp : subSubOps) {
473
0
                flattenOps.emplace_back(subSubOp);
474
0
            }
475
0
        } else {
476
0
            flattenOps.emplace_back(subOp);
477
0
        }
478
0
    }
479
480
0
    return ConcatenatedOperation::create(properties, flattenOps,
481
0
                                         coordinateOperationAccuracies());
482
0
}
483
484
// ---------------------------------------------------------------------------
485
486
/** \brief Return a coordinate transformer for this operation.
487
 *
488
 * The returned coordinate transformer is tied to the provided context,
489
 * and should only be called by the thread "owning" the passed context.
490
 * It should not be used after the context has been destroyed.
491
 *
492
 * @param ctx Execution context to which the transformer will be tied to.
493
 *            If null, the default context will be used (only safe for
494
 *            single-threaded applications).
495
 * @return a new CoordinateTransformer instance.
496
 * @since 9.3
497
 * @throw UnsupportedOperationException if the transformer cannot be
498
 * instantiated.
499
 */
500
CoordinateTransformerNNPtr
501
0
CoordinateOperation::coordinateTransformer(PJ_CONTEXT *ctx) const {
502
0
    auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>(
503
0
        shared_from_this().as_nullable()));
504
0
    return CoordinateTransformer::create(l_this, ctx);
505
0
}
506
507
// ---------------------------------------------------------------------------
508
509
//! @cond Doxygen_Suppress
510
0
CoordinateOperationNNPtr CoordinateOperation::shallowClone() const {
511
0
    return _shallowClone();
512
0
}
513
//! @endcond
514
515
// ---------------------------------------------------------------------------
516
517
//! @cond Doxygen_Suppress
518
struct CoordinateTransformer::Private {
519
    PJ *pj_;
520
};
521
//! @endcond
522
523
// ---------------------------------------------------------------------------
524
525
CoordinateTransformer::CoordinateTransformer()
526
0
    : d(std::make_unique<Private>()) {}
527
528
// ---------------------------------------------------------------------------
529
530
//! @cond Doxygen_Suppress
531
0
CoordinateTransformer::~CoordinateTransformer() {
532
0
    if (d->pj_) {
533
0
        proj_assign_context(d->pj_, pj_get_default_ctx());
534
0
        proj_destroy(d->pj_);
535
0
    }
536
0
}
537
//! @endcond
538
539
// ---------------------------------------------------------------------------
540
541
CoordinateTransformerNNPtr
542
CoordinateTransformer::create(const CoordinateOperationNNPtr &op,
543
0
                              PJ_CONTEXT *ctx) {
544
0
    auto transformer = NN_NO_CHECK(
545
0
        CoordinateTransformer::make_unique<CoordinateTransformer>());
546
    // pj_obj_create does not sanitize the context
547
0
    if (ctx == nullptr)
548
0
        ctx = pj_get_default_ctx();
549
0
    transformer->d->pj_ = pj_obj_create(ctx, op);
550
0
    if (transformer->d->pj_ == nullptr)
551
0
        throw util::UnsupportedOperationException(
552
0
            "Cannot instantiate transformer");
553
0
    return transformer;
554
0
}
555
556
// ---------------------------------------------------------------------------
557
558
/** Transforms a coordinate tuple.
559
 *
560
 * PJ_COORD is a union of many structures. In the context of this method,
561
 * it is prudent to only use the v[] array, with the understanding that
562
 * the expected input values should be passed in the order and the unit of
563
 * the successive axis of the input CRS. Similarly the values returned in the
564
 * v[] array of the output PJ_COORD are in the order and the unit of the
565
 * successive axis of the output CRS.
566
 * For coordinate operations involving a time-dependent operation,
567
 * coord.v[3] is the decimal year of the coordinate epoch of the input (or
568
 * HUGE_VAL to indicate none)
569
 *
570
 * If an error occurs, HUGE_VAL is returned in the .v[0] member of the output
571
 * coordinate tuple.
572
 *
573
 * Example how to transform coordinates from EPSG:4326 (WGS 84
574
 * latitude/longitude) to EPSG:32631 (WGS 84 / UTM zone 31N).
575
\code{.cpp}
576
    auto authFactory =
577
        AuthorityFactory::create(DatabaseContext::create(), std::string());
578
    auto coord_op_ctxt = CoordinateOperationContext::create(
579
        authFactory, nullptr, 0.0);
580
    auto authFactoryEPSG =
581
        AuthorityFactory::create(DatabaseContext::create(), "EPSG");
582
    auto list = CoordinateOperationFactory::create()->createOperations(
583
        authFactoryEPSG->createCoordinateReferenceSystem("4326"),
584
        authFactoryEPSG->createCoordinateReferenceSystem("32631"),
585
        coord_op_ctxt);
586
    ASSERT_TRUE(!list.empty());
587
    PJ_CONTEXT* ctx = proj_context_create();
588
    auto transformer = list[0]->coordinateTransformer(ctx);
589
    PJ_COORD c;
590
    c.v[0] = 49; // latitude in degree
591
    c.v[1] = 2;  // longitude in degree
592
    c.v[2] = 0;
593
    c.v[3] = HUGE_VAL;
594
    c = transformer->transform(c);
595
    EXPECT_NEAR(c.v[0], 426857.98771728, 1e-8); // easting in metre
596
    EXPECT_NEAR(c.v[1], 5427937.52346492, 1e-8); // northing in metre
597
    proj_context_destroy(ctx);
598
\endcode
599
 */
600
0
PJ_COORD CoordinateTransformer::transform(PJ_COORD coord) {
601
0
    return proj_trans(d->pj_, PJ_FWD, coord);
602
0
}
603
604
// ---------------------------------------------------------------------------
605
606
0
OperationMethod::OperationMethod() : d(std::make_unique<Private>()) {}
607
608
// ---------------------------------------------------------------------------
609
610
OperationMethod::OperationMethod(const OperationMethod &other)
611
0
    : IdentifiedObject(other), d(std::make_unique<Private>(*other.d)) {}
612
613
// ---------------------------------------------------------------------------
614
615
//! @cond Doxygen_Suppress
616
0
OperationMethod::~OperationMethod() = default;
617
//! @endcond
618
619
// ---------------------------------------------------------------------------
620
621
/** \brief Return the formula(s) or procedure used by this coordinate operation
622
 * method.
623
 *
624
 * This may be a reference to a publication (in which case use
625
 * formulaCitation()).
626
 *
627
 * Note that the operation method may not be analytic, in which case this
628
 * attribute references or contains the procedure, not an analytic formula.
629
 *
630
 * @return the formula, or empty.
631
 */
632
0
const util::optional<std::string> &OperationMethod::formula() PROJ_PURE_DEFN {
633
0
    return d->formula_;
634
0
}
635
636
// ---------------------------------------------------------------------------
637
638
/** \brief Return a reference to a publication giving the formula(s) or
639
 * procedure
640
 * used by the coordinate operation method.
641
 *
642
 * @return the formula citation, or empty.
643
 */
644
const util::optional<metadata::Citation> &
645
0
OperationMethod::formulaCitation() PROJ_PURE_DEFN {
646
0
    return d->formulaCitation_;
647
0
}
648
649
// ---------------------------------------------------------------------------
650
651
/** \brief Return the parameters of this operation method.
652
 *
653
 * @return the parameters.
654
 */
655
const std::vector<GeneralOperationParameterNNPtr> &
656
0
OperationMethod::parameters() PROJ_PURE_DEFN {
657
0
    return d->parameters_;
658
0
}
659
660
// ---------------------------------------------------------------------------
661
662
/** \brief Instantiate a operation method from a vector of
663
 * GeneralOperationParameter.
664
 *
665
 * @param properties See \ref general_properties. At minimum the name should be
666
 * defined.
667
 * @param parameters Vector of GeneralOperationParameterNNPtr.
668
 * @return a new OperationMethod.
669
 */
670
OperationMethodNNPtr OperationMethod::create(
671
    const util::PropertyMap &properties,
672
0
    const std::vector<GeneralOperationParameterNNPtr> &parameters) {
673
0
    OperationMethodNNPtr method(
674
0
        OperationMethod::nn_make_shared<OperationMethod>());
675
0
    method->assignSelf(method);
676
0
    method->setProperties(properties);
677
0
    method->d->parameters_ = parameters;
678
0
    properties.getStringValue("proj_method", method->d->projMethodOverride_);
679
0
    return method;
680
0
}
681
682
// ---------------------------------------------------------------------------
683
684
/** \brief Instantiate a operation method from a vector of OperationParameter.
685
 *
686
 * @param properties See \ref general_properties. At minimum the name should be
687
 * defined.
688
 * @param parameters Vector of OperationParameterNNPtr.
689
 * @return a new OperationMethod.
690
 */
691
OperationMethodNNPtr OperationMethod::create(
692
    const util::PropertyMap &properties,
693
0
    const std::vector<OperationParameterNNPtr> &parameters) {
694
0
    std::vector<GeneralOperationParameterNNPtr> parametersGeneral;
695
0
    parametersGeneral.reserve(parameters.size());
696
0
    for (const auto &p : parameters) {
697
0
        parametersGeneral.push_back(p);
698
0
    }
699
0
    return create(properties, parametersGeneral);
700
0
}
701
702
// ---------------------------------------------------------------------------
703
704
/** \brief Return the EPSG code, either directly, or through the name
705
 * @return code, or 0 if not found
706
 */
707
0
int OperationMethod::getEPSGCode() PROJ_PURE_DEFN {
708
0
    int epsg_code = IdentifiedObject::getEPSGCode();
709
0
    if (epsg_code == 0) {
710
0
        auto l_name = nameStr();
711
0
        if (ends_with(l_name, " (3D)")) {
712
0
            l_name.resize(l_name.size() - strlen(" (3D)"));
713
0
        }
714
0
        size_t nMethodNameCodes = 0;
715
0
        const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes);
716
0
        for (size_t i = 0; i < nMethodNameCodes; ++i) {
717
0
            const auto &tuple = methodNameCodes[i];
718
0
            if (metadata::Identifier::isEquivalentName(l_name.c_str(),
719
0
                                                       tuple.name)) {
720
0
                return tuple.epsg_code;
721
0
            }
722
0
        }
723
0
    }
724
0
    return epsg_code;
725
0
}
726
727
// ---------------------------------------------------------------------------
728
729
//! @cond Doxygen_Suppress
730
0
void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const {
731
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
732
0
    formatter->startNode(isWKT2 ? io::WKTConstants::METHOD
733
0
                                : io::WKTConstants::PROJECTION,
734
0
                         !identifiers().empty());
735
0
    std::string l_name(nameStr());
736
0
    if (!isWKT2) {
737
0
        const MethodMapping *mapping = getMapping(this);
738
0
        if (mapping == nullptr) {
739
0
            l_name = replaceAll(l_name, " ", "_");
740
0
        } else {
741
0
            if (l_name ==
742
0
                PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) {
743
0
                l_name = "Geostationary_Satellite";
744
0
            } else {
745
0
                if (mapping->wkt1_name == nullptr) {
746
0
                    throw io::FormattingException(
747
0
                        std::string("Unsupported conversion method: ") +
748
0
                        mapping->wkt2_name);
749
0
                }
750
0
                l_name = mapping->wkt1_name;
751
0
            }
752
0
        }
753
0
    }
754
0
    formatter->addQuotedString(l_name);
755
0
    if (formatter->outputId()) {
756
0
        formatID(formatter);
757
0
    }
758
0
    formatter->endNode();
759
0
}
760
//! @endcond
761
762
// ---------------------------------------------------------------------------
763
764
//! @cond Doxygen_Suppress
765
void OperationMethod::_exportToJSON(
766
    io::JSONFormatter *formatter) const // throw(FormattingException)
767
0
{
768
0
    auto writer = formatter->writer();
769
0
    auto objectContext(formatter->MakeObjectContext("OperationMethod",
770
0
                                                    !identifiers().empty()));
771
772
0
    writer->AddObjKey("name");
773
0
    writer->Add(nameStr());
774
775
0
    if (formatter->outputId()) {
776
0
        formatID(formatter);
777
0
    }
778
0
}
779
//! @endcond
780
781
// ---------------------------------------------------------------------------
782
783
//! @cond Doxygen_Suppress
784
bool OperationMethod::_isEquivalentTo(
785
    const util::IComparable *other, util::IComparable::Criterion criterion,
786
0
    const io::DatabaseContextPtr &dbContext) const {
787
0
    auto otherOM = dynamic_cast<const OperationMethod *>(other);
788
0
    if (otherOM == nullptr ||
789
0
        !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
790
0
        return false;
791
0
    }
792
    // TODO test formula and formulaCitation
793
0
    const auto &params = parameters();
794
0
    const auto &otherParams = otherOM->parameters();
795
0
    const auto paramsSize = params.size();
796
0
    if (paramsSize != otherParams.size()) {
797
0
        return false;
798
0
    }
799
0
    if (criterion == util::IComparable::Criterion::STRICT) {
800
0
        for (size_t i = 0; i < paramsSize; i++) {
801
0
            if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion,
802
0
                                            dbContext)) {
803
0
                return false;
804
0
            }
805
0
        }
806
0
    } else {
807
0
        std::vector<bool> candidateIndices(paramsSize, true);
808
0
        for (size_t i = 0; i < paramsSize; i++) {
809
0
            bool found = false;
810
0
            for (size_t j = 0; j < paramsSize; j++) {
811
0
                if (candidateIndices[j] &&
812
0
                    params[i]->_isEquivalentTo(otherParams[j].get(), criterion,
813
0
                                               dbContext)) {
814
0
                    candidateIndices[j] = false;
815
0
                    found = true;
816
0
                    break;
817
0
                }
818
0
            }
819
0
            if (!found) {
820
0
                return false;
821
0
            }
822
0
        }
823
0
    }
824
0
    return true;
825
0
}
826
//! @endcond
827
828
// ---------------------------------------------------------------------------
829
830
//! @cond Doxygen_Suppress
831
struct GeneralParameterValue::Private {};
832
//! @endcond
833
834
// ---------------------------------------------------------------------------
835
836
//! @cond Doxygen_Suppress
837
0
GeneralParameterValue::GeneralParameterValue() : d(nullptr) {}
838
839
// ---------------------------------------------------------------------------
840
841
GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &)
842
0
    : d(nullptr) {}
843
//! @endcond
844
845
// ---------------------------------------------------------------------------
846
847
//! @cond Doxygen_Suppress
848
0
GeneralParameterValue::~GeneralParameterValue() = default;
849
//! @endcond
850
851
// ---------------------------------------------------------------------------
852
853
//! @cond Doxygen_Suppress
854
struct OperationParameterValue::Private {
855
    OperationParameterNNPtr parameter;
856
    ParameterValueNNPtr parameterValue;
857
858
    Private(const OperationParameterNNPtr &parameterIn,
859
            const ParameterValueNNPtr &valueIn)
860
0
        : parameter(parameterIn), parameterValue(valueIn) {}
861
};
862
//! @endcond
863
864
// ---------------------------------------------------------------------------
865
866
OperationParameterValue::OperationParameterValue(
867
    const OperationParameterValue &other)
868
0
    : GeneralParameterValue(other), d(std::make_unique<Private>(*other.d)) {}
869
870
// ---------------------------------------------------------------------------
871
872
OperationParameterValue::OperationParameterValue(
873
    const OperationParameterNNPtr &parameterIn,
874
    const ParameterValueNNPtr &valueIn)
875
0
    : GeneralParameterValue(),
876
0
      d(std::make_unique<Private>(parameterIn, valueIn)) {}
877
878
// ---------------------------------------------------------------------------
879
880
/** \brief Instantiate a OperationParameterValue.
881
 *
882
 * @param parameterIn Parameter (definition).
883
 * @param valueIn Parameter value.
884
 * @return a new OperationParameterValue.
885
 */
886
OperationParameterValueNNPtr
887
OperationParameterValue::create(const OperationParameterNNPtr &parameterIn,
888
0
                                const ParameterValueNNPtr &valueIn) {
889
0
    return OperationParameterValue::nn_make_shared<OperationParameterValue>(
890
0
        parameterIn, valueIn);
891
0
}
892
893
// ---------------------------------------------------------------------------
894
895
//! @cond Doxygen_Suppress
896
0
OperationParameterValue::~OperationParameterValue() = default;
897
//! @endcond
898
899
// ---------------------------------------------------------------------------
900
901
/** \brief Return the parameter (definition)
902
 *
903
 * @return the parameter (definition).
904
 */
905
const OperationParameterNNPtr &
906
0
OperationParameterValue::parameter() PROJ_PURE_DEFN {
907
0
    return d->parameter;
908
0
}
909
910
// ---------------------------------------------------------------------------
911
912
/** \brief Return the parameter value.
913
 *
914
 * @return the parameter value.
915
 */
916
const ParameterValueNNPtr &
917
0
OperationParameterValue::parameterValue() PROJ_PURE_DEFN {
918
0
    return d->parameterValue;
919
0
}
920
921
// ---------------------------------------------------------------------------
922
923
//! @cond Doxygen_Suppress
924
void OperationParameterValue::_exportToWKT(
925
    // cppcheck-suppress passedByValue
926
0
    io::WKTFormatter *formatter) const {
927
0
    _exportToWKT(formatter, nullptr);
928
0
}
929
930
void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter,
931
0
                                           const MethodMapping *mapping) const {
932
0
    const ParamMapping *paramMapping =
933
0
        mapping ? getMapping(mapping, d->parameter) : nullptr;
934
0
    if (paramMapping && paramMapping->wkt1_name == nullptr) {
935
0
        return;
936
0
    }
937
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
938
0
    if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) {
939
0
        formatter->startNode(io::WKTConstants::PARAMETERFILE,
940
0
                             !parameter()->identifiers().empty());
941
0
    } else {
942
0
        formatter->startNode(io::WKTConstants::PARAMETER,
943
0
                             !parameter()->identifiers().empty());
944
0
    }
945
0
    if (paramMapping) {
946
0
        formatter->addQuotedString(paramMapping->wkt1_name);
947
0
    } else {
948
0
        formatter->addQuotedString(parameter()->nameStr());
949
0
    }
950
0
    parameterValue()->_exportToWKT(formatter);
951
0
    if (formatter->outputId()) {
952
0
        parameter()->formatID(formatter);
953
0
    }
954
0
    formatter->endNode();
955
0
}
956
//! @endcond
957
958
// ---------------------------------------------------------------------------
959
960
//! @cond Doxygen_Suppress
961
void OperationParameterValue::_exportToJSON(
962
0
    io::JSONFormatter *formatter) const {
963
0
    auto writer = formatter->writer();
964
0
    auto objectContext(formatter->MakeObjectContext(
965
0
        "ParameterValue", !parameter()->identifiers().empty()));
966
967
0
    writer->AddObjKey("name");
968
0
    writer->Add(parameter()->nameStr());
969
970
0
    const auto &l_value(parameterValue());
971
0
    const auto value_type = l_value->type();
972
0
    if (value_type == ParameterValue::Type::MEASURE) {
973
0
        writer->AddObjKey("value");
974
0
        writer->Add(l_value->value().value(), 15);
975
0
        writer->AddObjKey("unit");
976
0
        const auto &l_unit(l_value->value().unit());
977
0
        if (l_unit == common::UnitOfMeasure::METRE ||
978
0
            l_unit == common::UnitOfMeasure::DEGREE ||
979
0
            l_unit == common::UnitOfMeasure::SCALE_UNITY) {
980
0
            writer->Add(l_unit.name());
981
0
        } else {
982
0
            l_unit._exportToJSON(formatter);
983
0
        }
984
0
    } else if (value_type == ParameterValue::Type::FILENAME) {
985
0
        writer->AddObjKey("value");
986
0
        writer->Add(l_value->valueFile());
987
0
    } else if (value_type == ParameterValue::Type::INTEGER) {
988
0
        writer->AddObjKey("value");
989
0
        writer->Add(l_value->integerValue());
990
0
    }
991
992
0
    if (formatter->outputId()) {
993
0
        parameter()->formatID(formatter);
994
0
    }
995
0
}
996
//! @endcond
997
998
// ---------------------------------------------------------------------------
999
1000
//! @cond Doxygen_Suppress
1001
1002
/** Utility method used on WKT2 import to convert from abridged transformation
1003
 * to "normal" transformation parameters.
1004
 */
1005
bool OperationParameterValue::convertFromAbridged(
1006
    const std::string &paramName, double &val,
1007
0
    const common::UnitOfMeasure *&unit, int &paramEPSGCode) {
1008
0
    if (metadata::Identifier::isEquivalentName(
1009
0
            paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) ||
1010
0
        paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
1011
0
        unit = &common::UnitOfMeasure::METRE;
1012
0
        paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION;
1013
0
        return true;
1014
0
    } else if (metadata::Identifier::isEquivalentName(
1015
0
                   paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) ||
1016
0
               paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
1017
0
        unit = &common::UnitOfMeasure::METRE;
1018
0
        paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION;
1019
0
        return true;
1020
0
    } else if (metadata::Identifier::isEquivalentName(
1021
0
                   paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) ||
1022
0
               paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
1023
0
        unit = &common::UnitOfMeasure::METRE;
1024
0
        paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION;
1025
0
        return true;
1026
0
    } else if (metadata::Identifier::isEquivalentName(
1027
0
                   paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) ||
1028
0
               paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
1029
0
        unit = &common::UnitOfMeasure::ARC_SECOND;
1030
0
        paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION;
1031
0
        return true;
1032
0
    } else if (metadata::Identifier::isEquivalentName(
1033
0
                   paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) ||
1034
0
               paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
1035
0
        unit = &common::UnitOfMeasure::ARC_SECOND;
1036
0
        paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION;
1037
0
        return true;
1038
1039
0
    } else if (metadata::Identifier::isEquivalentName(
1040
0
                   paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) ||
1041
0
               paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
1042
0
        unit = &common::UnitOfMeasure::ARC_SECOND;
1043
0
        paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION;
1044
0
        return true;
1045
1046
0
    } else if (metadata::Identifier::isEquivalentName(
1047
0
                   paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) ||
1048
0
               paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
1049
0
        val = (val - 1.0) * 1e6;
1050
0
        unit = &common::UnitOfMeasure::PARTS_PER_MILLION;
1051
0
        paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE;
1052
0
        return true;
1053
0
    }
1054
0
    return false;
1055
0
}
1056
//! @endcond
1057
1058
// ---------------------------------------------------------------------------
1059
1060
//! @cond Doxygen_Suppress
1061
bool OperationParameterValue::_isEquivalentTo(
1062
    const util::IComparable *other, util::IComparable::Criterion criterion,
1063
0
    const io::DatabaseContextPtr &dbContext) const {
1064
0
    auto otherOPV = dynamic_cast<const OperationParameterValue *>(other);
1065
0
    if (otherOPV == nullptr) {
1066
0
        return false;
1067
0
    }
1068
0
    if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion,
1069
0
                                       dbContext)) {
1070
0
        return false;
1071
0
    }
1072
0
    if (criterion == util::IComparable::Criterion::STRICT) {
1073
0
        return d->parameterValue->_isEquivalentTo(
1074
0
            otherOPV->d->parameterValue.get(), criterion);
1075
0
    }
1076
0
    if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(),
1077
0
                                           criterion, dbContext)) {
1078
0
        return true;
1079
0
    }
1080
0
    if (d->parameter->getEPSGCode() ==
1081
0
            EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE ||
1082
0
        d->parameter->getEPSGCode() ==
1083
0
            EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) {
1084
0
        if (parameterValue()->type() == ParameterValue::Type::MEASURE &&
1085
0
            otherOPV->parameterValue()->type() ==
1086
0
                ParameterValue::Type::MEASURE) {
1087
0
            const double a = std::fmod(parameterValue()->value().convertToUnit(
1088
0
                                           common::UnitOfMeasure::DEGREE) +
1089
0
                                           360.0,
1090
0
                                       360.0);
1091
0
            const double b =
1092
0
                std::fmod(otherOPV->parameterValue()->value().convertToUnit(
1093
0
                              common::UnitOfMeasure::DEGREE) +
1094
0
                              360.0,
1095
0
                          360.0);
1096
0
            return std::fabs(a - b) <= 1e-10 * std::fabs(a);
1097
0
        }
1098
0
    }
1099
0
    return false;
1100
0
}
1101
//! @endcond
1102
1103
// ---------------------------------------------------------------------------
1104
1105
//! @cond Doxygen_Suppress
1106
struct GeneralOperationParameter::Private {};
1107
//! @endcond
1108
1109
// ---------------------------------------------------------------------------
1110
1111
0
GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {}
1112
1113
// ---------------------------------------------------------------------------
1114
1115
GeneralOperationParameter::GeneralOperationParameter(
1116
    const GeneralOperationParameter &other)
1117
0
    : IdentifiedObject(other), d(nullptr) {}
1118
1119
// ---------------------------------------------------------------------------
1120
1121
//! @cond Doxygen_Suppress
1122
0
GeneralOperationParameter::~GeneralOperationParameter() = default;
1123
//! @endcond
1124
1125
// ---------------------------------------------------------------------------
1126
1127
//! @cond Doxygen_Suppress
1128
struct OperationParameter::Private {};
1129
//! @endcond
1130
1131
// ---------------------------------------------------------------------------
1132
1133
0
OperationParameter::OperationParameter() : d(nullptr) {}
1134
1135
// ---------------------------------------------------------------------------
1136
1137
OperationParameter::OperationParameter(const OperationParameter &other)
1138
0
    : GeneralOperationParameter(other), d(nullptr) {}
1139
1140
// ---------------------------------------------------------------------------
1141
1142
//! @cond Doxygen_Suppress
1143
0
OperationParameter::~OperationParameter() = default;
1144
//! @endcond
1145
1146
// ---------------------------------------------------------------------------
1147
1148
/** \brief Instantiate a OperationParameter.
1149
 *
1150
 * @param properties See \ref general_properties. At minimum the name should be
1151
 * defined.
1152
 * @return a new OperationParameter.
1153
 */
1154
OperationParameterNNPtr
1155
0
OperationParameter::create(const util::PropertyMap &properties) {
1156
0
    OperationParameterNNPtr op(
1157
0
        OperationParameter::nn_make_shared<OperationParameter>());
1158
0
    op->assignSelf(op);
1159
0
    op->setProperties(properties);
1160
0
    return op;
1161
0
}
1162
1163
// ---------------------------------------------------------------------------
1164
1165
//! @cond Doxygen_Suppress
1166
bool OperationParameter::_isEquivalentTo(
1167
    const util::IComparable *other, util::IComparable::Criterion criterion,
1168
0
    const io::DatabaseContextPtr &dbContext) const {
1169
0
    auto otherOP = dynamic_cast<const OperationParameter *>(other);
1170
0
    if (otherOP == nullptr) {
1171
0
        return false;
1172
0
    }
1173
0
    if (criterion == util::IComparable::Criterion::STRICT) {
1174
0
        return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext);
1175
0
    }
1176
0
    if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) {
1177
0
        return true;
1178
0
    }
1179
0
    auto l_epsgCode = getEPSGCode();
1180
0
    return l_epsgCode != 0 && l_epsgCode == otherOP->getEPSGCode();
1181
0
}
1182
//! @endcond
1183
1184
// ---------------------------------------------------------------------------
1185
1186
0
void OperationParameter::_exportToWKT(io::WKTFormatter *) const {}
1187
1188
// ---------------------------------------------------------------------------
1189
1190
/** \brief Return the name of a parameter designed by its EPSG code
1191
 * @return name, or nullptr if not found
1192
 */
1193
0
const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept {
1194
0
    size_t nParamNameCodes = 0;
1195
0
    const auto paramNameCodes = getParamNameCodes(nParamNameCodes);
1196
0
    for (size_t i = 0; i < nParamNameCodes; ++i) {
1197
0
        const auto &tuple = paramNameCodes[i];
1198
0
        if (tuple.epsg_code == epsg_code) {
1199
0
            return tuple.name;
1200
0
        }
1201
0
    }
1202
0
    return nullptr;
1203
0
}
1204
1205
// ---------------------------------------------------------------------------
1206
1207
/** \brief Return the EPSG code, either directly, or through the name
1208
 * @return code, or 0 if not found
1209
 */
1210
0
int OperationParameter::getEPSGCode() PROJ_PURE_DEFN {
1211
0
    int epsg_code = IdentifiedObject::getEPSGCode();
1212
0
    if (epsg_code == 0) {
1213
0
        const auto &l_name = nameStr();
1214
0
        size_t nParamNameCodes = 0;
1215
0
        const auto paramNameCodes = getParamNameCodes(nParamNameCodes);
1216
0
        for (size_t i = 0; i < nParamNameCodes; ++i) {
1217
0
            const auto &tuple = paramNameCodes[i];
1218
0
            if (metadata::Identifier::isEquivalentName(l_name.c_str(),
1219
0
                                                       tuple.name)) {
1220
0
                return tuple.epsg_code;
1221
0
            }
1222
0
        }
1223
0
        if (metadata::Identifier::isEquivalentName(l_name.c_str(),
1224
0
                                                   "Latitude of origin")) {
1225
0
            return EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN;
1226
0
        }
1227
0
        if (metadata::Identifier::isEquivalentName(l_name.c_str(),
1228
0
                                                   "Scale factor")) {
1229
0
            return EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN;
1230
0
        }
1231
0
    }
1232
0
    return epsg_code;
1233
0
}
1234
1235
// ---------------------------------------------------------------------------
1236
1237
//! @cond Doxygen_Suppress
1238
struct SingleOperation::Private {
1239
    std::vector<GeneralParameterValueNNPtr> parameterValues_{};
1240
    OperationMethodNNPtr method_;
1241
1242
    explicit Private(const OperationMethodNNPtr &methodIn)
1243
0
        : method_(methodIn) {}
1244
};
1245
//! @endcond
1246
1247
// ---------------------------------------------------------------------------
1248
1249
SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn)
1250
0
    : d(std::make_unique<Private>(methodIn)) {
1251
1252
0
    const int methodEPSGCode = d->method_->getEPSGCode();
1253
0
    const auto &methodName = d->method_->nameStr();
1254
0
    setRequiresPerCoordinateInputTime(
1255
0
        isTimeDependent(methodName) ||
1256
0
        methodEPSGCode ==
1257
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
1258
0
        methodEPSGCode ==
1259
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
1260
0
        methodEPSGCode ==
1261
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D ||
1262
0
        methodEPSGCode ==
1263
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
1264
0
        methodEPSGCode ==
1265
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
1266
0
        methodEPSGCode ==
1267
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D);
1268
0
}
1269
1270
// ---------------------------------------------------------------------------
1271
1272
SingleOperation::SingleOperation(const SingleOperation &other)
1273
    :
1274
#if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT)
1275
      CoordinateOperation(other),
1276
#endif
1277
0
      d(std::make_unique<Private>(*other.d)) {
1278
0
}
1279
1280
// ---------------------------------------------------------------------------
1281
1282
//! @cond Doxygen_Suppress
1283
0
SingleOperation::~SingleOperation() = default;
1284
//! @endcond
1285
1286
// ---------------------------------------------------------------------------
1287
1288
/** \brief Return the parameter values.
1289
 *
1290
 * @return the parameter values.
1291
 */
1292
const std::vector<GeneralParameterValueNNPtr> &
1293
0
SingleOperation::parameterValues() PROJ_PURE_DEFN {
1294
0
    return d->parameterValues_;
1295
0
}
1296
1297
// ---------------------------------------------------------------------------
1298
1299
/** \brief Return the operation method associated to the operation.
1300
 *
1301
 * @return the operation method.
1302
 */
1303
0
const OperationMethodNNPtr &SingleOperation::method() PROJ_PURE_DEFN {
1304
0
    return d->method_;
1305
0
}
1306
1307
// ---------------------------------------------------------------------------
1308
1309
void SingleOperation::setParameterValues(
1310
0
    const std::vector<GeneralParameterValueNNPtr> &values) {
1311
0
    d->parameterValues_ = values;
1312
0
}
1313
1314
// ---------------------------------------------------------------------------
1315
1316
//! @cond Doxygen_Suppress
1317
static const ParameterValuePtr nullParameterValue;
1318
//! @endcond
1319
1320
/** \brief Return the parameter value corresponding to a parameter name or
1321
 * EPSG code
1322
 *
1323
 * @param paramName the parameter name (or empty, in which case epsg_code
1324
 *                  should be non zero)
1325
 * @param epsg_code the parameter EPSG code (possibly zero)
1326
 * @return the value, or nullptr if not found.
1327
 */
1328
const ParameterValuePtr &
1329
SingleOperation::parameterValue(const std::string &paramName,
1330
0
                                int epsg_code) const noexcept {
1331
0
    if (epsg_code) {
1332
0
        for (const auto &genOpParamvalue : parameterValues()) {
1333
0
            auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
1334
0
                genOpParamvalue.get());
1335
0
            if (opParamvalue) {
1336
0
                const auto &parameter = opParamvalue->parameter();
1337
0
                if (parameter->getEPSGCode() == epsg_code) {
1338
0
                    return opParamvalue->parameterValue();
1339
0
                }
1340
0
            }
1341
0
        }
1342
0
    }
1343
0
    for (const auto &genOpParamvalue : parameterValues()) {
1344
0
        auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
1345
0
            genOpParamvalue.get());
1346
0
        if (opParamvalue) {
1347
0
            const auto &parameter = opParamvalue->parameter();
1348
0
            if (metadata::Identifier::isEquivalentName(
1349
0
                    paramName.c_str(), parameter->nameStr().c_str())) {
1350
0
                return opParamvalue->parameterValue();
1351
0
            }
1352
0
        }
1353
0
    }
1354
0
    for (const auto &genOpParamvalue : parameterValues()) {
1355
0
        auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
1356
0
            genOpParamvalue.get());
1357
0
        if (opParamvalue) {
1358
0
            const auto &parameter = opParamvalue->parameter();
1359
0
            if (areEquivalentParameters(paramName, parameter->nameStr())) {
1360
0
                return opParamvalue->parameterValue();
1361
0
            }
1362
0
        }
1363
0
    }
1364
0
    return nullParameterValue;
1365
0
}
1366
1367
// ---------------------------------------------------------------------------
1368
1369
/** \brief Return the parameter value corresponding to a EPSG code
1370
 *
1371
 * @param epsg_code the parameter EPSG code
1372
 * @return the value, or nullptr if not found.
1373
 */
1374
const ParameterValuePtr &
1375
0
SingleOperation::parameterValue(int epsg_code) const noexcept {
1376
0
    for (const auto &genOpParamvalue : parameterValues()) {
1377
0
        auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
1378
0
            genOpParamvalue.get());
1379
0
        if (opParamvalue) {
1380
0
            const auto &parameter = opParamvalue->parameter();
1381
0
            if (parameter->getEPSGCode() == epsg_code) {
1382
0
                return opParamvalue->parameterValue();
1383
0
            }
1384
0
        }
1385
0
    }
1386
0
    return nullParameterValue;
1387
0
}
1388
1389
// ---------------------------------------------------------------------------
1390
1391
/** \brief Return the parameter value, as a measure, corresponding to a
1392
 * parameter name or EPSG code
1393
 *
1394
 * @param paramName the parameter name (or empty, in which case epsg_code
1395
 *                  should be non zero)
1396
 * @param epsg_code the parameter EPSG code (possibly zero)
1397
 * @return the measure, or the empty Measure() object if not found.
1398
 */
1399
const common::Measure &
1400
SingleOperation::parameterValueMeasure(const std::string &paramName,
1401
0
                                       int epsg_code) const noexcept {
1402
0
    const auto &val = parameterValue(paramName, epsg_code);
1403
0
    if (val && val->type() == ParameterValue::Type::MEASURE) {
1404
0
        return val->value();
1405
0
    }
1406
0
    return nullMeasure;
1407
0
}
1408
1409
/** \brief Return the parameter value, as a measure, corresponding to a
1410
 * EPSG code
1411
 *
1412
 * @param epsg_code the parameter EPSG code
1413
 * @return the measure, or the empty Measure() object if not found.
1414
 */
1415
const common::Measure &
1416
0
SingleOperation::parameterValueMeasure(int epsg_code) const noexcept {
1417
0
    const auto &val = parameterValue(epsg_code);
1418
0
    if (val && val->type() == ParameterValue::Type::MEASURE) {
1419
0
        return val->value();
1420
0
    }
1421
0
    return nullMeasure;
1422
0
}
1423
1424
//! @cond Doxygen_Suppress
1425
1426
double
1427
0
SingleOperation::parameterValueNumericAsSI(int epsg_code) const noexcept {
1428
0
    const auto &val = parameterValue(epsg_code);
1429
0
    if (val && val->type() == ParameterValue::Type::MEASURE) {
1430
0
        return val->value().getSIValue();
1431
0
    }
1432
0
    return 0.0;
1433
0
}
1434
1435
double SingleOperation::parameterValueNumeric(
1436
0
    int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept {
1437
0
    const auto &val = parameterValue(epsg_code);
1438
0
    if (val && val->type() == ParameterValue::Type::MEASURE) {
1439
0
        return val->value().convertToUnit(targetUnit);
1440
0
    }
1441
0
    return 0.0;
1442
0
}
1443
1444
double SingleOperation::parameterValueNumeric(
1445
    const char *param_name,
1446
0
    const common::UnitOfMeasure &targetUnit) const noexcept {
1447
0
    const auto &val = parameterValue(param_name, 0);
1448
0
    if (val && val->type() == ParameterValue::Type::MEASURE) {
1449
0
        return val->value().convertToUnit(targetUnit);
1450
0
    }
1451
0
    return 0.0;
1452
0
}
1453
1454
//! @endcond
1455
// ---------------------------------------------------------------------------
1456
1457
/** \brief Instantiate a PROJ-based single operation.
1458
 *
1459
 * \note The operation might internally be a pipeline chaining several
1460
 * operations.
1461
 * The use of the SingleOperation modeling here is mostly to be able to get
1462
 * the PROJ string as a parameter.
1463
 *
1464
 * @param properties Properties
1465
 * @param PROJString the PROJ string.
1466
 * @param sourceCRS source CRS (might be null).
1467
 * @param targetCRS target CRS (might be null).
1468
 * @param accuracies Vector of positional accuracy (might be empty).
1469
 * @return the new instance
1470
 */
1471
SingleOperationNNPtr SingleOperation::createPROJBased(
1472
    const util::PropertyMap &properties, const std::string &PROJString,
1473
    const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS,
1474
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1475
0
    return util::nn_static_pointer_cast<SingleOperation>(
1476
0
        PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS,
1477
0
                                   accuracies));
1478
0
}
1479
1480
// ---------------------------------------------------------------------------
1481
1482
//! @cond Doxygen_Suppress
1483
bool SingleOperation::_isEquivalentTo(
1484
    const util::IComparable *other, util::IComparable::Criterion criterion,
1485
0
    const io::DatabaseContextPtr &dbContext) const {
1486
0
    return _isEquivalentTo(other, criterion, dbContext, false);
1487
0
}
1488
1489
bool SingleOperation::_isEquivalentTo(const util::IComparable *other,
1490
                                      util::IComparable::Criterion criterion,
1491
                                      const io::DatabaseContextPtr &dbContext,
1492
0
                                      bool inOtherDirection) const {
1493
1494
0
    auto otherSO = dynamic_cast<const SingleOperation *>(other);
1495
0
    if (otherSO == nullptr ||
1496
0
        (criterion == util::IComparable::Criterion::STRICT &&
1497
0
         !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) {
1498
0
        return false;
1499
0
    }
1500
1501
0
    const int methodEPSGCode = d->method_->getEPSGCode();
1502
0
    const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode();
1503
1504
0
    bool equivalentMethods =
1505
0
        (criterion == util::IComparable::Criterion::EQUIVALENT &&
1506
0
         methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) ||
1507
0
        d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion,
1508
0
                                    dbContext);
1509
0
    if (!equivalentMethods &&
1510
0
        criterion == util::IComparable::Criterion::EQUIVALENT) {
1511
0
        if ((methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA &&
1512
0
             otherMethodEPSGCode ==
1513
0
                 EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) ||
1514
0
            (otherMethodEPSGCode ==
1515
0
                 EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA &&
1516
0
             methodEPSGCode ==
1517
0
                 EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) ||
1518
0
            (methodEPSGCode ==
1519
0
                 EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA &&
1520
0
             otherMethodEPSGCode ==
1521
0
                 EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) ||
1522
0
            (otherMethodEPSGCode ==
1523
0
                 EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA &&
1524
0
             methodEPSGCode ==
1525
0
                 EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) ||
1526
0
            (methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
1527
0
             otherMethodEPSGCode ==
1528
0
                 EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) ||
1529
0
            (otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL &&
1530
0
             methodEPSGCode ==
1531
0
                 EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) {
1532
0
            auto geodCRS =
1533
0
                dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
1534
0
            auto otherGeodCRS = dynamic_cast<const crs::GeodeticCRS *>(
1535
0
                otherSO->sourceCRS().get());
1536
0
            if (geodCRS && otherGeodCRS && geodCRS->ellipsoid()->isSphere() &&
1537
0
                otherGeodCRS->ellipsoid()->isSphere()) {
1538
0
                equivalentMethods = true;
1539
0
            }
1540
0
        }
1541
0
    }
1542
1543
0
    if (!equivalentMethods) {
1544
0
        if (criterion == util::IComparable::Criterion::EQUIVALENT) {
1545
1546
0
            const auto isTOWGS84Transf = [](int code) {
1547
0
                return code ==
1548
0
                           EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
1549
0
                       code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
1550
0
                       code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
1551
0
                       code ==
1552
0
                           EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
1553
0
                       code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
1554
0
                       code ==
1555
0
                           EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
1556
0
                       code ==
1557
0
                           EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D ||
1558
0
                       code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D ||
1559
0
                       code ==
1560
0
                           EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D ||
1561
0
                       code ==
1562
0
                           EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND;
1563
0
            };
1564
1565
            // Translation vs (PV or CF)
1566
            // or different PV vs CF convention
1567
0
            if (isTOWGS84Transf(methodEPSGCode) &&
1568
0
                isTOWGS84Transf(otherMethodEPSGCode)) {
1569
0
                auto transf = static_cast<const Transformation *>(this);
1570
0
                auto otherTransf = static_cast<const Transformation *>(otherSO);
1571
0
                auto params = transf->getTOWGS84Parameters(true);
1572
0
                auto otherParams = otherTransf->getTOWGS84Parameters(true);
1573
0
                assert(params.size() == 7);
1574
0
                assert(otherParams.size() == 7);
1575
0
                for (size_t i = 0; i < 7; i++) {
1576
0
                    if (std::fabs(params[i] - otherParams[i]) >
1577
0
                        1e-10 * std::fabs(params[i])) {
1578
0
                        return false;
1579
0
                    }
1580
0
                }
1581
0
                return true;
1582
0
            }
1583
1584
            // _1SP methods can sometimes be equivalent to _2SP ones
1585
            // Check it by using convertToOtherMethod()
1586
0
            if (methodEPSGCode ==
1587
0
                    EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP &&
1588
0
                otherMethodEPSGCode ==
1589
0
                    EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
1590
                // Convert from 2SP to 1SP as the other direction has more
1591
                // degree of liberties.
1592
0
                return otherSO->_isEquivalentTo(this, criterion, dbContext);
1593
0
            } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A &&
1594
0
                        otherMethodEPSGCode ==
1595
0
                            EPSG_CODE_METHOD_MERCATOR_VARIANT_B) ||
1596
0
                       (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B &&
1597
0
                        otherMethodEPSGCode ==
1598
0
                            EPSG_CODE_METHOD_MERCATOR_VARIANT_A) ||
1599
0
                       (methodEPSGCode ==
1600
0
                            EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP &&
1601
0
                        otherMethodEPSGCode ==
1602
0
                            EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) {
1603
0
                auto conv = dynamic_cast<const Conversion *>(this);
1604
0
                if (conv) {
1605
0
                    auto eqConv =
1606
0
                        conv->convertToOtherMethod(otherMethodEPSGCode);
1607
0
                    if (eqConv) {
1608
0
                        return eqConv->_isEquivalentTo(other, criterion,
1609
0
                                                       dbContext);
1610
0
                    }
1611
0
                }
1612
0
            }
1613
0
        }
1614
1615
0
        return false;
1616
0
    }
1617
1618
0
    const auto &values = d->parameterValues_;
1619
0
    const auto &otherValues = otherSO->d->parameterValues_;
1620
0
    const auto valuesSize = values.size();
1621
0
    const auto otherValuesSize = otherValues.size();
1622
0
    if (criterion == util::IComparable::Criterion::STRICT) {
1623
0
        if (valuesSize != otherValuesSize) {
1624
0
            return false;
1625
0
        }
1626
0
        for (size_t i = 0; i < valuesSize; i++) {
1627
0
            if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion,
1628
0
                                            dbContext)) {
1629
0
                return false;
1630
0
            }
1631
0
        }
1632
0
        return true;
1633
0
    }
1634
1635
0
    std::vector<bool> candidateIndices(otherValuesSize, true);
1636
0
    bool equivalent = true;
1637
0
    bool foundMissingArgs = valuesSize != otherValuesSize;
1638
1639
0
    for (size_t i = 0; equivalent && i < valuesSize; i++) {
1640
0
        auto opParamvalue =
1641
0
            dynamic_cast<const OperationParameterValue *>(values[i].get());
1642
0
        if (!opParamvalue)
1643
0
            return false;
1644
1645
0
        equivalent = false;
1646
0
        bool sameNameDifferentValue = false;
1647
0
        for (size_t j = 0; j < otherValuesSize; j++) {
1648
0
            if (candidateIndices[j] &&
1649
0
                values[i]->_isEquivalentTo(otherValues[j].get(), criterion,
1650
0
                                           dbContext)) {
1651
0
                candidateIndices[j] = false;
1652
0
                equivalent = true;
1653
0
                break;
1654
0
            } else if (candidateIndices[j]) {
1655
0
                auto otherOpParamvalue =
1656
0
                    dynamic_cast<const OperationParameterValue *>(
1657
0
                        otherValues[j].get());
1658
0
                if (!otherOpParamvalue)
1659
0
                    return false;
1660
0
                sameNameDifferentValue =
1661
0
                    opParamvalue->parameter()->_isEquivalentTo(
1662
0
                        otherOpParamvalue->parameter().get(), criterion,
1663
0
                        dbContext);
1664
0
                if (sameNameDifferentValue) {
1665
0
                    candidateIndices[j] = false;
1666
0
                    break;
1667
0
                }
1668
0
            }
1669
0
        }
1670
1671
0
        if (!equivalent &&
1672
0
            methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
1673
            // For LCC_2SP, the standard parallels can be switched and
1674
            // this will result in the same result.
1675
0
            const int paramEPSGCode = opParamvalue->parameter()->getEPSGCode();
1676
0
            if (paramEPSGCode ==
1677
0
                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL ||
1678
0
                paramEPSGCode ==
1679
0
                    EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) {
1680
0
                auto value_1st = parameterValue(
1681
0
                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL);
1682
0
                auto value_2nd = parameterValue(
1683
0
                    EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL);
1684
0
                if (value_1st && value_2nd) {
1685
0
                    equivalent =
1686
0
                        value_1st->_isEquivalentTo(
1687
0
                            otherSO
1688
0
                                ->parameterValue(
1689
0
                                    EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL)
1690
0
                                .get(),
1691
0
                            criterion, dbContext) &&
1692
0
                        value_2nd->_isEquivalentTo(
1693
0
                            otherSO
1694
0
                                ->parameterValue(
1695
0
                                    EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL)
1696
0
                                .get(),
1697
0
                            criterion, dbContext);
1698
0
                }
1699
0
            }
1700
0
        }
1701
1702
0
        if (equivalent) {
1703
0
            continue;
1704
0
        }
1705
1706
0
        if (sameNameDifferentValue) {
1707
0
            break;
1708
0
        }
1709
1710
        // If there are parameters in this method not found in the other one,
1711
        // check that they are set to a default neutral value, that is 1
1712
        // for scale, and 0 otherwise.
1713
0
        foundMissingArgs = true;
1714
0
        const auto &value = opParamvalue->parameterValue();
1715
0
        if (value->type() != ParameterValue::Type::MEASURE) {
1716
0
            break;
1717
0
        }
1718
0
        if (value->value().unit().type() ==
1719
0
            common::UnitOfMeasure::Type::SCALE) {
1720
0
            equivalent = value->value().getSIValue() == 1.0;
1721
0
        } else {
1722
0
            equivalent = value->value().getSIValue() == 0.0;
1723
0
        }
1724
0
    }
1725
1726
    // In the case the arguments don't perfectly match, try the reverse
1727
    // check.
1728
0
    if (equivalent && foundMissingArgs && !inOtherDirection) {
1729
0
        return otherSO->_isEquivalentTo(this, criterion, dbContext, true);
1730
0
    }
1731
1732
    // Equivalent formulations of 2SP can have different parameters
1733
    // Then convert to 1SP and compare.
1734
0
    if (!equivalent &&
1735
0
        methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) {
1736
0
        auto conv = dynamic_cast<const Conversion *>(this);
1737
0
        auto otherConv = dynamic_cast<const Conversion *>(other);
1738
0
        if (conv && otherConv) {
1739
0
            auto thisAs1SP = conv->convertToOtherMethod(
1740
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
1741
0
            auto otherAs1SP = otherConv->convertToOtherMethod(
1742
0
                EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
1743
0
            if (thisAs1SP && otherAs1SP) {
1744
0
                equivalent = thisAs1SP->_isEquivalentTo(otherAs1SP.get(),
1745
0
                                                        criterion, dbContext);
1746
0
            }
1747
0
        }
1748
0
    }
1749
0
    return equivalent;
1750
0
}
1751
//! @endcond
1752
1753
// ---------------------------------------------------------------------------
1754
1755
std::set<GridDescription>
1756
SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext,
1757
0
                             bool considerKnownGridsAsAvailable) const {
1758
0
    std::set<GridDescription> res;
1759
0
    for (const auto &genOpParamvalue : parameterValues()) {
1760
0
        auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
1761
0
            genOpParamvalue.get());
1762
0
        if (opParamvalue) {
1763
0
            const auto &value = opParamvalue->parameterValue();
1764
0
            if (value->type() == ParameterValue::Type::FILENAME) {
1765
0
                const auto gridNames = split(value->valueFile(), ",");
1766
0
                for (const auto &gridName : gridNames) {
1767
0
                    GridDescription desc;
1768
0
                    desc.shortName = gridName;
1769
0
                    if (databaseContext) {
1770
0
                        databaseContext->lookForGridInfo(
1771
0
                            desc.shortName, considerKnownGridsAsAvailable,
1772
0
                            desc.fullName, desc.packageName, desc.url,
1773
0
                            desc.directDownload, desc.openLicense,
1774
0
                            desc.available);
1775
0
                    }
1776
0
                    res.insert(std::move(desc));
1777
0
                }
1778
0
            }
1779
0
        }
1780
0
    }
1781
0
    return res;
1782
0
}
1783
1784
// ---------------------------------------------------------------------------
1785
1786
/** \brief Validate the parameters used by a coordinate operation.
1787
 *
1788
 * Return whether the method is known or not, or a list of missing or extra
1789
 * parameters for the operations recognized by this implementation.
1790
 */
1791
0
std::list<std::string> SingleOperation::validateParameters() const {
1792
0
    std::list<std::string> res;
1793
1794
0
    const auto &l_method = method();
1795
0
    const auto &methodName = l_method->nameStr();
1796
0
    const auto methodEPSGCode = l_method->getEPSGCode();
1797
1798
0
    const auto findMapping = [methodEPSGCode, &methodName](
1799
0
                                 const MethodMapping *mappings,
1800
0
                                 size_t mappingCount) -> const MethodMapping * {
1801
0
        if (methodEPSGCode != 0) {
1802
0
            for (size_t i = 0; i < mappingCount; ++i) {
1803
0
                const auto &mapping = mappings[i];
1804
0
                if (methodEPSGCode == mapping.epsg_code) {
1805
0
                    return &mapping;
1806
0
                }
1807
0
            }
1808
0
        }
1809
0
        for (size_t i = 0; i < mappingCount; ++i) {
1810
0
            const auto &mapping = mappings[i];
1811
0
            if (metadata::Identifier::isEquivalentName(mapping.wkt2_name,
1812
0
                                                       methodName.c_str())) {
1813
0
                return &mapping;
1814
0
            }
1815
0
        }
1816
0
        return nullptr;
1817
0
    };
1818
1819
0
    size_t nProjectionMethodMappings = 0;
1820
0
    const auto projectionMethodMappings =
1821
0
        getProjectionMethodMappings(nProjectionMethodMappings);
1822
0
    const MethodMapping *methodMapping =
1823
0
        findMapping(projectionMethodMappings, nProjectionMethodMappings);
1824
0
    if (methodMapping == nullptr) {
1825
0
        size_t nOtherMethodMappings = 0;
1826
0
        const auto otherMethodMappings =
1827
0
            getOtherMethodMappings(nOtherMethodMappings);
1828
0
        methodMapping = findMapping(otherMethodMappings, nOtherMethodMappings);
1829
0
    }
1830
0
    if (!methodMapping) {
1831
0
        res.emplace_back("Unknown method " + methodName);
1832
0
        return res;
1833
0
    }
1834
0
    if (methodMapping->wkt2_name != methodName) {
1835
0
        if (metadata::Identifier::isEquivalentName(methodMapping->wkt2_name,
1836
0
                                                   methodName.c_str())) {
1837
0
            std::string msg("Method name ");
1838
0
            msg += methodName;
1839
0
            msg += " is equivalent to official ";
1840
0
            msg += methodMapping->wkt2_name;
1841
0
            msg += " but not strictly equal";
1842
0
            res.emplace_back(msg);
1843
0
        } else {
1844
0
            std::string msg("Method name ");
1845
0
            msg += methodName;
1846
0
            msg += ", matched to ";
1847
0
            msg += methodMapping->wkt2_name;
1848
0
            msg += ", through its EPSG code has not an equivalent name";
1849
0
            res.emplace_back(msg);
1850
0
        }
1851
0
    }
1852
0
    if (methodEPSGCode != 0 && methodEPSGCode != methodMapping->epsg_code) {
1853
0
        std::string msg("Method of EPSG code ");
1854
0
        msg += toString(methodEPSGCode);
1855
0
        msg += " does not match official code (";
1856
0
        msg += toString(methodMapping->epsg_code);
1857
0
        msg += ')';
1858
0
        res.emplace_back(msg);
1859
0
    }
1860
1861
    // Check if expected parameters are found
1862
0
    for (int i = 0;
1863
0
         methodMapping->params && methodMapping->params[i] != nullptr; ++i) {
1864
0
        const auto *paramMapping = methodMapping->params[i];
1865
1866
0
        const OperationParameterValue *opv = nullptr;
1867
0
        for (const auto &genOpParamvalue : parameterValues()) {
1868
0
            auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
1869
0
                genOpParamvalue.get());
1870
0
            if (opParamvalue) {
1871
0
                const auto &parameter = opParamvalue->parameter();
1872
0
                if ((paramMapping->epsg_code != 0 &&
1873
0
                     parameter->getEPSGCode() == paramMapping->epsg_code) ||
1874
0
                    ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) {
1875
0
                    opv = opParamvalue;
1876
0
                    break;
1877
0
                }
1878
0
            }
1879
0
        }
1880
1881
0
        if (!opv) {
1882
0
            if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL ||
1883
0
                 methodEPSGCode ==
1884
0
                     EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) &&
1885
0
                paramMapping == &paramLatitudeNatOrigin) {
1886
                // extension of EPSG used by GDAL/PROJ, so we should not
1887
                // warn on its absence.
1888
0
                continue;
1889
0
            }
1890
0
            std::string msg("Cannot find expected parameter ");
1891
0
            msg += paramMapping->wkt2_name;
1892
0
            res.emplace_back(msg);
1893
0
            continue;
1894
0
        }
1895
0
        const auto &parameter = opv->parameter();
1896
0
        if (paramMapping->wkt2_name != parameter->nameStr()) {
1897
0
            if (ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) {
1898
0
                std::string msg("Parameter name ");
1899
0
                msg += parameter->nameStr();
1900
0
                msg += " is equivalent to official ";
1901
0
                msg += paramMapping->wkt2_name;
1902
0
                msg += " but not strictly equal";
1903
0
                res.emplace_back(msg);
1904
0
            } else {
1905
0
                std::string msg("Parameter name ");
1906
0
                msg += parameter->nameStr();
1907
0
                msg += ", matched to ";
1908
0
                msg += paramMapping->wkt2_name;
1909
0
                msg += ", through its EPSG code has not an equivalent name";
1910
0
                res.emplace_back(msg);
1911
0
            }
1912
0
        }
1913
0
        const auto paramEPSGCode = parameter->getEPSGCode();
1914
0
        if (paramEPSGCode != 0 && paramEPSGCode != paramMapping->epsg_code) {
1915
0
            std::string msg("Parameter of EPSG code ");
1916
0
            msg += toString(paramEPSGCode);
1917
0
            msg += " does not match official code (";
1918
0
            msg += toString(paramMapping->epsg_code);
1919
0
            msg += ')';
1920
0
            res.emplace_back(msg);
1921
0
        }
1922
0
    }
1923
1924
    // Check if there are extra parameters
1925
0
    for (const auto &genOpParamvalue : parameterValues()) {
1926
0
        auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
1927
0
            genOpParamvalue.get());
1928
0
        if (opParamvalue) {
1929
0
            const auto &parameter = opParamvalue->parameter();
1930
0
            if (!getMapping(methodMapping, parameter)) {
1931
0
                std::string msg("Parameter ");
1932
0
                msg += parameter->nameStr();
1933
0
                msg += " found but not expected for this method";
1934
0
                res.emplace_back(msg);
1935
0
            }
1936
0
        }
1937
0
    }
1938
1939
0
    return res;
1940
0
}
1941
1942
// ---------------------------------------------------------------------------
1943
1944
//! @cond Doxygen_Suppress
1945
0
bool SingleOperation::isLongitudeRotation() const {
1946
0
    return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION;
1947
0
}
1948
1949
//! @endcond
1950
1951
// ---------------------------------------------------------------------------
1952
1953
//! @cond Doxygen_Suppress
1954
static const std::string nullString;
1955
1956
static const std::string &_getNTv1Filename(const SingleOperation *op,
1957
0
                                           bool allowInverse) {
1958
1959
0
    const auto &l_method = op->method();
1960
0
    const auto &methodName = l_method->nameStr();
1961
0
    if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 ||
1962
0
        (allowInverse &&
1963
0
         ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) {
1964
0
        const auto &fileParameter = op->parameterValue(
1965
0
            EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
1966
0
            EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
1967
0
        if (fileParameter &&
1968
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
1969
0
            return fileParameter->valueFile();
1970
0
        }
1971
0
    }
1972
0
    return nullString;
1973
0
}
1974
1975
//
1976
static const std::string &_getNTv2Filename(const SingleOperation *op,
1977
0
                                           bool allowInverse) {
1978
1979
0
    const auto &l_method = op->method();
1980
0
    if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 ||
1981
0
        (allowInverse &&
1982
0
         ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) {
1983
0
        const auto &fileParameter = op->parameterValue(
1984
0
            EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
1985
0
            EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
1986
0
        if (fileParameter &&
1987
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
1988
0
            return fileParameter->valueFile();
1989
0
        }
1990
0
    }
1991
0
    return nullString;
1992
0
}
1993
1994
//! @endcond
1995
1996
// ---------------------------------------------------------------------------
1997
//! @cond Doxygen_Suppress
1998
0
const std::string &Transformation::getPROJ4NadgridsCompatibleFilename() const {
1999
2000
0
    const std::string &filename = _getNTv2Filename(this, false);
2001
0
    if (!filename.empty()) {
2002
0
        return filename;
2003
0
    }
2004
2005
0
    if (method()->getEPSGCode() == EPSG_CODE_METHOD_NADCON) {
2006
0
        const auto &latitudeFileParameter =
2007
0
            parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE,
2008
0
                           EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE);
2009
0
        const auto &longitudeFileParameter =
2010
0
            parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE,
2011
0
                           EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE);
2012
0
        if (latitudeFileParameter &&
2013
0
            latitudeFileParameter->type() == ParameterValue::Type::FILENAME &&
2014
0
            longitudeFileParameter &&
2015
0
            longitudeFileParameter->type() == ParameterValue::Type::FILENAME) {
2016
0
            return latitudeFileParameter->valueFile();
2017
0
        }
2018
0
    }
2019
2020
0
    if (ci_equal(method()->nameStr(),
2021
0
                 PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF)) {
2022
0
        const auto &fileParameter = parameterValue(
2023
0
            EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
2024
0
            EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
2025
0
        if (fileParameter &&
2026
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2027
0
            return fileParameter->valueFile();
2028
0
        }
2029
0
    }
2030
2031
0
    return nullString;
2032
0
}
2033
//! @endcond
2034
2035
// ---------------------------------------------------------------------------
2036
2037
//! @cond Doxygen_Suppress
2038
static const std::string &_getCTABLE2Filename(const SingleOperation *op,
2039
0
                                              bool allowInverse) {
2040
0
    const auto &l_method = op->method();
2041
0
    const auto &methodName = l_method->nameStr();
2042
0
    if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) ||
2043
0
        (allowInverse &&
2044
0
         ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) {
2045
0
        const auto &fileParameter = op->parameterValue(
2046
0
            EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
2047
0
            EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
2048
0
        if (fileParameter &&
2049
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2050
0
            return fileParameter->valueFile();
2051
0
        }
2052
0
    }
2053
0
    return nullString;
2054
0
}
2055
//! @endcond
2056
2057
// ---------------------------------------------------------------------------
2058
2059
//! @cond Doxygen_Suppress
2060
static const std::string &
2061
0
_getHorizontalShiftGTIFFFilename(const SingleOperation *op, bool allowInverse) {
2062
0
    const auto &l_method = op->method();
2063
0
    const auto &methodName = l_method->nameStr();
2064
0
    if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) ||
2065
0
        ci_equal(methodName, PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF) ||
2066
0
        (allowInverse &&
2067
0
         ci_equal(methodName,
2068
0
                  INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF)) ||
2069
0
        (allowInverse &&
2070
0
         ci_equal(methodName,
2071
0
                  INVERSE_OF + PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF))) {
2072
0
        {
2073
0
            const auto &fileParameter = op->parameterValue(
2074
0
                EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
2075
0
                EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
2076
0
            if (fileParameter &&
2077
0
                fileParameter->type() == ParameterValue::Type::FILENAME) {
2078
0
                return fileParameter->valueFile();
2079
0
            }
2080
0
        }
2081
0
        {
2082
0
            const auto &fileParameter = op->parameterValue(
2083
0
                PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE,
2084
0
                0);
2085
0
            if (fileParameter &&
2086
0
                fileParameter->type() == ParameterValue::Type::FILENAME) {
2087
0
                return fileParameter->valueFile();
2088
0
            }
2089
0
        }
2090
0
    }
2091
0
    return nullString;
2092
0
}
2093
//! @endcond
2094
2095
// ---------------------------------------------------------------------------
2096
2097
//! @cond Doxygen_Suppress
2098
static const std::string &
2099
_getGeocentricTranslationFilename(const SingleOperation *op,
2100
0
                                  bool allowInverse) {
2101
2102
0
    const auto &l_method = op->method();
2103
0
    const auto &methodName = l_method->nameStr();
2104
0
    if (l_method->getEPSGCode() ==
2105
0
            EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN ||
2106
0
        (allowInverse &&
2107
0
         ci_equal(
2108
0
             methodName,
2109
0
             INVERSE_OF +
2110
0
                 EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN))) {
2111
0
        const auto &fileParameter =
2112
0
            op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE,
2113
0
                               EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE);
2114
0
        if (fileParameter &&
2115
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2116
0
            return fileParameter->valueFile();
2117
0
        }
2118
0
    }
2119
0
    return nullString;
2120
0
}
2121
//! @endcond
2122
2123
// ---------------------------------------------------------------------------
2124
2125
//! @cond Doxygen_Suppress
2126
static const std::string &
2127
_getGeographic3DOffsetByVelocityGridFilename(const SingleOperation *op,
2128
0
                                             bool allowInverse) {
2129
2130
0
    const auto &l_method = op->method();
2131
0
    const auto &methodName = l_method->nameStr();
2132
0
    if (l_method->getEPSGCode() ==
2133
0
            EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL ||
2134
0
        (allowInverse &&
2135
0
         ci_equal(
2136
0
             methodName,
2137
0
             INVERSE_OF +
2138
0
                 EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL))) {
2139
0
        const auto &fileParameter = op->parameterValue(
2140
0
            EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
2141
0
            EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE);
2142
0
        if (fileParameter &&
2143
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2144
0
            return fileParameter->valueFile();
2145
0
        }
2146
0
    }
2147
0
    return nullString;
2148
0
}
2149
//! @endcond
2150
2151
// ---------------------------------------------------------------------------
2152
2153
//! @cond Doxygen_Suppress
2154
static const std::string &
2155
_getVerticalOffsetByVelocityGridFilename(const SingleOperation *op,
2156
0
                                         bool allowInverse) {
2157
2158
0
    const auto &l_method = op->method();
2159
0
    const auto &methodName = l_method->nameStr();
2160
0
    if (l_method->getEPSGCode() ==
2161
0
            EPSG_CODE_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL ||
2162
0
        (allowInverse &&
2163
0
         ci_equal(
2164
0
             methodName,
2165
0
             INVERSE_OF +
2166
0
                 EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL))) {
2167
0
        const auto &fileParameter = op->parameterValue(
2168
0
            EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
2169
0
            EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE);
2170
0
        if (fileParameter &&
2171
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2172
0
            return fileParameter->valueFile();
2173
0
        }
2174
0
    }
2175
0
    return nullString;
2176
0
}
2177
//! @endcond
2178
2179
// ---------------------------------------------------------------------------
2180
2181
//! @cond Doxygen_Suppress
2182
static const std::string &
2183
0
_getHeightToGeographic3DFilename(const SingleOperation *op, bool allowInverse) {
2184
2185
0
    const auto &methodName = op->method()->nameStr();
2186
2187
0
    if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) ||
2188
0
        (allowInverse &&
2189
0
         ci_equal(methodName,
2190
0
                  INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) {
2191
0
        const auto &fileParameter =
2192
0
            op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
2193
0
                               EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
2194
0
        if (fileParameter &&
2195
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2196
0
            return fileParameter->valueFile();
2197
0
        }
2198
0
    }
2199
0
    return nullString;
2200
0
}
2201
//! @endcond
2202
2203
// ---------------------------------------------------------------------------
2204
2205
//! @cond Doxygen_Suppress
2206
bool Transformation::isGeographic3DToGravityRelatedHeight(
2207
0
    const OperationMethodNNPtr &method, bool allowInverse) {
2208
0
    const auto &methodName = method->nameStr();
2209
0
    static const char *const methodCodes[] = {
2210
0
        "1025", // Geographic3D to GravityRelatedHeight (EGM2008)
2211
0
        "1030", // Geographic3D to GravityRelatedHeight (NZgeoid)
2212
0
        "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire)
2213
0
        "1047", // Geographic3D to GravityRelatedHeight (Gravsoft)
2214
0
        "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2)
2215
0
        "1050", // Geographic3D to GravityRelatedHeight (CI)
2216
0
        "1059", // Geographic3D to GravityRelatedHeight (PNG)
2217
0
        "1088", // Geog3D to Geog2D+GravityRelatedHeight (gtx)
2218
0
        "1060", // Geographic3D to GravityRelatedHeight (CGG2013)
2219
0
        "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire)
2220
0
        "1073", // Geographic3D to GravityRelatedHeight (IGN2009)
2221
0
        "1081", // Geographic3D to GravityRelatedHeight (BEV AT)
2222
0
        "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2)
2223
0
        "1089", // Geog3D to Geog2D+GravityRelatedHeight (BEV AT)
2224
0
        "1090", // Geog3D to Geog2D+GravityRelatedHeight (CGG 2013)
2225
0
        "1091", // Geog3D to Geog2D+GravityRelatedHeight (CI)
2226
0
        "1092", // Geog3D to Geog2D+GravityRelatedHeight (EGM2008)
2227
0
        "1093", // Geog3D to Geog2D+GravityRelatedHeight (Gravsoft)
2228
0
        "1094", // Geog3D to Geog2D+GravityRelatedHeight (IGN1997)
2229
0
        "1095", // Geog3D to Geog2D+GravityRelatedHeight (IGN2009)
2230
0
        "1096", // Geog3D to Geog2D+GravityRelatedHeight (OSGM15-Ire)
2231
0
        "1097", // Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB)
2232
0
        "1098", // Geog3D to Geog2D+GravityRelatedHeight (SA 2010)
2233
0
        "1100", // Geog3D to Geog2D+GravityRelatedHeight (PL txt)
2234
0
        "1103", // Geog3D to Geog2D+GravityRelatedHeight (EGM)
2235
0
        "1105", // Geog3D to Geog2D+GravityRelatedHeight (ITAL2005)
2236
0
        "1109", // Geographic3D to Depth (Gravsoft)
2237
0
        "1110", // Geog3D to Geog2D+Depth (Gravsoft)
2238
0
        "1115", // Geog3D to Geog2D+Depth (txt)
2239
0
        "1118", // Geog3D to Geog2D+GravityRelatedHeight (ISG)
2240
0
        "1122", // Geog3D to Geog2D+Depth (gtx)
2241
0
        "1124", // Geog3D to Geog2D+GravityRelatedHeight (gtg)
2242
0
        "1126", // Vertical change by geoid grid difference (NRCan)
2243
0
        "1127", // Geographic3D to Depth (gtg)
2244
0
        "1128", // Geog3D to Geog2D+Depth (gtg)
2245
0
        "1135", // Geog3D to Geog2D+GravityRelatedHeight (NGS bin)
2246
0
        "9661", // Geographic3D to GravityRelatedHeight (EGM)
2247
0
        "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98)
2248
0
        "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB)
2249
0
        "9664", // Geographic3D to GravityRelatedHeight (IGN1997)
2250
0
        "9665", // Geographic3D to GravityRelatedHeight (US .gtx)
2251
0
        "9635", // Geog3D to Geog2D+GravityRelatedHeight (US .gtx)
2252
0
    };
2253
2254
0
    if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) {
2255
0
        return true;
2256
0
    }
2257
0
    if (allowInverse &&
2258
0
        ci_find(methodName,
2259
0
                INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) {
2260
0
        return true;
2261
0
    }
2262
2263
0
    for (const auto &code : methodCodes) {
2264
0
        for (const auto &idSrc : method->identifiers()) {
2265
0
            const auto &srcAuthName = *(idSrc->codeSpace());
2266
0
            const auto &srcCode = idSrc->code();
2267
0
            if (ci_equal(srcAuthName, "EPSG") && srcCode == code) {
2268
0
                return true;
2269
0
            }
2270
0
            if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") &&
2271
0
                srcCode == code) {
2272
0
                return true;
2273
0
            }
2274
0
        }
2275
0
    }
2276
0
    return false;
2277
0
}
2278
//! @endcond
2279
2280
// ---------------------------------------------------------------------------
2281
2282
//! @cond Doxygen_Suppress
2283
0
const std::string &Transformation::getHeightToGeographic3DFilename() const {
2284
2285
0
    const std::string &ret = _getHeightToGeographic3DFilename(this, false);
2286
0
    if (!ret.empty())
2287
0
        return ret;
2288
0
    if (isGeographic3DToGravityRelatedHeight(method(), false)) {
2289
0
        const auto &fileParameter =
2290
0
            parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
2291
0
                           EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
2292
0
        if (fileParameter &&
2293
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2294
0
            return fileParameter->valueFile();
2295
0
        }
2296
0
    }
2297
0
    return nullString;
2298
0
}
2299
//! @endcond
2300
2301
// ---------------------------------------------------------------------------
2302
2303
//! @cond Doxygen_Suppress
2304
static util::PropertyMap
2305
0
createSimilarPropertiesOperation(const CoordinateOperationNNPtr &obj) {
2306
0
    util::PropertyMap map;
2307
2308
    // The domain(s) are unchanged
2309
0
    addDomains(map, obj.get());
2310
2311
0
    const std::string &forwardName = obj->nameStr();
2312
0
    if (!forwardName.empty()) {
2313
0
        map.set(common::IdentifiedObject::NAME_KEY, forwardName);
2314
0
    }
2315
2316
0
    const std::string &remarks = obj->remarks();
2317
0
    if (!remarks.empty()) {
2318
0
        map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
2319
0
    }
2320
2321
0
    addModifiedIdentifier(map, obj.get(), false, true);
2322
2323
0
    return map;
2324
0
}
2325
//! @endcond
2326
2327
// ---------------------------------------------------------------------------
2328
2329
//! @cond Doxygen_Suppress
2330
static TransformationNNPtr
2331
createNTv1(const util::PropertyMap &properties,
2332
           const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
2333
           const std::string &filename,
2334
0
           const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
2335
0
    const VectorOfParameters parameters{createOpParamNameEPSGCode(
2336
0
        EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
2337
0
    const VectorOfValues values{ParameterValue::createFilename(filename)};
2338
0
    return Transformation::create(
2339
0
        properties, sourceCRSIn, targetCRSIn, nullptr,
2340
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1), parameters, values,
2341
0
        accuracies);
2342
0
}
2343
//! @endcond
2344
2345
// ---------------------------------------------------------------------------
2346
2347
//! @cond Doxygen_Suppress
2348
static util::PropertyMap
2349
0
createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) {
2350
0
    util::PropertyMap map;
2351
2352
0
    const std::string &forwardName = obj->nameStr();
2353
0
    if (!forwardName.empty()) {
2354
0
        map.set(common::IdentifiedObject::NAME_KEY, forwardName);
2355
0
    }
2356
2357
0
    {
2358
0
        auto ar = util::ArrayOfBaseObject::create();
2359
0
        for (const auto &idSrc : obj->identifiers()) {
2360
0
            const auto &srcAuthName = *(idSrc->codeSpace());
2361
0
            const auto &srcCode = idSrc->code();
2362
0
            auto idsProp = util::PropertyMap().set(
2363
0
                metadata::Identifier::CODESPACE_KEY, srcAuthName);
2364
0
            ar->add(metadata::Identifier::create(srcCode, idsProp));
2365
0
        }
2366
0
        if (!ar->empty()) {
2367
0
            map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
2368
0
        }
2369
0
    }
2370
2371
0
    return map;
2372
0
}
2373
//! @endcond
2374
2375
// ---------------------------------------------------------------------------
2376
2377
static bool isRegularVerticalGridMethod(int methodEPSGCode,
2378
0
                                        bool &reverseOffsetSign) {
2379
0
    if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NRCAN_BYN ||
2380
0
        methodEPSGCode ==
2381
0
            EPSG_CODE_METHOD_VERTICALCHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN) {
2382
        // NRCAN vertical shift grids use a reverse convention from other
2383
        // grids: the value in the grid is the value to subtract from the
2384
        // source vertical CRS to get the target value.
2385
0
        reverseOffsetSign = true;
2386
0
        return true;
2387
0
    }
2388
0
    reverseOffsetSign = false;
2389
0
    return methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
2390
0
           methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
2391
0
           methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX ||
2392
0
           methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_ASC ||
2393
0
           methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTG ||
2394
0
           methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_PL_TXT;
2395
0
}
2396
2397
// ---------------------------------------------------------------------------
2398
2399
/** \brief Return an equivalent transformation to the current one, but using
2400
 * PROJ alternative grid names.
2401
 */
2402
TransformationNNPtr SingleOperation::substitutePROJAlternativeGridNames(
2403
0
    io::DatabaseContextNNPtr databaseContext) const {
2404
0
    auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
2405
0
        shared_from_this().as_nullable()));
2406
2407
0
    const auto &l_method = method();
2408
0
    const int methodEPSGCode = l_method->getEPSGCode();
2409
2410
0
    std::string projFilename;
2411
0
    std::string projGridFormat;
2412
0
    bool inverseDirection = false;
2413
2414
0
    const auto &NTv1Filename = _getNTv1Filename(this, false);
2415
0
    const auto &NTv2Filename = _getNTv2Filename(this, false);
2416
0
    std::string lasFilename;
2417
0
    if (methodEPSGCode == EPSG_CODE_METHOD_NADCON ||
2418
0
        methodEPSGCode == EPSG_CODE_METHOD_NADCON5_2D ||
2419
0
        methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D) {
2420
0
        const auto &latitudeFileParameter =
2421
0
            parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE,
2422
0
                           EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE);
2423
0
        const auto &longitudeFileParameter =
2424
0
            parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE,
2425
0
                           EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE);
2426
0
        if (latitudeFileParameter &&
2427
0
            latitudeFileParameter->type() == ParameterValue::Type::FILENAME &&
2428
0
            longitudeFileParameter &&
2429
0
            longitudeFileParameter->type() == ParameterValue::Type::FILENAME) {
2430
0
            lasFilename = latitudeFileParameter->valueFile();
2431
0
        }
2432
0
    }
2433
0
    const auto &horizontalGridName = !NTv1Filename.empty()   ? NTv1Filename
2434
0
                                     : !NTv2Filename.empty() ? NTv2Filename
2435
0
                                                             : lasFilename;
2436
0
    const auto l_interpolationCRS = interpolationCRS();
2437
2438
0
    if (!horizontalGridName.empty() && databaseContext->lookForGridAlternative(
2439
0
                                           horizontalGridName, projFilename,
2440
0
                                           projGridFormat, inverseDirection)) {
2441
2442
0
        if (horizontalGridName == projFilename) {
2443
0
            if (inverseDirection) {
2444
0
                throw util::UnsupportedOperationException(
2445
0
                    "Inverse direction for " + projFilename + " not supported");
2446
0
            }
2447
0
            return self;
2448
0
        }
2449
2450
0
        const auto l_sourceCRSNull = sourceCRS();
2451
0
        const auto l_targetCRSNull = targetCRS();
2452
0
        if (l_sourceCRSNull == nullptr) {
2453
0
            throw util::UnsupportedOperationException("Missing sourceCRS");
2454
0
        }
2455
0
        if (l_targetCRSNull == nullptr) {
2456
0
            throw util::UnsupportedOperationException("Missing targetCRS");
2457
0
        }
2458
0
        auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2459
0
        auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2460
0
        const auto &l_accuracies = coordinateOperationAccuracies();
2461
0
        if (projGridFormat == "GTiff") {
2462
0
            const VectorOfParameters parameters{
2463
0
                methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D
2464
0
                    ? OperationParameter::create(util::PropertyMap().set(
2465
0
                          common::IdentifiedObject::NAME_KEY,
2466
0
                          PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE))
2467
0
                    : createOpParamNameEPSGCode(
2468
0
                          EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
2469
0
            auto methodProperties = util::PropertyMap().set(
2470
0
                common::IdentifiedObject::NAME_KEY,
2471
0
                (methodEPSGCode == EPSG_CODE_METHOD_NADCON5_2D ||
2472
0
                 methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D)
2473
0
                    ? PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF
2474
0
                    : PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF);
2475
0
            const VectorOfValues values{
2476
0
                ParameterValue::createFilename(projFilename)};
2477
0
            if (inverseDirection) {
2478
0
                return Transformation::create(
2479
0
                           createPropertiesForInverse(self.as_nullable().get(),
2480
0
                                                      true, false),
2481
0
                           l_targetCRS, l_sourceCRS, l_interpolationCRS,
2482
0
                           methodProperties, parameters, values, l_accuracies)
2483
0
                    ->inverseAsTransformation();
2484
2485
0
            } else {
2486
0
                return Transformation::create(
2487
0
                    createSimilarPropertiesOperation(self), l_sourceCRS,
2488
0
                    l_targetCRS, l_interpolationCRS, methodProperties,
2489
0
                    parameters, values, l_accuracies);
2490
0
            }
2491
0
        } else if (projGridFormat == "NTv1") {
2492
0
            if (inverseDirection) {
2493
0
                return createNTv1(createPropertiesForInverse(
2494
0
                                      self.as_nullable().get(), true, false),
2495
0
                                  l_targetCRS, l_sourceCRS, projFilename,
2496
0
                                  l_accuracies)
2497
0
                    ->inverseAsTransformation();
2498
0
            } else {
2499
0
                return createNTv1(createSimilarPropertiesOperation(self),
2500
0
                                  l_sourceCRS, l_targetCRS, projFilename,
2501
0
                                  l_accuracies);
2502
0
            }
2503
0
        } else if (projGridFormat == "NTv2") {
2504
0
            if (inverseDirection) {
2505
0
                return Transformation::createNTv2(
2506
0
                           createPropertiesForInverse(self.as_nullable().get(),
2507
0
                                                      true, false),
2508
0
                           l_targetCRS, l_sourceCRS, projFilename, l_accuracies)
2509
0
                    ->inverseAsTransformation();
2510
0
            } else {
2511
0
                return Transformation::createNTv2(
2512
0
                    createSimilarPropertiesOperation(self), l_sourceCRS,
2513
0
                    l_targetCRS, projFilename, l_accuracies);
2514
0
            }
2515
0
        } else if (projGridFormat == "CTable2") {
2516
0
            const VectorOfParameters parameters{createOpParamNameEPSGCode(
2517
0
                EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
2518
0
            auto methodProperties =
2519
0
                util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
2520
0
                                        PROJ_WKT2_NAME_METHOD_CTABLE2);
2521
0
            const VectorOfValues values{
2522
0
                ParameterValue::createFilename(projFilename)};
2523
0
            if (inverseDirection) {
2524
0
                return Transformation::create(
2525
0
                           createPropertiesForInverse(self.as_nullable().get(),
2526
0
                                                      true, false),
2527
0
                           l_targetCRS, l_sourceCRS, l_interpolationCRS,
2528
0
                           methodProperties, parameters, values, l_accuracies)
2529
0
                    ->inverseAsTransformation();
2530
2531
0
            } else {
2532
0
                return Transformation::create(
2533
0
                    createSimilarPropertiesOperation(self), l_sourceCRS,
2534
0
                    l_targetCRS, l_interpolationCRS, methodProperties,
2535
0
                    parameters, values, l_accuracies);
2536
0
            }
2537
0
        }
2538
0
    }
2539
2540
0
    if (Transformation::isGeographic3DToGravityRelatedHeight(method(), false)) {
2541
0
        const auto &fileParameter =
2542
0
            parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
2543
0
                           EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
2544
0
        if (fileParameter &&
2545
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2546
0
            const auto &filename = fileParameter->valueFile();
2547
0
            if (databaseContext->lookForGridAlternative(
2548
0
                    filename, projFilename, projGridFormat, inverseDirection)) {
2549
2550
0
                if (inverseDirection) {
2551
0
                    throw util::UnsupportedOperationException(
2552
0
                        "Inverse direction for "
2553
0
                        "Geographic3DToGravityRelatedHeight not supported");
2554
0
                }
2555
2556
0
                if (filename == projFilename) {
2557
0
                    return self;
2558
0
                }
2559
2560
0
                const auto l_sourceCRSNull = sourceCRS();
2561
0
                const auto l_targetCRSNull = targetCRS();
2562
0
                if (l_sourceCRSNull == nullptr) {
2563
0
                    throw util::UnsupportedOperationException(
2564
0
                        "Missing sourceCRS");
2565
0
                }
2566
0
                if (l_targetCRSNull == nullptr) {
2567
0
                    throw util::UnsupportedOperationException(
2568
0
                        "Missing targetCRS");
2569
0
                }
2570
0
                auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2571
0
                auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2572
0
                const VectorOfParameters parameters{createOpParamNameEPSGCode(
2573
0
                    EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)};
2574
0
                const VectorOfValues values{
2575
0
                    ParameterValue::createFilename(projFilename)};
2576
#ifdef disabled_for_now
2577
                if (inverseDirection) {
2578
                    return Transformation::create(
2579
                               createPropertiesForInverse(
2580
                                   self.as_nullable().get(), true, false),
2581
                               l_targetCRS, l_sourceCRS, l_interpolationCRS,
2582
                               createSimilarPropertiesMethod(method()),
2583
                               parameters, values,
2584
                               coordinateOperationAccuracies())
2585
                        ->inverseAsTransformation();
2586
                } else
2587
#endif
2588
0
                {
2589
0
                    return Transformation::create(
2590
0
                        createSimilarPropertiesOperation(self), l_sourceCRS,
2591
0
                        l_targetCRS, l_interpolationCRS,
2592
0
                        createSimilarPropertiesMethod(method()), parameters,
2593
0
                        values, coordinateOperationAccuracies());
2594
0
                }
2595
0
            }
2596
0
        }
2597
0
    }
2598
2599
0
    const auto &geocentricTranslationFilename =
2600
0
        _getGeocentricTranslationFilename(this, false);
2601
0
    if (!geocentricTranslationFilename.empty()) {
2602
0
        if (databaseContext->lookForGridAlternative(
2603
0
                geocentricTranslationFilename, projFilename, projGridFormat,
2604
0
                inverseDirection)) {
2605
2606
0
            if (inverseDirection) {
2607
0
                throw util::UnsupportedOperationException(
2608
0
                    "Inverse direction for "
2609
0
                    "GeocentricTranslation not supported");
2610
0
            }
2611
2612
0
            if (geocentricTranslationFilename == projFilename) {
2613
0
                return self;
2614
0
            }
2615
2616
0
            const auto l_sourceCRSNull = sourceCRS();
2617
0
            const auto l_targetCRSNull = targetCRS();
2618
0
            if (l_sourceCRSNull == nullptr) {
2619
0
                throw util::UnsupportedOperationException("Missing sourceCRS");
2620
0
            }
2621
0
            if (l_targetCRSNull == nullptr) {
2622
0
                throw util::UnsupportedOperationException("Missing targetCRS");
2623
0
            }
2624
0
            auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2625
0
            auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2626
0
            const VectorOfParameters parameters{createOpParamNameEPSGCode(
2627
0
                EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)};
2628
0
            const VectorOfValues values{
2629
0
                ParameterValue::createFilename(projFilename)};
2630
0
            return Transformation::create(
2631
0
                createSimilarPropertiesOperation(self), l_sourceCRS,
2632
0
                l_targetCRS, l_interpolationCRS,
2633
0
                createSimilarPropertiesMethod(method()), parameters, values,
2634
0
                coordinateOperationAccuracies());
2635
0
        }
2636
0
    }
2637
2638
0
    const auto &geographic3DOffsetByVelocityGridFilename =
2639
0
        _getGeographic3DOffsetByVelocityGridFilename(this, false);
2640
0
    if (!geographic3DOffsetByVelocityGridFilename.empty()) {
2641
0
        if (databaseContext->lookForGridAlternative(
2642
0
                geographic3DOffsetByVelocityGridFilename, projFilename,
2643
0
                projGridFormat, inverseDirection)) {
2644
2645
0
            if (inverseDirection) {
2646
0
                throw util::UnsupportedOperationException(
2647
0
                    "Inverse direction for "
2648
0
                    "Geographic3DOFffsetByVelocityGrid not supported");
2649
0
            }
2650
2651
0
            if (geographic3DOffsetByVelocityGridFilename == projFilename) {
2652
0
                return self;
2653
0
            }
2654
2655
0
            const auto l_sourceCRSNull = sourceCRS();
2656
0
            const auto l_targetCRSNull = targetCRS();
2657
0
            if (l_sourceCRSNull == nullptr) {
2658
0
                throw util::UnsupportedOperationException("Missing sourceCRS");
2659
0
            }
2660
0
            if (l_targetCRSNull == nullptr) {
2661
0
                throw util::UnsupportedOperationException("Missing targetCRS");
2662
0
            }
2663
0
            auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2664
0
            auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2665
0
            const VectorOfParameters parameters{createOpParamNameEPSGCode(
2666
0
                EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE)};
2667
0
            const VectorOfValues values{
2668
0
                ParameterValue::createFilename(projFilename)};
2669
0
            return Transformation::create(
2670
0
                createSimilarPropertiesOperation(self), l_sourceCRS,
2671
0
                l_targetCRS, l_interpolationCRS,
2672
0
                createSimilarPropertiesMethod(method()), parameters, values,
2673
0
                coordinateOperationAccuracies());
2674
0
        }
2675
0
    }
2676
2677
0
    const auto &verticalOffsetByVelocityGridFilename =
2678
0
        _getVerticalOffsetByVelocityGridFilename(this, false);
2679
0
    if (!verticalOffsetByVelocityGridFilename.empty()) {
2680
0
        if (databaseContext->lookForGridAlternative(
2681
0
                verticalOffsetByVelocityGridFilename, projFilename,
2682
0
                projGridFormat, inverseDirection)) {
2683
2684
0
            if (inverseDirection) {
2685
0
                throw util::UnsupportedOperationException(
2686
0
                    "Inverse direction for "
2687
0
                    "VerticalOffsetByVelocityGrid not supported");
2688
0
            }
2689
2690
0
            if (verticalOffsetByVelocityGridFilename == projFilename) {
2691
0
                return self;
2692
0
            }
2693
2694
0
            const auto l_sourceCRSNull = sourceCRS();
2695
0
            const auto l_targetCRSNull = targetCRS();
2696
0
            if (l_sourceCRSNull == nullptr) {
2697
0
                throw util::UnsupportedOperationException("Missing sourceCRS");
2698
0
            }
2699
0
            if (l_targetCRSNull == nullptr) {
2700
0
                throw util::UnsupportedOperationException("Missing targetCRS");
2701
0
            }
2702
0
            auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2703
0
            auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2704
0
            const VectorOfParameters parameters{createOpParamNameEPSGCode(
2705
0
                EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE)};
2706
0
            const VectorOfValues values{
2707
0
                ParameterValue::createFilename(projFilename)};
2708
0
            return Transformation::create(
2709
0
                createSimilarPropertiesOperation(self), l_sourceCRS,
2710
0
                l_targetCRS, l_interpolationCRS,
2711
0
                createSimilarPropertiesMethod(method()), parameters, values,
2712
0
                coordinateOperationAccuracies());
2713
0
        }
2714
0
    }
2715
2716
0
    bool reverseOffsetSign = false;
2717
0
    if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON ||
2718
0
        isRegularVerticalGridMethod(methodEPSGCode, reverseOffsetSign)) {
2719
0
        int parameterCode = EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE;
2720
0
        auto fileParameter = parameterValue(
2721
0
            EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, parameterCode);
2722
0
        if (!fileParameter) {
2723
0
            parameterCode = EPSG_CODE_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE;
2724
0
            fileParameter = parameterValue(
2725
0
                EPSG_NAME_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE, parameterCode);
2726
0
        }
2727
0
        if (fileParameter &&
2728
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
2729
2730
0
            const auto &filename = fileParameter->valueFile();
2731
0
            if (databaseContext->lookForGridAlternative(
2732
0
                    filename, projFilename, projGridFormat, inverseDirection)) {
2733
2734
0
                if (filename == projFilename) {
2735
0
                    if (inverseDirection) {
2736
0
                        throw util::UnsupportedOperationException(
2737
0
                            "Inverse direction for " + projFilename +
2738
0
                            " not supported");
2739
0
                    }
2740
0
                    return self;
2741
0
                }
2742
2743
0
                const auto l_sourceCRSNull = sourceCRS();
2744
0
                const auto l_targetCRSNull = targetCRS();
2745
0
                if (l_sourceCRSNull == nullptr) {
2746
0
                    throw util::UnsupportedOperationException(
2747
0
                        "Missing sourceCRS");
2748
0
                }
2749
0
                if (l_targetCRSNull == nullptr) {
2750
0
                    throw util::UnsupportedOperationException(
2751
0
                        "Missing targetCRS");
2752
0
                }
2753
0
                auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2754
0
                auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2755
0
                const VectorOfParameters parameters{
2756
0
                    createOpParamNameEPSGCode(parameterCode)};
2757
0
                const VectorOfValues values{
2758
0
                    ParameterValue::createFilename(projFilename)};
2759
0
                if (inverseDirection) {
2760
0
                    return Transformation::create(
2761
0
                               createPropertiesForInverse(
2762
0
                                   self.as_nullable().get(), true, false),
2763
0
                               l_targetCRS, l_sourceCRS, l_interpolationCRS,
2764
0
                               createSimilarPropertiesMethod(method()),
2765
0
                               parameters, values,
2766
0
                               coordinateOperationAccuracies())
2767
0
                        ->inverseAsTransformation();
2768
0
                } else {
2769
0
                    return Transformation::create(
2770
0
                        createSimilarPropertiesOperation(self), l_sourceCRS,
2771
0
                        l_targetCRS, l_interpolationCRS,
2772
0
                        createSimilarPropertiesMethod(method()), parameters,
2773
0
                        values, coordinateOperationAccuracies());
2774
0
                }
2775
0
            }
2776
0
        }
2777
0
    }
2778
2779
0
    static const struct {
2780
0
        int methodEPSGCode;
2781
0
        int gridFilenameParamEPSGCode;
2782
0
        const char *gridFilenameParamName;
2783
0
    } gridTransformations[] = {
2784
0
        {EPSG_CODE_METHOD_NEW_ZEALAND_DEFORMATION_MODEL,
2785
0
         EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
2786
0
         EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE},
2787
0
        {EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON,
2788
0
         EPSG_CODE_PARAMETER_TIN_OFFSET_FILE,
2789
0
         EPSG_NAME_PARAMETER_TIN_OFFSET_FILE},
2790
0
        {EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON,
2791
0
         EPSG_CODE_PARAMETER_TIN_OFFSET_FILE,
2792
0
         EPSG_NAME_PARAMETER_TIN_OFFSET_FILE},
2793
0
        {EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON,
2794
0
         EPSG_CODE_PARAMETER_TIN_OFFSET_FILE,
2795
0
         EPSG_NAME_PARAMETER_TIN_OFFSET_FILE},
2796
0
    };
2797
2798
0
    for (const auto &gridTransf : gridTransformations) {
2799
0
        if (methodEPSGCode == gridTransf.methodEPSGCode) {
2800
0
            auto fileParameter =
2801
0
                parameterValue(gridTransf.gridFilenameParamName,
2802
0
                               gridTransf.gridFilenameParamEPSGCode);
2803
0
            if (fileParameter &&
2804
0
                fileParameter->type() == ParameterValue::Type::FILENAME) {
2805
2806
0
                const auto &filename = fileParameter->valueFile();
2807
0
                if (databaseContext->lookForGridAlternative(
2808
0
                        filename, projFilename, projGridFormat,
2809
0
                        inverseDirection)) {
2810
2811
0
                    if (filename == projFilename) {
2812
0
                        if (inverseDirection) {
2813
0
                            throw util::UnsupportedOperationException(
2814
0
                                "Inverse direction for " + projFilename +
2815
0
                                " not supported");
2816
0
                        }
2817
0
                        return self;
2818
0
                    }
2819
2820
0
                    const auto l_sourceCRSNull = sourceCRS();
2821
0
                    const auto l_targetCRSNull = targetCRS();
2822
0
                    if (l_sourceCRSNull == nullptr) {
2823
0
                        throw util::UnsupportedOperationException(
2824
0
                            "Missing sourceCRS");
2825
0
                    }
2826
0
                    if (l_targetCRSNull == nullptr) {
2827
0
                        throw util::UnsupportedOperationException(
2828
0
                            "Missing targetCRS");
2829
0
                    }
2830
0
                    auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2831
0
                    auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2832
0
                    const VectorOfParameters parameters{
2833
0
                        createOpParamNameEPSGCode(
2834
0
                            gridTransf.gridFilenameParamEPSGCode)};
2835
0
                    const VectorOfValues values{
2836
0
                        ParameterValue::createFilename(projFilename)};
2837
0
                    if (inverseDirection) {
2838
0
                        return Transformation::create(
2839
0
                                   createPropertiesForInverse(
2840
0
                                       self.as_nullable().get(), true, false),
2841
0
                                   l_targetCRS, l_sourceCRS, l_interpolationCRS,
2842
0
                                   createSimilarPropertiesMethod(method()),
2843
0
                                   parameters, values,
2844
0
                                   coordinateOperationAccuracies())
2845
0
                            ->inverseAsTransformation();
2846
0
                    } else {
2847
0
                        return Transformation::create(
2848
0
                            createSimilarPropertiesOperation(self), l_sourceCRS,
2849
0
                            l_targetCRS, l_interpolationCRS,
2850
0
                            createSimilarPropertiesMethod(method()), parameters,
2851
0
                            values, coordinateOperationAccuracies());
2852
0
                    }
2853
0
                }
2854
0
            }
2855
0
            break;
2856
0
        }
2857
0
    }
2858
2859
0
    if (methodEPSGCode ==
2860
0
            EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG ||
2861
0
        methodEPSGCode ==
2862
0
            EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG) {
2863
0
        auto fileParameter =
2864
0
            parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
2865
0
                           EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE);
2866
0
        if (fileParameter &&
2867
0
            fileParameter->type() == ParameterValue::Type::FILENAME &&
2868
0
            databaseContext->lookForGridAlternative(
2869
0
                fileParameter->valueFile(), projFilename, projGridFormat,
2870
0
                inverseDirection)) {
2871
2872
0
            if (inverseDirection) {
2873
0
                throw util::UnsupportedOperationException(
2874
0
                    "Inverse direction not supported");
2875
0
            }
2876
2877
0
            if (fileParameter->valueFile() == projFilename) {
2878
0
                return self;
2879
0
            }
2880
2881
0
            const auto l_sourceCRSNull = sourceCRS();
2882
0
            const auto l_targetCRSNull = targetCRS();
2883
0
            if (l_sourceCRSNull == nullptr) {
2884
0
                throw util::UnsupportedOperationException("Missing sourceCRS");
2885
0
            }
2886
0
            if (l_targetCRSNull == nullptr) {
2887
0
                throw util::UnsupportedOperationException("Missing targetCRS");
2888
0
            }
2889
0
            auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2890
0
            auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2891
2892
0
            std::vector<OperationParameterNNPtr> parameters;
2893
0
            std::vector<ParameterValueNNPtr> values;
2894
0
            for (const auto &genOpParamvalue : parameterValues()) {
2895
0
                auto opParamvalue =
2896
0
                    dynamic_cast<const OperationParameterValue *>(
2897
0
                        genOpParamvalue.get());
2898
0
                if (!opParamvalue) {
2899
0
                    throw util::UnsupportedOperationException(
2900
0
                        "non OperationParameterValue parameter value");
2901
0
                }
2902
0
                const auto &parameter = opParamvalue->parameter();
2903
0
                parameters.push_back(parameter);
2904
0
                if (parameter->getEPSGCode() ==
2905
0
                    EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE) {
2906
0
                    values.push_back(
2907
0
                        ParameterValue::createFilename(projFilename));
2908
0
                } else {
2909
0
                    values.push_back(opParamvalue->parameterValue());
2910
0
                }
2911
0
            }
2912
0
            return Transformation::create(
2913
0
                createSimilarPropertiesOperation(self), l_sourceCRS,
2914
0
                l_targetCRS, l_interpolationCRS,
2915
0
                createSimilarPropertiesMethod(method()), parameters, values,
2916
0
                coordinateOperationAccuracies());
2917
0
        }
2918
0
    }
2919
2920
0
    if (methodEPSGCode ==
2921
0
        EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) {
2922
0
        std::string projFilenameGTF;
2923
0
        std::string projFilenamePMV;
2924
0
        auto fileParameterGTF =
2925
0
            parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE,
2926
0
                           EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE);
2927
0
        auto fileParameterPMV =
2928
0
            parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
2929
0
                           EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE);
2930
0
        if (fileParameterGTF &&
2931
0
            fileParameterGTF->type() == ParameterValue::Type::FILENAME &&
2932
0
            databaseContext->lookForGridAlternative(
2933
0
                fileParameterGTF->valueFile(), projFilenameGTF, projGridFormat,
2934
0
                inverseDirection) &&
2935
0
            fileParameterPMV &&
2936
0
            fileParameterPMV->type() == ParameterValue::Type::FILENAME &&
2937
0
            databaseContext->lookForGridAlternative(
2938
0
                fileParameterPMV->valueFile(), projFilenamePMV, projGridFormat,
2939
0
                inverseDirection)) {
2940
2941
0
            if (inverseDirection) {
2942
0
                throw util::UnsupportedOperationException(
2943
0
                    "Inverse direction not supported");
2944
0
            }
2945
2946
0
            if (fileParameterGTF->valueFile() == projFilenameGTF &&
2947
0
                fileParameterPMV->valueFile() == projFilenamePMV) {
2948
0
                return self;
2949
0
            }
2950
2951
0
            const auto l_sourceCRSNull = sourceCRS();
2952
0
            const auto l_targetCRSNull = targetCRS();
2953
0
            if (l_sourceCRSNull == nullptr) {
2954
0
                throw util::UnsupportedOperationException("Missing sourceCRS");
2955
0
            }
2956
0
            if (l_targetCRSNull == nullptr) {
2957
0
                throw util::UnsupportedOperationException("Missing targetCRS");
2958
0
            }
2959
0
            auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull);
2960
0
            auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull);
2961
2962
0
            std::vector<OperationParameterNNPtr> parameters;
2963
0
            std::vector<ParameterValueNNPtr> values;
2964
0
            for (const auto &genOpParamvalue : parameterValues()) {
2965
0
                auto opParamvalue =
2966
0
                    dynamic_cast<const OperationParameterValue *>(
2967
0
                        genOpParamvalue.get());
2968
0
                if (!opParamvalue) {
2969
0
                    throw util::UnsupportedOperationException(
2970
0
                        "non OperationParameterValue parameter value");
2971
0
                }
2972
0
                const auto &parameter = opParamvalue->parameter();
2973
0
                parameters.push_back(parameter);
2974
0
                if (parameter->getEPSGCode() ==
2975
0
                    EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE) {
2976
0
                    values.push_back(
2977
0
                        ParameterValue::createFilename(projFilenameGTF));
2978
0
                } else if (
2979
0
                    parameter->getEPSGCode() ==
2980
0
                    EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE) {
2981
0
                    values.push_back(
2982
0
                        ParameterValue::createFilename(projFilenamePMV));
2983
0
                } else {
2984
0
                    values.push_back(opParamvalue->parameterValue());
2985
0
                }
2986
0
            }
2987
0
            return Transformation::create(
2988
0
                createSimilarPropertiesOperation(self), l_sourceCRS,
2989
0
                l_targetCRS, l_interpolationCRS,
2990
0
                createSimilarPropertiesMethod(method()), parameters, values,
2991
0
                coordinateOperationAccuracies());
2992
0
        }
2993
0
    }
2994
2995
0
    return self;
2996
0
}
2997
2998
//! @cond Doxygen_Suppress
2999
// ---------------------------------------------------------------------------
3000
3001
0
InvalidOperation::InvalidOperation(const char *message) : Exception(message) {}
3002
3003
// ---------------------------------------------------------------------------
3004
3005
InvalidOperation::InvalidOperation(const std::string &message)
3006
0
    : Exception(message) {}
3007
3008
// ---------------------------------------------------------------------------
3009
3010
0
InvalidOperation::InvalidOperation(const InvalidOperation &) = default;
3011
3012
// ---------------------------------------------------------------------------
3013
3014
0
InvalidOperation::~InvalidOperation() = default;
3015
//! @endcond
3016
3017
// ---------------------------------------------------------------------------
3018
3019
GeneralParameterValueNNPtr
3020
SingleOperation::createOperationParameterValueFromInterpolationCRS(
3021
0
    int methodEPSGCode, int crsEPSGCode) {
3022
0
    util::PropertyMap propertiesParameter;
3023
0
    propertiesParameter.set(
3024
0
        common::IdentifiedObject::NAME_KEY,
3025
0
        methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE
3026
0
            ? EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS
3027
0
            : EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS);
3028
0
    propertiesParameter.set(
3029
0
        metadata::Identifier::CODE_KEY,
3030
0
        methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE
3031
0
            ? EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS
3032
0
            : EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS);
3033
0
    propertiesParameter.set(metadata::Identifier::CODESPACE_KEY,
3034
0
                            metadata::Identifier::EPSG);
3035
0
    return OperationParameterValue::create(
3036
0
        OperationParameter::create(propertiesParameter),
3037
0
        ParameterValue::create(crsEPSGCode));
3038
0
}
3039
3040
// ---------------------------------------------------------------------------
3041
3042
void SingleOperation::exportTransformationToWKT(
3043
0
    io::WKTFormatter *formatter) const {
3044
0
    const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2;
3045
0
    if (!isWKT2) {
3046
0
        throw io::FormattingException(
3047
0
            "Transformation can only be exported to WKT2");
3048
0
    }
3049
3050
0
    if (formatter->abridgedTransformation()) {
3051
0
        formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION,
3052
0
                             !identifiers().empty());
3053
0
    } else {
3054
0
        formatter->startNode(io::WKTConstants::COORDINATEOPERATION,
3055
0
                             !identifiers().empty());
3056
0
    }
3057
3058
0
    formatter->addQuotedString(nameStr());
3059
3060
0
    if (formatter->use2019Keywords()) {
3061
0
        const auto &version = operationVersion();
3062
0
        if (version.has_value()) {
3063
0
            formatter->startNode(io::WKTConstants::VERSION, false);
3064
0
            formatter->addQuotedString(*version);
3065
0
            formatter->endNode();
3066
0
        }
3067
0
    }
3068
3069
0
    if (!formatter->abridgedTransformation()) {
3070
0
        exportSourceCRSAndTargetCRSToWKT(this, formatter);
3071
0
    }
3072
3073
0
    const auto &l_method = method();
3074
0
    l_method->_exportToWKT(formatter);
3075
3076
0
    bool hasInterpolationCRSParameter = false;
3077
0
    for (const auto &paramValue : parameterValues()) {
3078
0
        const auto opParamvalue =
3079
0
            dynamic_cast<const OperationParameterValue *>(paramValue.get());
3080
0
        const int paramEPSGCode =
3081
0
            opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0;
3082
0
        if (paramEPSGCode ==
3083
0
                EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS ||
3084
0
            paramEPSGCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) {
3085
0
            hasInterpolationCRSParameter = true;
3086
0
        }
3087
0
        paramValue->_exportToWKT(formatter, nullptr);
3088
0
    }
3089
3090
0
    const auto l_interpolationCRS = interpolationCRS();
3091
0
    if (formatter->abridgedTransformation()) {
3092
        // If we have an interpolation CRS that has a EPSG code, then
3093
        // we can export it as a PARAMETER[]
3094
0
        if (!hasInterpolationCRSParameter && l_interpolationCRS) {
3095
0
            const auto code = l_interpolationCRS->getEPSGCode();
3096
0
            if (code != 0) {
3097
0
                const auto methodEPSGCode = l_method->getEPSGCode();
3098
0
                createOperationParameterValueFromInterpolationCRS(
3099
0
                    methodEPSGCode, code)
3100
0
                    ->_exportToWKT(formatter, nullptr);
3101
0
            }
3102
0
        }
3103
0
    } else {
3104
0
        if (l_interpolationCRS) {
3105
0
            formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false);
3106
0
            interpolationCRS()->_exportToWKT(formatter);
3107
0
            formatter->endNode();
3108
0
        }
3109
3110
0
        if (!coordinateOperationAccuracies().empty()) {
3111
0
            formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false);
3112
0
            formatter->add(coordinateOperationAccuracies()[0]->value());
3113
0
            formatter->endNode();
3114
0
        }
3115
0
    }
3116
3117
0
    ObjectUsage::baseExportToWKT(formatter);
3118
0
    formatter->endNode();
3119
0
}
3120
3121
// ---------------------------------------------------------------------------
3122
3123
//! @cond Doxygen_Suppress
3124
3125
// If crs is a geographic CRS, or a compound CRS of a geographic CRS,
3126
// or a compoundCRS of a bound CRS of a geographic CRS, return that
3127
// geographic CRS
3128
static crs::GeographicCRSPtr
3129
0
extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) {
3130
0
    auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs);
3131
0
    if (!geogCRS) {
3132
0
        auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs);
3133
0
        if (compoundCRS) {
3134
0
            const auto &components = compoundCRS->componentReferenceSystems();
3135
0
            if (!components.empty()) {
3136
0
                geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
3137
0
                    components[0]);
3138
0
                if (!geogCRS) {
3139
0
                    auto boundCRS =
3140
0
                        util::nn_dynamic_pointer_cast<crs::BoundCRS>(
3141
0
                            components[0]);
3142
0
                    if (boundCRS) {
3143
0
                        geogCRS =
3144
0
                            util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
3145
0
                                boundCRS->baseCRS());
3146
0
                    }
3147
0
                }
3148
0
            }
3149
0
        } else {
3150
0
            auto boundCRS = util::nn_dynamic_pointer_cast<crs::BoundCRS>(crs);
3151
0
            if (boundCRS) {
3152
0
                geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
3153
0
                    boundCRS->baseCRS());
3154
0
            }
3155
0
        }
3156
0
    }
3157
0
    return geogCRS;
3158
0
}
3159
3160
// ---------------------------------------------------------------------------
3161
3162
[[noreturn]] static void
3163
0
ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) {
3164
0
    throw io::FormattingException(concat("Can apply ", std::string(trfrm_name),
3165
0
                                         " only to GeodeticCRS / "
3166
0
                                         "GeographicCRS"));
3167
0
}
3168
3169
// ---------------------------------------------------------------------------
3170
3171
static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
3172
                                       const crs::CRSNNPtr &crs, bool addPushV3,
3173
0
                                       const char *trfrm_name) {
3174
0
    auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
3175
0
    if (sourceCRSGeog) {
3176
0
        formatter->startInversion();
3177
0
        sourceCRSGeog->_exportToPROJString(formatter);
3178
0
        formatter->stopInversion();
3179
0
        if (util::isOfExactType<crs::DerivedGeographicCRS>(
3180
0
                *(sourceCRSGeog.get()))) {
3181
0
            const auto derivedGeogCRS =
3182
0
                dynamic_cast<const crs::DerivedGeographicCRS *>(
3183
0
                    sourceCRSGeog.get());
3184
            // The export of a DerivedGeographicCRS in non-CRS mode adds
3185
            // unit conversion and axis swapping to the base CRS.
3186
            // We must compensate for that formatter->startInversion();
3187
0
            formatter->startInversion();
3188
0
            derivedGeogCRS->baseCRS()->addAngularUnitConvertAndAxisSwap(
3189
0
                formatter);
3190
0
            formatter->stopInversion();
3191
0
        }
3192
3193
0
        if (addPushV3) {
3194
0
            formatter->addStep("push");
3195
0
            formatter->addParam("v_3");
3196
0
        }
3197
3198
0
        formatter->addStep("cart");
3199
0
        sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
3200
0
    } else {
3201
0
        auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
3202
0
        if (!sourceCRSGeod) {
3203
0
            ThrowExceptionNotGeodeticGeographic(trfrm_name);
3204
0
        }
3205
0
        formatter->startInversion();
3206
0
        sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
3207
0
        formatter->stopInversion();
3208
0
    }
3209
0
}
3210
// ---------------------------------------------------------------------------
3211
3212
static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter,
3213
                                       const crs::CRSNNPtr &crs, bool addPopV3,
3214
0
                                       const char *trfrm_name) {
3215
0
    auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
3216
0
    if (targetCRSGeog) {
3217
0
        formatter->addStep("cart");
3218
0
        formatter->setCurrentStepInverted(true);
3219
0
        targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
3220
3221
0
        if (addPopV3) {
3222
0
            formatter->addStep("pop");
3223
0
            formatter->addParam("v_3");
3224
0
        }
3225
0
        if (util::isOfExactType<crs::DerivedGeographicCRS>(
3226
0
                *(targetCRSGeog.get()))) {
3227
            // The export of a DerivedGeographicCRS in non-CRS mode adds
3228
            // unit conversion and axis swapping to the base CRS.
3229
            // We must compensate for that formatter->startInversion();
3230
0
            const auto derivedGeogCRS =
3231
0
                dynamic_cast<const crs::DerivedGeographicCRS *>(
3232
0
                    targetCRSGeog.get());
3233
0
            derivedGeogCRS->baseCRS()->addAngularUnitConvertAndAxisSwap(
3234
0
                formatter);
3235
0
        }
3236
0
        targetCRSGeog->_exportToPROJString(formatter);
3237
0
    } else {
3238
0
        auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
3239
0
        if (!targetCRSGeod) {
3240
0
            ThrowExceptionNotGeodeticGeographic(trfrm_name);
3241
0
        }
3242
0
        targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
3243
0
    }
3244
0
}
3245
3246
//! @endcond
3247
3248
// ---------------------------------------------------------------------------
3249
3250
/* static */
3251
void SingleOperation::exportToPROJStringChangeVerticalUnit(
3252
0
    io::PROJStringFormatter *formatter, double convFactor) {
3253
3254
0
    const auto uom = common::UnitOfMeasure(std::string(), convFactor,
3255
0
                                           common::UnitOfMeasure::Type::LINEAR)
3256
0
                         .exportToPROJString();
3257
0
    const std::string reverse_uom(
3258
0
        convFactor == 0.0
3259
0
            ? std::string()
3260
0
            : common::UnitOfMeasure(std::string(), 1.0 / convFactor,
3261
0
                                    common::UnitOfMeasure::Type::LINEAR)
3262
0
                  .exportToPROJString());
3263
0
    if (uom == "m") {
3264
        // do nothing
3265
0
    } else if (!uom.empty()) {
3266
0
        formatter->addStep("unitconvert");
3267
0
        formatter->addParam("z_in", uom);
3268
0
        formatter->addParam("z_out", "m");
3269
0
    } else if (!reverse_uom.empty()) {
3270
0
        formatter->addStep("unitconvert");
3271
0
        formatter->addParam("z_in", "m");
3272
0
        formatter->addParam("z_out", reverse_uom);
3273
0
    } else if (fabs(convFactor -
3274
0
                    common::UnitOfMeasure::FOOT.conversionToSI() /
3275
0
                        common::UnitOfMeasure::US_FOOT.conversionToSI()) <
3276
0
               1e-10) {
3277
0
        formatter->addStep("unitconvert");
3278
0
        formatter->addParam("z_in", "ft");
3279
0
        formatter->addParam("z_out", "us-ft");
3280
0
    } else if (fabs(convFactor -
3281
0
                    common::UnitOfMeasure::US_FOOT.conversionToSI() /
3282
0
                        common::UnitOfMeasure::FOOT.conversionToSI()) < 1e-10) {
3283
0
        formatter->addStep("unitconvert");
3284
0
        formatter->addParam("z_in", "us-ft");
3285
0
        formatter->addParam("z_out", "ft");
3286
0
    } else {
3287
0
        formatter->addStep("affine");
3288
0
        formatter->addParam("s33", convFactor);
3289
0
    }
3290
0
}
3291
3292
// ---------------------------------------------------------------------------
3293
3294
bool SingleOperation::exportToPROJStringGeneric(
3295
0
    io::PROJStringFormatter *formatter) const {
3296
0
    const int methodEPSGCode = method()->getEPSGCode();
3297
3298
0
    if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) {
3299
0
        const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value();
3300
0
        const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value();
3301
0
        const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value();
3302
0
        const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value();
3303
0
        const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value();
3304
0
        const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value();
3305
3306
        // Do not mess with axis unit and order for that transformation
3307
3308
0
        formatter->addStep("affine");
3309
0
        formatter->addParam("xoff", A0);
3310
0
        formatter->addParam("s11", A1);
3311
0
        formatter->addParam("s12", A2);
3312
0
        formatter->addParam("yoff", B0);
3313
0
        formatter->addParam("s21", B1);
3314
0
        formatter->addParam("s22", B2);
3315
3316
0
        return true;
3317
0
    }
3318
3319
0
    if (method()->nameStr() ==
3320
0
        PROJ_WKT2_NAME_METHOD_3D_AFFINE_PARAMETRIC_TRANSFORMATION) {
3321
0
        const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value();
3322
0
        const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value();
3323
0
        const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value();
3324
0
        const double A3 =
3325
0
            parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_A3).value();
3326
0
        const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value();
3327
0
        const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value();
3328
0
        const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value();
3329
0
        const double B3 =
3330
0
            parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_B3).value();
3331
0
        const double C0 =
3332
0
            parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_C0).value();
3333
0
        const double C1 =
3334
0
            parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_C1).value();
3335
0
        const double C2 =
3336
0
            parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_C2).value();
3337
0
        const double C3 =
3338
0
            parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_C3).value();
3339
3340
        // Do not mess with axis unit and order for that transformation
3341
3342
0
        formatter->addStep("affine");
3343
0
        formatter->addParam("xoff", A0);
3344
0
        formatter->addParam("s11", A1);
3345
0
        formatter->addParam("s12", A2);
3346
0
        formatter->addParam("s13", A3);
3347
0
        formatter->addParam("yoff", B0);
3348
0
        formatter->addParam("s21", B1);
3349
0
        formatter->addParam("s22", B2);
3350
0
        formatter->addParam("s23", B3);
3351
0
        formatter->addParam("zoff", C0);
3352
0
        formatter->addParam("s31", C1);
3353
0
        formatter->addParam("s32", C2);
3354
0
        formatter->addParam("s33", C3);
3355
3356
0
        return true;
3357
0
    }
3358
3359
0
    if (methodEPSGCode == EPSG_CODE_METHOD_SIMILARITY_TRANSFORMATION) {
3360
0
        const double XT0 =
3361
0
            parameterValueMeasure(
3362
0
                EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT_TARGET_CRS)
3363
0
                .value();
3364
0
        const double YT0 =
3365
0
            parameterValueMeasure(
3366
0
                EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT_TARGET_CRS)
3367
0
                .value();
3368
0
        const double M =
3369
0
            parameterValueMeasure(
3370
0
                EPSG_CODE_PARAMETER_SCALE_FACTOR_FOR_SOURCE_CRS_AXES)
3371
0
                .value();
3372
0
        const double q = parameterValueNumeric(
3373
0
            EPSG_CODE_PARAMETER_ROTATION_ANGLE_OF_SOURCE_CRS_AXES,
3374
0
            common::UnitOfMeasure::RADIAN);
3375
3376
        // Do not mess with axis unit and order for that transformation
3377
3378
0
        formatter->addStep("affine");
3379
0
        formatter->addParam("xoff", XT0);
3380
0
        formatter->addParam("s11", M * cos(q));
3381
0
        formatter->addParam("s12", M * sin(q));
3382
0
        formatter->addParam("yoff", YT0);
3383
0
        formatter->addParam("s21", -M * sin(q));
3384
0
        formatter->addParam("s22", M * cos(q));
3385
3386
0
        return true;
3387
0
    }
3388
3389
0
    if (isAxisOrderReversal(methodEPSGCode)) {
3390
0
        formatter->addStep("axisswap");
3391
0
        formatter->addParam("order", "2,1");
3392
0
        auto sourceCRSGeog =
3393
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
3394
0
        auto targetCRSGeog =
3395
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
3396
0
        if (sourceCRSGeog && targetCRSGeog) {
3397
0
            const auto &unitSrc =
3398
0
                sourceCRSGeog->coordinateSystem()->axisList()[0]->unit();
3399
0
            const auto &unitDst =
3400
0
                targetCRSGeog->coordinateSystem()->axisList()[0]->unit();
3401
0
            if (!unitSrc._isEquivalentTo(
3402
0
                    unitDst, util::IComparable::Criterion::EQUIVALENT)) {
3403
0
                formatter->addStep("unitconvert");
3404
0
                auto projUnit = unitSrc.exportToPROJString();
3405
0
                if (projUnit.empty()) {
3406
0
                    formatter->addParam("xy_in", unitSrc.conversionToSI());
3407
0
                } else {
3408
0
                    formatter->addParam("xy_in", projUnit);
3409
0
                }
3410
0
                projUnit = unitDst.exportToPROJString();
3411
0
                if (projUnit.empty()) {
3412
0
                    formatter->addParam("xy_out", unitDst.conversionToSI());
3413
0
                } else {
3414
0
                    formatter->addParam("xy_out", projUnit);
3415
0
                }
3416
0
            }
3417
0
        }
3418
0
        return true;
3419
0
    }
3420
3421
0
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) {
3422
3423
0
        auto sourceCRSGeod =
3424
0
            dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
3425
0
        if (!sourceCRSGeod) {
3426
0
            auto sourceCRSCompound =
3427
0
                dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get());
3428
0
            if (sourceCRSCompound) {
3429
0
                sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(
3430
0
                    sourceCRSCompound->componentReferenceSystems()
3431
0
                        .front()
3432
0
                        .get());
3433
0
            }
3434
0
        }
3435
0
        auto targetCRSGeod =
3436
0
            dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get());
3437
0
        if (!targetCRSGeod) {
3438
0
            auto targetCRSCompound =
3439
0
                dynamic_cast<const crs::CompoundCRS *>(targetCRS().get());
3440
0
            if (targetCRSCompound) {
3441
0
                targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(
3442
0
                    targetCRSCompound->componentReferenceSystems()
3443
0
                        .front()
3444
0
                        .get());
3445
0
            }
3446
0
        }
3447
0
        if (sourceCRSGeod && targetCRSGeod) {
3448
0
            auto sourceCRSGeog =
3449
0
                dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod);
3450
0
            auto targetCRSGeog =
3451
0
                dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod);
3452
0
            bool isSrcGeocentric = sourceCRSGeod->isGeocentric();
3453
0
            bool isSrcGeographic = sourceCRSGeog != nullptr;
3454
0
            bool isTargetGeocentric = targetCRSGeod->isGeocentric();
3455
0
            bool isTargetGeographic = targetCRSGeog != nullptr;
3456
0
            if ((isSrcGeocentric && isTargetGeographic) ||
3457
0
                (isSrcGeographic && isTargetGeocentric)) {
3458
3459
0
                formatter->startInversion();
3460
0
                sourceCRSGeod->_exportToPROJString(formatter);
3461
0
                formatter->stopInversion();
3462
3463
0
                targetCRSGeod->_exportToPROJString(formatter);
3464
3465
0
                return true;
3466
0
            }
3467
0
        }
3468
3469
0
        throw io::FormattingException("Invalid nature of source and/or "
3470
0
                                      "targetCRS for Geographic/Geocentric "
3471
0
                                      "conversion");
3472
0
    }
3473
3474
0
    if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
3475
0
        const double convFactor = parameterValueNumericAsSI(
3476
0
            EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
3477
0
        exportToPROJStringChangeVerticalUnit(formatter, convFactor);
3478
0
        return true;
3479
0
    }
3480
3481
0
    if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
3482
0
        formatter->addStep("axisswap");
3483
0
        formatter->addParam("order", "1,2,-3");
3484
0
        return true;
3485
0
    }
3486
3487
0
    formatter->setCoordinateOperationOptimizations(true);
3488
3489
0
    bool positionVectorConvention = true;
3490
0
    bool sevenParamsTransform = false;
3491
0
    bool threeParamsTransform = false;
3492
0
    bool fifteenParamsTransform = false;
3493
0
    bool fullMatrix = false;
3494
0
    const auto &l_method = method();
3495
0
    const auto &methodName = l_method->nameStr();
3496
0
    const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF);
3497
0
    const auto paramCount = parameterValues().size();
3498
0
    const bool l_isTimeDependent = isTimeDependent(methodName);
3499
0
    const bool isPositionVector =
3500
0
        ci_find(methodName, "Position Vector") != std::string::npos ||
3501
0
        ci_find(methodName, "PV") != std::string::npos;
3502
0
    const bool isCoordinateFrame =
3503
0
        ci_find(methodName, "Coordinate Frame") != std::string::npos ||
3504
0
        ci_find(methodName, "CF") != std::string::npos;
3505
0
    if (methodEPSGCode ==
3506
0
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC ||
3507
0
        methodEPSGCode ==
3508
0
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D ||
3509
0
        methodEPSGCode ==
3510
0
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D) {
3511
0
        positionVectorConvention = false;
3512
0
        sevenParamsTransform = true;
3513
0
        fullMatrix = true;
3514
0
    } else if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) ||
3515
0
               methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
3516
0
               methodEPSGCode ==
3517
0
                   EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
3518
0
               methodEPSGCode ==
3519
0
                   EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D ||
3520
0
               methodEPSGCode ==
3521
0
                   EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND) {
3522
0
        positionVectorConvention = false;
3523
0
        sevenParamsTransform = true;
3524
0
    } else if (
3525
0
        (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) ||
3526
0
        methodEPSGCode ==
3527
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
3528
0
        methodEPSGCode ==
3529
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
3530
0
        methodEPSGCode ==
3531
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
3532
0
        positionVectorConvention = false;
3533
0
        fifteenParamsTransform = true;
3534
0
    } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) ||
3535
0
               methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
3536
0
               methodEPSGCode ==
3537
0
                   EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
3538
0
               methodEPSGCode ==
3539
0
                   EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
3540
0
        sevenParamsTransform = true;
3541
0
    } else if (
3542
0
        (paramCount == 15 && isPositionVector && l_isTimeDependent) ||
3543
0
        methodEPSGCode ==
3544
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
3545
0
        methodEPSGCode ==
3546
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
3547
0
        methodEPSGCode ==
3548
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
3549
0
        fifteenParamsTransform = true;
3550
0
    } else if ((paramCount == 3 &&
3551
0
                ci_find(methodName, "Geocentric translations") !=
3552
0
                    std::string::npos) ||
3553
0
               methodEPSGCode ==
3554
0
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
3555
0
               methodEPSGCode ==
3556
0
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
3557
0
               methodEPSGCode ==
3558
0
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
3559
0
        threeParamsTransform = true;
3560
0
    }
3561
0
    if (threeParamsTransform || sevenParamsTransform ||
3562
0
        fifteenParamsTransform) {
3563
0
        double x =
3564
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
3565
0
        double y =
3566
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
3567
0
        double z =
3568
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
3569
3570
0
        auto l_sourceCRS = sourceCRS();
3571
0
        auto l_targetCRS = targetCRS();
3572
0
        auto sourceCRSGeog =
3573
0
            dynamic_cast<const crs::GeographicCRS *>(l_sourceCRS.get());
3574
0
        auto targetCRSGeog =
3575
0
            dynamic_cast<const crs::GeographicCRS *>(l_targetCRS.get());
3576
0
        const bool sourceIsCompound =
3577
0
            !sourceCRSGeog &&
3578
0
            dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get());
3579
0
        const bool targetIsCompound =
3580
0
            !targetCRSGeog &&
3581
0
            dynamic_cast<const crs::CompoundCRS *>(l_targetCRS.get());
3582
0
        const bool addPushPopV3 =
3583
0
            (((sourceCRSGeog &&
3584
0
               sourceCRSGeog->coordinateSystem()->axisList().size() == 2) ||
3585
0
              (targetCRSGeog &&
3586
0
               targetCRSGeog->coordinateSystem()->axisList().size() == 2)) ||
3587
0
             sourceIsCompound || targetIsCompound) &&
3588
            // Below check is for example for
3589
            // EPSG:10905 ("ETRS89/DREF91/2016 to Asse 2025 + Asse 2025 height
3590
            // (1)") whose target CRS is a compound CRS
3591
0
            !(sourceCRSGeog &&
3592
0
              sourceCRSGeog->coordinateSystem()->axisList().size() == 3 &&
3593
0
              targetIsCompound) &&
3594
0
            !(targetCRSGeog &&
3595
0
              targetCRSGeog->coordinateSystem()->axisList().size() == 3 &&
3596
0
              sourceIsCompound);
3597
3598
0
        if (l_sourceCRS) {
3599
0
            setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS),
3600
0
                                       addPushPopV3, "Helmert");
3601
0
        }
3602
3603
0
        double sourceYear =
3604
0
            sourceCoordinateEpoch().has_value()
3605
0
                ? getRoundedEpochInDecimalYear(
3606
0
                      sourceCoordinateEpoch()->coordinateEpoch().convertToUnit(
3607
0
                          common::UnitOfMeasure::YEAR))
3608
0
                : 0;
3609
0
        double targetYear =
3610
0
            targetCoordinateEpoch().has_value()
3611
0
                ? getRoundedEpochInDecimalYear(
3612
0
                      targetCoordinateEpoch()->coordinateEpoch().convertToUnit(
3613
0
                          common::UnitOfMeasure::YEAR))
3614
0
                : 0;
3615
0
        if (sourceYear > 0 && targetYear == 0)
3616
0
            targetYear = sourceYear;
3617
0
        else if (targetYear > 0 && sourceYear == 0)
3618
0
            sourceYear = targetYear;
3619
0
        if (sourceYear > 0) {
3620
0
            formatter->addStep("set");
3621
0
            formatter->addParam("v_4", sourceYear);
3622
0
        }
3623
3624
0
        formatter->addStep("helmert");
3625
0
        if (fullMatrix)
3626
0
            formatter->addParam("exact");
3627
0
        formatter->addParam("x", x);
3628
0
        formatter->addParam("y", y);
3629
0
        formatter->addParam("z", z);
3630
0
        if (sevenParamsTransform || fifteenParamsTransform) {
3631
0
            double rx =
3632
0
                parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
3633
0
                                      common::UnitOfMeasure::ARC_SECOND);
3634
0
            double ry =
3635
0
                parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
3636
0
                                      common::UnitOfMeasure::ARC_SECOND);
3637
0
            double rz =
3638
0
                parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
3639
0
                                      common::UnitOfMeasure::ARC_SECOND);
3640
0
            double scaleDiff =
3641
0
                parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
3642
0
                                      common::UnitOfMeasure::PARTS_PER_MILLION);
3643
0
            formatter->addParam("rx", rx);
3644
0
            formatter->addParam("ry", ry);
3645
0
            formatter->addParam("rz", rz);
3646
0
            formatter->addParam("s", scaleDiff);
3647
0
            if (fifteenParamsTransform) {
3648
0
                double rate_x = parameterValueNumeric(
3649
0
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
3650
0
                    common::UnitOfMeasure::METRE_PER_YEAR);
3651
0
                double rate_y = parameterValueNumeric(
3652
0
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
3653
0
                    common::UnitOfMeasure::METRE_PER_YEAR);
3654
0
                double rate_z = parameterValueNumeric(
3655
0
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
3656
0
                    common::UnitOfMeasure::METRE_PER_YEAR);
3657
0
                double rate_rx = parameterValueNumeric(
3658
0
                    EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
3659
0
                    common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
3660
0
                double rate_ry = parameterValueNumeric(
3661
0
                    EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
3662
0
                    common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
3663
0
                double rate_rz = parameterValueNumeric(
3664
0
                    EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
3665
0
                    common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
3666
0
                double rate_scaleDiff = parameterValueNumeric(
3667
0
                    EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
3668
0
                    common::UnitOfMeasure::PPM_PER_YEAR);
3669
0
                double referenceEpochYear =
3670
0
                    parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
3671
0
                                          common::UnitOfMeasure::YEAR);
3672
0
                formatter->addParam("dx", rate_x);
3673
0
                formatter->addParam("dy", rate_y);
3674
0
                formatter->addParam("dz", rate_z);
3675
0
                formatter->addParam("drx", rate_rx);
3676
0
                formatter->addParam("dry", rate_ry);
3677
0
                formatter->addParam("drz", rate_rz);
3678
0
                formatter->addParam("ds", rate_scaleDiff);
3679
0
                formatter->addParam("t_epoch", referenceEpochYear);
3680
0
            }
3681
0
            if (positionVectorConvention) {
3682
0
                formatter->addParam("convention", "position_vector");
3683
0
            } else {
3684
0
                formatter->addParam("convention", "coordinate_frame");
3685
0
            }
3686
0
        }
3687
3688
0
        if (targetYear > 0) {
3689
0
            formatter->addStep("set");
3690
0
            formatter->addParam("v_4", targetYear);
3691
0
        }
3692
3693
0
        if (l_targetCRS) {
3694
0
            setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS),
3695
0
                                       addPushPopV3, "Helmert");
3696
0
        }
3697
3698
0
        return true;
3699
0
    }
3700
3701
0
    if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC ||
3702
0
        methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
3703
0
        methodEPSGCode ==
3704
0
            EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D ||
3705
0
        methodEPSGCode ==
3706
0
            EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
3707
0
        methodEPSGCode ==
3708
0
            EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D ||
3709
0
        methodEPSGCode ==
3710
0
            EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) {
3711
3712
0
        positionVectorConvention =
3713
0
            isPositionVector ||
3714
0
            methodEPSGCode ==
3715
0
                EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
3716
0
            methodEPSGCode ==
3717
0
                EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
3718
0
            methodEPSGCode ==
3719
0
                EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D;
3720
3721
0
        double x =
3722
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
3723
0
        double y =
3724
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
3725
0
        double z =
3726
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
3727
0
        double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
3728
0
                                          common::UnitOfMeasure::ARC_SECOND);
3729
0
        double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
3730
0
                                          common::UnitOfMeasure::ARC_SECOND);
3731
0
        double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
3732
0
                                          common::UnitOfMeasure::ARC_SECOND);
3733
0
        double scaleDiff =
3734
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
3735
0
                                  common::UnitOfMeasure::PARTS_PER_MILLION);
3736
3737
0
        double px = parameterValueNumericAsSI(
3738
0
            EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT);
3739
0
        double py = parameterValueNumericAsSI(
3740
0
            EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT);
3741
0
        double pz = parameterValueNumericAsSI(
3742
0
            EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT);
3743
3744
0
        bool addPushPopV3 =
3745
0
            (methodEPSGCode ==
3746
0
                 EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D ||
3747
0
             methodEPSGCode ==
3748
0
                 EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D);
3749
3750
0
        auto l_sourceCRS = sourceCRS();
3751
0
        if (l_sourceCRS) {
3752
0
            setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS),
3753
0
                                       addPushPopV3, "Molodensky-Badekas");
3754
0
        }
3755
3756
0
        formatter->addStep("molobadekas");
3757
0
        formatter->addParam("x", x);
3758
0
        formatter->addParam("y", y);
3759
0
        formatter->addParam("z", z);
3760
0
        formatter->addParam("rx", rx);
3761
0
        formatter->addParam("ry", ry);
3762
0
        formatter->addParam("rz", rz);
3763
0
        formatter->addParam("s", scaleDiff);
3764
0
        formatter->addParam("px", px);
3765
0
        formatter->addParam("py", py);
3766
0
        formatter->addParam("pz", pz);
3767
0
        if (positionVectorConvention) {
3768
0
            formatter->addParam("convention", "position_vector");
3769
0
        } else {
3770
0
            formatter->addParam("convention", "coordinate_frame");
3771
0
        }
3772
3773
0
        auto l_targetCRS = targetCRS();
3774
0
        if (l_targetCRS) {
3775
0
            setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS),
3776
0
                                       addPushPopV3, "Molodensky-Badekas");
3777
0
        }
3778
3779
0
        return true;
3780
0
    }
3781
3782
0
    if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
3783
0
        methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
3784
0
        double x =
3785
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
3786
0
        double y =
3787
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
3788
0
        double z =
3789
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
3790
0
        double da = parameterValueNumericAsSI(
3791
0
            EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
3792
0
        double df = parameterValueNumericAsSI(
3793
0
            EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
3794
3795
0
        auto sourceCRSGeog =
3796
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
3797
0
        if (!sourceCRSGeog) {
3798
0
            throw io::FormattingException(
3799
0
                "Can apply Molodensky only to GeographicCRS");
3800
0
        }
3801
3802
0
        auto targetCRSGeog =
3803
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
3804
0
        if (!targetCRSGeog) {
3805
0
            throw io::FormattingException(
3806
0
                "Can apply Molodensky only to GeographicCRS");
3807
0
        }
3808
3809
0
        formatter->startInversion();
3810
0
        sourceCRSGeog->_exportToPROJString(formatter);
3811
0
        formatter->stopInversion();
3812
3813
0
        formatter->addStep("molodensky");
3814
0
        sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
3815
0
        formatter->addParam("dx", x);
3816
0
        formatter->addParam("dy", y);
3817
0
        formatter->addParam("dz", z);
3818
0
        formatter->addParam("da", da);
3819
0
        formatter->addParam("df", df);
3820
3821
0
        if (ci_find(methodName, "Abridged") != std::string::npos ||
3822
0
            methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
3823
0
            formatter->addParam("abridged");
3824
0
        }
3825
3826
0
        targetCRSGeog->_exportToPROJString(formatter);
3827
3828
0
        return true;
3829
0
    }
3830
3831
0
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
3832
0
        double offsetLat =
3833
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
3834
0
                                  common::UnitOfMeasure::ARC_SECOND);
3835
0
        double offsetLong =
3836
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
3837
0
                                  common::UnitOfMeasure::ARC_SECOND);
3838
3839
0
        auto l_sourceCRS = sourceCRS();
3840
0
        auto sourceCRSGeog =
3841
0
            l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent(
3842
0
                              NN_NO_CHECK(l_sourceCRS))
3843
0
                        : nullptr;
3844
0
        if (!sourceCRSGeog) {
3845
0
            throw io::FormattingException(
3846
0
                "Can apply Geographic 2D offsets only to GeographicCRS");
3847
0
        }
3848
3849
0
        auto l_targetCRS = targetCRS();
3850
0
        auto targetCRSGeog =
3851
0
            l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent(
3852
0
                              NN_NO_CHECK(l_targetCRS))
3853
0
                        : nullptr;
3854
0
        if (!targetCRSGeog) {
3855
0
            throw io::FormattingException(
3856
0
                "Can apply Geographic 2D offsets only to GeographicCRS");
3857
0
        }
3858
3859
0
        formatter->startInversion();
3860
0
        sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3861
0
        formatter->stopInversion();
3862
3863
0
        if (offsetLat != 0.0 || offsetLong != 0.0) {
3864
0
            formatter->addStep("geogoffset");
3865
0
            formatter->addParam("dlat", offsetLat);
3866
0
            formatter->addParam("dlon", offsetLong);
3867
0
        }
3868
3869
0
        targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3870
3871
0
        return true;
3872
0
    }
3873
3874
0
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
3875
0
        double offsetLat =
3876
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
3877
0
                                  common::UnitOfMeasure::ARC_SECOND);
3878
0
        double offsetLong =
3879
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
3880
0
                                  common::UnitOfMeasure::ARC_SECOND);
3881
0
        double offsetHeight =
3882
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
3883
3884
0
        auto sourceCRSGeog =
3885
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
3886
0
        if (!sourceCRSGeog) {
3887
0
            auto boundCRS =
3888
0
                dynamic_cast<const crs::BoundCRS *>(sourceCRS().get());
3889
0
            if (boundCRS) {
3890
0
                sourceCRSGeog = dynamic_cast<crs::GeographicCRS *>(
3891
0
                    boundCRS->baseCRS().get());
3892
0
            }
3893
0
            if (!sourceCRSGeog) {
3894
0
                throw io::FormattingException(
3895
0
                    "Can apply Geographic 3D offsets only to GeographicCRS");
3896
0
            }
3897
0
        }
3898
3899
0
        auto targetCRSGeog =
3900
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
3901
0
        if (!targetCRSGeog) {
3902
0
            auto boundCRS =
3903
0
                dynamic_cast<const crs::BoundCRS *>(targetCRS().get());
3904
0
            if (boundCRS) {
3905
0
                targetCRSGeog = dynamic_cast<const crs::GeographicCRS *>(
3906
0
                    boundCRS->baseCRS().get());
3907
0
            }
3908
0
            if (!targetCRSGeog) {
3909
0
                throw io::FormattingException(
3910
0
                    "Can apply Geographic 3D offsets only to GeographicCRS");
3911
0
            }
3912
0
        }
3913
3914
0
        formatter->startInversion();
3915
0
        sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3916
0
        formatter->stopInversion();
3917
3918
0
        if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
3919
0
            formatter->addStep("geogoffset");
3920
0
            formatter->addParam("dlat", offsetLat);
3921
0
            formatter->addParam("dlon", offsetLong);
3922
0
            formatter->addParam("dh", offsetHeight);
3923
0
        }
3924
3925
0
        targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3926
3927
0
        return true;
3928
0
    }
3929
3930
0
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
3931
0
        double offsetLat =
3932
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
3933
0
                                  common::UnitOfMeasure::ARC_SECOND);
3934
0
        double offsetLong =
3935
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
3936
0
                                  common::UnitOfMeasure::ARC_SECOND);
3937
0
        double offsetHeight =
3938
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT);
3939
3940
0
        auto sourceCRSGeog =
3941
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
3942
0
        if (!sourceCRSGeog) {
3943
0
            auto sourceCRSCompound =
3944
0
                dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get());
3945
0
            if (sourceCRSCompound) {
3946
0
                sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get();
3947
0
            }
3948
0
            if (!sourceCRSGeog) {
3949
0
                throw io::FormattingException("Can apply Geographic 2D with "
3950
0
                                              "height offsets only to "
3951
0
                                              "GeographicCRS / CompoundCRS");
3952
0
            }
3953
0
        }
3954
3955
0
        auto targetCRSGeog =
3956
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
3957
0
        if (!targetCRSGeog) {
3958
0
            auto targetCRSCompound =
3959
0
                dynamic_cast<const crs::CompoundCRS *>(targetCRS().get());
3960
0
            if (targetCRSCompound) {
3961
0
                targetCRSGeog = targetCRSCompound->extractGeographicCRS().get();
3962
0
            }
3963
0
            if (!targetCRSGeog) {
3964
0
                throw io::FormattingException("Can apply Geographic 2D with "
3965
0
                                              "height offsets only to "
3966
0
                                              "GeographicCRS / CompoundCRS");
3967
0
            }
3968
0
        }
3969
3970
0
        formatter->startInversion();
3971
0
        sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3972
0
        formatter->stopInversion();
3973
3974
0
        if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
3975
0
            formatter->addStep("geogoffset");
3976
0
            formatter->addParam("dlat", offsetLat);
3977
0
            formatter->addParam("dlon", offsetLong);
3978
0
            formatter->addParam("dh", offsetHeight);
3979
0
        }
3980
3981
0
        targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3982
3983
0
        return true;
3984
0
    }
3985
3986
0
    if (methodEPSGCode == EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS) {
3987
0
        double eastingOffset = parameterValueNumeric(
3988
0
            EPSG_CODE_PARAMETER_EASTING_OFFSET, common::UnitOfMeasure::METRE);
3989
0
        double northingOffset = parameterValueNumeric(
3990
0
            EPSG_CODE_PARAMETER_NORTHING_OFFSET, common::UnitOfMeasure::METRE);
3991
3992
0
        const auto checkIfCompatEngineeringCRS = [](const crs::CRSPtr &crs) {
3993
0
            auto engineeringCRS =
3994
0
                dynamic_cast<const crs::EngineeringCRS *>(crs.get());
3995
0
            if (engineeringCRS) {
3996
0
                auto cs = dynamic_cast<cs::CartesianCS *>(
3997
0
                    engineeringCRS->coordinateSystem().get());
3998
0
                if (!cs) {
3999
0
                    throw io::FormattingException(
4000
0
                        "Can apply Cartesian grid offsets only to "
4001
0
                        "EngineeringCRS with CartesianCS");
4002
0
                }
4003
0
                const auto &unit = cs->axisList()[0]->unit();
4004
0
                if (!unit._isEquivalentTo(
4005
0
                        common::UnitOfMeasure::METRE,
4006
0
                        util::IComparable::Criterion::EQUIVALENT)) {
4007
                    // Could be enhanced to support other units...
4008
0
                    throw io::FormattingException(
4009
0
                        "Can apply Cartesian grid offsets only to "
4010
0
                        "EngineeringCRS with CartesianCS with metre unit");
4011
0
                }
4012
0
            } else {
4013
0
                throw io::FormattingException(
4014
0
                    "Can apply Cartesian grid offsets only to ProjectedCRS or "
4015
0
                    "EngineeringCRS");
4016
0
            }
4017
0
        };
4018
4019
0
        auto l_sourceCRS = sourceCRS();
4020
0
        auto sourceCRSProj =
4021
0
            dynamic_cast<const crs::ProjectedCRS *>(l_sourceCRS.get());
4022
0
        if (!sourceCRSProj) {
4023
0
            checkIfCompatEngineeringCRS(l_sourceCRS);
4024
0
        }
4025
4026
0
        auto l_targetCRS = targetCRS();
4027
0
        auto targetCRSProj =
4028
0
            dynamic_cast<const crs::ProjectedCRS *>(l_targetCRS.get());
4029
0
        if (!targetCRSProj) {
4030
0
            checkIfCompatEngineeringCRS(l_targetCRS);
4031
0
        }
4032
4033
0
        if (sourceCRSProj) {
4034
0
            formatter->startInversion();
4035
0
            sourceCRSProj->addUnitConvertAndAxisSwap(formatter, false);
4036
0
            formatter->stopInversion();
4037
0
        }
4038
4039
0
        if (eastingOffset != 0.0 || northingOffset != 0.0) {
4040
0
            formatter->addStep("affine");
4041
0
            formatter->addParam("xoff", eastingOffset);
4042
0
            formatter->addParam("yoff", northingOffset);
4043
0
        }
4044
4045
0
        if (targetCRSProj) {
4046
0
            targetCRSProj->addUnitConvertAndAxisSwap(formatter, false);
4047
0
        }
4048
4049
0
        return true;
4050
0
    }
4051
4052
0
    if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
4053
4054
0
        const crs::CRS *srcCRS = sourceCRS().get();
4055
0
        const crs::CRS *tgtCRS = targetCRS().get();
4056
4057
0
        const auto sourceCRSCompound =
4058
0
            dynamic_cast<const crs::CompoundCRS *>(srcCRS);
4059
0
        const auto targetCRSCompound =
4060
0
            dynamic_cast<const crs::CompoundCRS *>(tgtCRS);
4061
0
        if (sourceCRSCompound && targetCRSCompound &&
4062
0
            sourceCRSCompound->componentReferenceSystems()[0]->_isEquivalentTo(
4063
0
                targetCRSCompound->componentReferenceSystems()[0].get(),
4064
0
                util::IComparable::Criterion::EQUIVALENT)) {
4065
0
            srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get();
4066
0
            tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get();
4067
0
        }
4068
4069
0
        auto sourceCRSVert = dynamic_cast<const crs::VerticalCRS *>(srcCRS);
4070
0
        if (!sourceCRSVert) {
4071
0
            throw io::FormattingException(
4072
0
                "Can apply Vertical offset only to VerticalCRS");
4073
0
        }
4074
4075
0
        auto targetCRSVert = dynamic_cast<const crs::VerticalCRS *>(tgtCRS);
4076
0
        if (!targetCRSVert) {
4077
0
            throw io::FormattingException(
4078
0
                "Can apply Vertical offset only to VerticalCRS");
4079
0
        }
4080
4081
0
        auto offsetHeight =
4082
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
4083
4084
0
        formatter->startInversion();
4085
0
        sourceCRSVert->addLinearUnitConvert(formatter);
4086
0
        formatter->stopInversion();
4087
4088
0
        if (offsetHeight != 0) {
4089
0
            formatter->addStep("geogoffset");
4090
0
            formatter->addParam("dh", offsetHeight);
4091
0
        }
4092
4093
0
        targetCRSVert->addLinearUnitConvert(formatter);
4094
4095
0
        return true;
4096
0
    }
4097
4098
0
    if (methodEPSGCode ==
4099
0
            EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT ||
4100
0
        methodEPSGCode ==
4101
0
            EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT) {
4102
0
        const crs::CRS *tgtCRS = targetCRS().get();
4103
0
        if (const auto targetCRSCompound =
4104
0
                dynamic_cast<const crs::CompoundCRS *>(tgtCRS)) {
4105
0
            tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get();
4106
0
        }
4107
0
        auto targetCRSVert = dynamic_cast<const crs::VerticalCRS *>(tgtCRS);
4108
0
        if (!targetCRSVert) {
4109
0
            throw io::FormattingException(
4110
0
                "Can apply Geographic3D to GravityRelatedHeight only to "
4111
0
                "VerticalCRS");
4112
0
        }
4113
4114
0
        auto geoidHeight =
4115
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT);
4116
4117
0
        if (geoidHeight != 0) {
4118
0
            formatter->addStep("affine");
4119
            // In the forward direction (Geographic3D to GravityRelatedHeight)
4120
            // we subtract the geoid height
4121
0
            formatter->addParam("zoff",
4122
0
                                isMethodInverseOf ? geoidHeight : -geoidHeight);
4123
0
        }
4124
4125
0
        targetCRSVert->addLinearUnitConvert(formatter);
4126
4127
0
        return true;
4128
0
    } else if (
4129
0
        ci_equal(l_method->nameStr(),
4130
0
                 INVERSE_OF +
4131
0
                     EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT) ||
4132
0
        ci_equal(
4133
0
            l_method->nameStr(),
4134
0
            INVERSE_OF +
4135
0
                EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT)) {
4136
0
        const crs::CRS *srcCRS = sourceCRS().get();
4137
0
        if (const auto sourceCRSCompound =
4138
0
                dynamic_cast<const crs::CompoundCRS *>(srcCRS)) {
4139
0
            srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get();
4140
0
        }
4141
0
        auto sourceCRSVert = dynamic_cast<const crs::VerticalCRS *>(srcCRS);
4142
0
        if (!sourceCRSVert) {
4143
0
            throw io::FormattingException(
4144
0
                "Can apply Inverse of Geographic3D to GravityRelatedHeight "
4145
0
                "only to VerticalCRS");
4146
0
        }
4147
4148
0
        auto geoidHeight =
4149
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT);
4150
4151
0
        formatter->startInversion();
4152
0
        sourceCRSVert->addLinearUnitConvert(formatter);
4153
0
        formatter->stopInversion();
4154
4155
0
        if (geoidHeight != 0) {
4156
0
            formatter->addStep("affine");
4157
            // In the forward direction (Geographic3D to GravityRelatedHeight)
4158
            // we subtract the geoid height
4159
0
            formatter->addParam("zoff", geoidHeight);
4160
0
        }
4161
4162
0
        return true;
4163
0
    }
4164
4165
0
    if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE) {
4166
4167
0
        const crs::CRS *srcCRS = sourceCRS().get();
4168
0
        const crs::CRS *tgtCRS = targetCRS().get();
4169
4170
0
        const auto sourceCRSCompound =
4171
0
            dynamic_cast<const crs::CompoundCRS *>(srcCRS);
4172
0
        const auto targetCRSCompound =
4173
0
            dynamic_cast<const crs::CompoundCRS *>(tgtCRS);
4174
0
        if (sourceCRSCompound && targetCRSCompound &&
4175
0
            sourceCRSCompound->componentReferenceSystems()[0]->_isEquivalentTo(
4176
0
                targetCRSCompound->componentReferenceSystems()[0].get(),
4177
0
                util::IComparable::Criterion::EQUIVALENT)) {
4178
0
            srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get();
4179
0
            tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get();
4180
0
        }
4181
4182
0
        auto sourceCRSVert = dynamic_cast<const crs::VerticalCRS *>(srcCRS);
4183
0
        if (!sourceCRSVert) {
4184
0
            throw io::FormattingException(
4185
0
                "Can apply Vertical offset and slope only to VerticalCRS");
4186
0
        }
4187
4188
0
        auto targetCRSVert = dynamic_cast<const crs::VerticalCRS *>(tgtCRS);
4189
0
        if (!targetCRSVert) {
4190
0
            throw io::FormattingException(
4191
0
                "Can apply Vertical offset and slope only to VerticalCRS");
4192
0
        }
4193
4194
0
        const auto latitudeEvaluationPoint =
4195
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT,
4196
0
                                  common::UnitOfMeasure::DEGREE);
4197
0
        const auto longitudeEvaluationPoint =
4198
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT,
4199
0
                                  common::UnitOfMeasure::DEGREE);
4200
0
        const auto offsetHeight =
4201
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
4202
0
        const auto inclinationLatitude =
4203
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_INCLINATION_IN_LATITUDE,
4204
0
                                  common::UnitOfMeasure::ARC_SECOND);
4205
0
        const auto inclinationLongitude =
4206
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_INCLINATION_IN_LONGITUDE,
4207
0
                                  common::UnitOfMeasure::ARC_SECOND);
4208
4209
0
        formatter->startInversion();
4210
0
        sourceCRSVert->addLinearUnitConvert(formatter);
4211
0
        formatter->stopInversion();
4212
4213
0
        formatter->addStep("vertoffset");
4214
0
        formatter->addParam("lat_0", latitudeEvaluationPoint);
4215
0
        formatter->addParam("lon_0", longitudeEvaluationPoint);
4216
0
        formatter->addParam("dh", offsetHeight);
4217
0
        formatter->addParam("slope_lat", inclinationLatitude);
4218
0
        formatter->addParam("slope_lon", inclinationLongitude);
4219
4220
0
        targetCRSVert->addLinearUnitConvert(formatter);
4221
4222
0
        return true;
4223
0
    }
4224
4225
    // Substitute grid names with PROJ friendly names.
4226
0
    if (formatter->databaseContext()) {
4227
0
        auto alternate = substitutePROJAlternativeGridNames(
4228
0
            NN_NO_CHECK(formatter->databaseContext()));
4229
0
        auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
4230
0
            shared_from_this().as_nullable()));
4231
4232
0
        if (alternate != self) {
4233
0
            alternate->_exportToPROJString(formatter);
4234
0
            return true;
4235
0
        }
4236
0
    }
4237
4238
0
    const auto &NTv1Filename = _getNTv1Filename(this, true);
4239
0
    const auto &NTv2Filename = _getNTv2Filename(this, true);
4240
0
    const auto &CTABLE2Filename = _getCTABLE2Filename(this, true);
4241
0
    const auto &HorizontalShiftGTIFFFilename =
4242
0
        _getHorizontalShiftGTIFFFilename(this, true);
4243
0
    const auto &hGridShiftFilename = !HorizontalShiftGTIFFFilename.empty()
4244
0
                                         ? HorizontalShiftGTIFFFilename
4245
0
                                     : !NTv1Filename.empty() ? NTv1Filename
4246
0
                                     : !NTv2Filename.empty() ? NTv2Filename
4247
0
                                                             : CTABLE2Filename;
4248
0
    if (!hGridShiftFilename.empty()) {
4249
0
        auto l_sourceCRS = sourceCRS();
4250
0
        auto sourceCRSGeog =
4251
0
            l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent(
4252
0
                              NN_NO_CHECK(l_sourceCRS))
4253
0
                        : nullptr;
4254
0
        if (!sourceCRSGeog) {
4255
0
            throw io::FormattingException(
4256
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4257
0
        }
4258
4259
0
        auto l_targetCRS = targetCRS();
4260
0
        auto targetCRSGeog =
4261
0
            l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent(
4262
0
                              NN_NO_CHECK(l_targetCRS))
4263
0
                        : nullptr;
4264
0
        if (!targetCRSGeog) {
4265
0
            throw io::FormattingException(
4266
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4267
0
        }
4268
4269
0
        if (!formatter->omitHorizontalConversionInVertTransformation()) {
4270
0
            formatter->startInversion();
4271
0
            sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4272
0
            formatter->stopInversion();
4273
0
        }
4274
4275
0
        if (isMethodInverseOf) {
4276
0
            formatter->startInversion();
4277
0
        }
4278
0
        if (methodName.find(PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF) !=
4279
0
            std::string::npos) {
4280
0
            formatter->addStep("gridshift");
4281
0
            if (sourceCRSGeog->coordinateSystem()->axisList().size() == 2 &&
4282
0
                parameterValue(
4283
0
                    PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE,
4284
0
                    0) != nullptr) {
4285
0
                formatter->addParam("no_z_transform");
4286
0
            }
4287
0
        } else
4288
0
            formatter->addStep("hgridshift");
4289
0
        formatter->addParam("grids", hGridShiftFilename);
4290
0
        if (isMethodInverseOf) {
4291
0
            formatter->stopInversion();
4292
0
        }
4293
4294
0
        if (!formatter->omitHorizontalConversionInVertTransformation()) {
4295
0
            targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4296
0
        }
4297
4298
0
        return true;
4299
0
    }
4300
4301
0
    const auto &geocentricTranslationFilename =
4302
0
        _getGeocentricTranslationFilename(this, true);
4303
0
    if (!geocentricTranslationFilename.empty()) {
4304
0
        auto sourceCRSGeog =
4305
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
4306
0
        if (!sourceCRSGeog) {
4307
0
            throw io::FormattingException(
4308
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4309
0
        }
4310
4311
0
        auto targetCRSGeog =
4312
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
4313
0
        if (!targetCRSGeog) {
4314
0
            throw io::FormattingException(
4315
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4316
0
        }
4317
4318
0
        const auto &interpCRS = interpolationCRS();
4319
0
        if (!interpCRS) {
4320
0
            throw io::FormattingException(
4321
0
                "InterpolationCRS required "
4322
0
                "for"
4323
0
                " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN);
4324
0
        }
4325
0
        const bool interpIsSrc = interpCRS->_isEquivalentTo(
4326
0
            sourceCRS().get(),
4327
0
            util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS);
4328
0
        const bool interpIsTarget = interpCRS->_isEquivalentTo(
4329
0
            targetCRS().get(),
4330
0
            util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS);
4331
0
        if (!interpIsSrc && !interpIsTarget) {
4332
0
            throw io::FormattingException(
4333
0
                "For"
4334
0
                " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN
4335
0
                ", interpolation CRS should be the source or target CRS");
4336
0
        }
4337
4338
0
        formatter->startInversion();
4339
0
        sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4340
0
        formatter->stopInversion();
4341
4342
0
        if (isMethodInverseOf) {
4343
0
            formatter->startInversion();
4344
0
        }
4345
4346
0
        formatter->addStep("push");
4347
0
        formatter->addParam("v_3");
4348
4349
0
        formatter->addStep("cart");
4350
0
        sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4351
4352
0
        formatter->addStep("xyzgridshift");
4353
0
        formatter->addParam("grids", geocentricTranslationFilename);
4354
0
        formatter->addParam("grid_ref",
4355
0
                            interpIsTarget ? "output_crs" : "input_crs");
4356
0
        (interpIsTarget ? targetCRSGeog : sourceCRSGeog)
4357
0
            ->ellipsoid()
4358
0
            ->_exportToPROJString(formatter);
4359
4360
0
        formatter->startInversion();
4361
0
        formatter->addStep("cart");
4362
0
        targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4363
0
        formatter->stopInversion();
4364
4365
0
        formatter->addStep("pop");
4366
0
        formatter->addParam("v_3");
4367
4368
0
        if (isMethodInverseOf) {
4369
0
            formatter->stopInversion();
4370
0
        }
4371
4372
0
        targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4373
4374
0
        return true;
4375
0
    }
4376
4377
0
    const auto &geographic3DOffsetByVelocityGridFilename =
4378
0
        _getGeographic3DOffsetByVelocityGridFilename(this, true);
4379
0
    if (!geographic3DOffsetByVelocityGridFilename.empty()) {
4380
0
        auto sourceCRSGeog =
4381
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
4382
0
        if (!sourceCRSGeog) {
4383
0
            throw io::FormattingException(
4384
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4385
0
        }
4386
4387
0
        const auto &srcEpoch =
4388
0
            sourceCRSGeog->datumNonNull(formatter->databaseContext())
4389
0
                ->anchorEpoch();
4390
0
        if (!srcEpoch.has_value()) {
4391
0
            throw io::FormattingException(
4392
0
                "For"
4393
0
                " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL
4394
0
                ", missing epoch for source CRS");
4395
0
        }
4396
4397
0
        auto targetCRSGeog =
4398
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
4399
0
        if (!targetCRSGeog) {
4400
0
            throw io::FormattingException(
4401
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4402
0
        }
4403
4404
0
        const auto &dstEpoch =
4405
0
            targetCRSGeog->datumNonNull(formatter->databaseContext())
4406
0
                ->anchorEpoch();
4407
0
        if (!dstEpoch.has_value()) {
4408
0
            throw io::FormattingException(
4409
0
                "For"
4410
0
                " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL
4411
0
                ", missing epoch for target CRS");
4412
0
        }
4413
4414
0
        const auto &interpCRS = interpolationCRS();
4415
0
        if (!interpCRS) {
4416
0
            throw io::FormattingException(
4417
0
                "InterpolationCRS required "
4418
0
                "for"
4419
0
                " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL);
4420
0
        }
4421
0
        const bool interpIsSrc = interpCRS->_isEquivalentTo(
4422
0
            sourceCRS()->demoteTo2D(std::string(), nullptr).get(),
4423
0
            util::IComparable::Criterion::EQUIVALENT);
4424
0
        const bool interpIsTarget = interpCRS->_isEquivalentTo(
4425
0
            targetCRS()->demoteTo2D(std::string(), nullptr).get(),
4426
0
            util::IComparable::Criterion::EQUIVALENT);
4427
0
        if (!interpIsSrc && !interpIsTarget) {
4428
0
            throw io::FormattingException(
4429
0
                "For"
4430
0
                " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL
4431
0
                ", interpolation CRS should be the source or target CRS");
4432
0
        }
4433
4434
0
        formatter->startInversion();
4435
0
        sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4436
0
        formatter->stopInversion();
4437
4438
0
        if (isMethodInverseOf) {
4439
0
            formatter->startInversion();
4440
0
        }
4441
4442
0
        const bool addPushPopV3 =
4443
0
            ((sourceCRSGeog &&
4444
0
              sourceCRSGeog->coordinateSystem()->axisList().size() == 2) ||
4445
0
             (targetCRSGeog &&
4446
0
              targetCRSGeog->coordinateSystem()->axisList().size() == 2));
4447
4448
0
        if (addPushPopV3) {
4449
0
            formatter->addStep("push");
4450
0
            formatter->addParam("v_3");
4451
0
        }
4452
4453
0
        formatter->addStep("cart");
4454
0
        sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4455
4456
0
        formatter->addStep("deformation");
4457
4458
0
        const double sourceYear =
4459
0
            srcEpoch->convertToUnit(common::UnitOfMeasure::YEAR);
4460
0
        const double targetYear =
4461
0
            dstEpoch->convertToUnit(common::UnitOfMeasure::YEAR);
4462
4463
0
        formatter->addParam("dt", targetYear - sourceYear);
4464
0
        formatter->addParam("grids", geographic3DOffsetByVelocityGridFilename);
4465
0
        (interpIsTarget ? targetCRSGeog : sourceCRSGeog)
4466
0
            ->ellipsoid()
4467
0
            ->_exportToPROJString(formatter);
4468
4469
0
        formatter->startInversion();
4470
0
        formatter->addStep("cart");
4471
0
        targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4472
0
        formatter->stopInversion();
4473
4474
0
        if (addPushPopV3) {
4475
0
            formatter->addStep("pop");
4476
0
            formatter->addParam("v_3");
4477
0
        }
4478
4479
0
        if (isMethodInverseOf) {
4480
0
            formatter->stopInversion();
4481
0
        }
4482
4483
0
        targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4484
4485
0
        return true;
4486
0
    }
4487
4488
0
    const auto &verticalOffsetByVelocityGridFilename =
4489
0
        _getVerticalOffsetByVelocityGridFilename(this, true);
4490
0
    if (!verticalOffsetByVelocityGridFilename.empty()) {
4491
4492
0
        const auto &interpCRS = interpolationCRS();
4493
0
        if (!interpCRS) {
4494
0
            throw io::FormattingException(
4495
0
                "InterpolationCRS required "
4496
0
                "for"
4497
0
                " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL);
4498
0
        }
4499
4500
0
        auto interpCRSGeog =
4501
0
            dynamic_cast<const crs::GeographicCRS *>(interpCRS.get());
4502
0
        if (!interpCRSGeog) {
4503
0
            throw io::FormattingException(
4504
0
                concat("Can apply ", methodName,
4505
0
                       " only to a GeographicCRS interpolation CRS"));
4506
0
        }
4507
4508
0
        const auto vertSrc =
4509
0
            dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get());
4510
0
        if (!vertSrc) {
4511
0
            throw io::FormattingException(concat(
4512
0
                "Can apply ", methodName, " only to a source VerticalCRS"));
4513
0
        }
4514
4515
0
        const auto &srcEpoch =
4516
0
            vertSrc->datumNonNull(formatter->databaseContext())->anchorEpoch();
4517
0
        if (!srcEpoch.has_value()) {
4518
0
            throw io::FormattingException(
4519
0
                "For"
4520
0
                " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL
4521
0
                ", missing epoch for source CRS");
4522
0
        }
4523
4524
0
        const auto vertDst =
4525
0
            dynamic_cast<const crs::VerticalCRS *>(targetCRS().get());
4526
0
        if (!vertDst) {
4527
0
            throw io::FormattingException(concat(
4528
0
                "Can apply ", methodName, " only to a target VerticalCRS"));
4529
0
        }
4530
4531
0
        const auto &dstEpoch =
4532
0
            vertDst->datumNonNull(formatter->databaseContext())->anchorEpoch();
4533
0
        if (!dstEpoch.has_value()) {
4534
0
            throw io::FormattingException(
4535
0
                "For"
4536
0
                " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL
4537
0
                ", missing epoch for target CRS");
4538
0
        }
4539
4540
0
        const double sourceYear =
4541
0
            srcEpoch->convertToUnit(common::UnitOfMeasure::YEAR);
4542
0
        const double targetYear =
4543
0
            dstEpoch->convertToUnit(common::UnitOfMeasure::YEAR);
4544
4545
0
        if (isMethodInverseOf) {
4546
0
            formatter->startInversion();
4547
0
        }
4548
0
        formatter->addStep("push");
4549
0
        formatter->addParam("v_1");
4550
0
        formatter->addParam("v_2");
4551
4552
0
        formatter->addStep("cart");
4553
0
        interpCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4554
4555
0
        formatter->addStep("deformation");
4556
0
        formatter->addParam("dt", targetYear - sourceYear);
4557
0
        formatter->addParam("grids", verticalOffsetByVelocityGridFilename);
4558
0
        interpCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4559
4560
0
        formatter->startInversion();
4561
0
        formatter->addStep("cart");
4562
0
        interpCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4563
0
        formatter->stopInversion();
4564
4565
0
        formatter->addStep("pop");
4566
0
        formatter->addParam("v_1");
4567
0
        formatter->addParam("v_2");
4568
4569
0
        if (isMethodInverseOf) {
4570
0
            formatter->stopInversion();
4571
0
        }
4572
4573
0
        return true;
4574
0
    }
4575
4576
0
    const auto &heightFilename = _getHeightToGeographic3DFilename(this, true);
4577
0
    if (!heightFilename.empty()) {
4578
0
        auto l_targetCRS = targetCRS();
4579
0
        auto targetCRSGeog =
4580
0
            l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent(
4581
0
                              NN_NO_CHECK(l_targetCRS))
4582
0
                        : nullptr;
4583
0
        if (!targetCRSGeog) {
4584
0
            throw io::FormattingException(
4585
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4586
0
        }
4587
4588
0
        if (!formatter->omitHorizontalConversionInVertTransformation()) {
4589
0
            formatter->startInversion();
4590
0
            formatter->pushOmitZUnitConversion();
4591
0
            targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4592
0
            formatter->popOmitZUnitConversion();
4593
0
            formatter->stopInversion();
4594
0
        }
4595
4596
0
        if (isMethodInverseOf) {
4597
0
            formatter->startInversion();
4598
0
        }
4599
0
        formatter->addStep("vgridshift");
4600
0
        formatter->addParam("grids", heightFilename);
4601
0
        formatter->addParam("multiplier", 1.0);
4602
0
        if (isMethodInverseOf) {
4603
0
            formatter->stopInversion();
4604
0
        }
4605
4606
0
        if (!formatter->omitHorizontalConversionInVertTransformation()) {
4607
0
            formatter->pushOmitZUnitConversion();
4608
0
            targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4609
0
            formatter->popOmitZUnitConversion();
4610
0
        }
4611
4612
0
        return true;
4613
0
    }
4614
4615
0
    if (Transformation::isGeographic3DToGravityRelatedHeight(method(), true)) {
4616
0
        auto fileParameter =
4617
0
            parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
4618
0
                           EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
4619
0
        if (fileParameter &&
4620
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
4621
0
            const auto &filename = fileParameter->valueFile();
4622
4623
0
            auto l_sourceCRS = sourceCRS();
4624
0
            auto sourceCRSGeog =
4625
0
                l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent(
4626
0
                                  NN_NO_CHECK(l_sourceCRS))
4627
0
                            : nullptr;
4628
0
            if (!sourceCRSGeog) {
4629
0
                throw io::FormattingException(
4630
0
                    concat("Can apply ", methodName, " only to GeographicCRS"));
4631
0
            }
4632
4633
0
            auto l_targetCRS = targetCRS();
4634
0
            auto targetVertCRS =
4635
0
                l_targetCRS ? l_targetCRS->extractVerticalCRS() : nullptr;
4636
0
            if (!targetVertCRS) {
4637
0
                throw io::FormattingException(
4638
0
                    concat("Can apply ", methodName,
4639
0
                           " only to a target CRS that has a VerticalCRS"));
4640
0
            }
4641
4642
0
            if (!formatter->omitHorizontalConversionInVertTransformation()) {
4643
0
                formatter->startInversion();
4644
0
                formatter->pushOmitZUnitConversion();
4645
0
                sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4646
0
                formatter->popOmitZUnitConversion();
4647
0
                formatter->stopInversion();
4648
0
            }
4649
4650
0
            bool doInversion = isMethodInverseOf;
4651
            // The EPSG Geog3DToHeight is the reverse convention of PROJ !
4652
0
            doInversion = !doInversion;
4653
0
            if (doInversion) {
4654
0
                formatter->startInversion();
4655
0
            }
4656
4657
            // For Geographic3D to Depth methods, we rely on the vertical axis
4658
            // direction instead of the name/code of the transformation method.
4659
0
            if (targetVertCRS->coordinateSystem()->axisList()[0]->direction() ==
4660
0
                cs::AxisDirection::DOWN) {
4661
0
                formatter->addStep("axisswap");
4662
0
                formatter->addParam("order", "1,2,-3");
4663
0
            }
4664
4665
0
            formatter->addStep("vgridshift");
4666
0
            formatter->addParam("grids", filename);
4667
0
            formatter->addParam("multiplier", 1.0);
4668
0
            if (doInversion) {
4669
0
                formatter->stopInversion();
4670
0
            }
4671
4672
0
            if (!formatter->omitHorizontalConversionInVertTransformation()) {
4673
0
                formatter->pushOmitZUnitConversion();
4674
0
                sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4675
0
                formatter->popOmitZUnitConversion();
4676
0
            }
4677
4678
0
            return true;
4679
0
        }
4680
0
    }
4681
4682
0
    if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) {
4683
0
        auto fileParameter =
4684
0
            parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
4685
0
                           EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
4686
0
        if (fileParameter &&
4687
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
4688
0
            formatter->addStep("vgridshift");
4689
0
            formatter->addParam("grids", fileParameter->valueFile());
4690
0
            if (fileParameter->valueFile().find(".tif") != std::string::npos) {
4691
0
                formatter->addParam("multiplier", 1.0);
4692
0
            } else {
4693
                // The vertcon grids go from NGVD 29 to NAVD 88, with units
4694
                // in millimeter (see
4695
                // https://github.com/OSGeo/proj.4/issues/1071), for gtx files
4696
0
                formatter->addParam("multiplier", 0.001);
4697
0
            }
4698
0
            return true;
4699
0
        }
4700
0
    }
4701
4702
0
    bool reverseOffsetSign = false;
4703
0
    if (isRegularVerticalGridMethod(methodEPSGCode, reverseOffsetSign)) {
4704
0
        int parameterCode = EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE;
4705
0
        auto fileParameter = parameterValue(
4706
0
            EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, parameterCode);
4707
0
        if (!fileParameter) {
4708
0
            parameterCode = EPSG_CODE_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE;
4709
0
            fileParameter = parameterValue(
4710
0
                EPSG_NAME_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE, parameterCode);
4711
0
        }
4712
0
        if (fileParameter &&
4713
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
4714
0
            formatter->addStep("vgridshift");
4715
0
            formatter->addParam("grids", fileParameter->valueFile());
4716
0
            formatter->addParam("multiplier", reverseOffsetSign ? -1.0 : 1.0);
4717
0
            return true;
4718
0
        }
4719
0
    }
4720
4721
0
    if (isLongitudeRotation()) {
4722
0
        double offsetDeg =
4723
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
4724
0
                                  common::UnitOfMeasure::DEGREE);
4725
0
        auto l_sourceCRS = sourceCRS();
4726
0
        auto sourceCRSGeog =
4727
0
            l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent(
4728
0
                              NN_NO_CHECK(l_sourceCRS))
4729
0
                        : nullptr;
4730
0
        if (!sourceCRSGeog) {
4731
0
            throw io::FormattingException(
4732
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4733
0
        }
4734
4735
0
        auto l_targetCRS = targetCRS();
4736
0
        auto targetCRSGeog =
4737
0
            l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent(
4738
0
                              NN_NO_CHECK(l_targetCRS))
4739
0
                        : nullptr;
4740
0
        if (!targetCRSGeog) {
4741
0
            throw io::FormattingException(
4742
0
                concat("Can apply ", methodName + " only to GeographicCRS"));
4743
0
        }
4744
4745
0
        if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo(
4746
0
                targetCRSGeog->ellipsoid().get(),
4747
0
                util::IComparable::Criterion::EQUIVALENT)) {
4748
            // This is arguable if we should check this...
4749
0
            throw io::FormattingException("Can apply Longitude rotation "
4750
0
                                          "only to SRS with same "
4751
0
                                          "ellipsoid");
4752
0
        }
4753
4754
0
        formatter->startInversion();
4755
0
        sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4756
0
        formatter->stopInversion();
4757
4758
0
        bool done = false;
4759
0
        if (offsetDeg != 0.0) {
4760
            // Optimization: as we are doing nominally a +step=inv,
4761
            // if the negation of the offset value is a well-known name,
4762
            // then use forward case with this name.
4763
0
            auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName(
4764
0
                common::Angle(-offsetDeg));
4765
0
            if (!projPMName.empty()) {
4766
0
                done = true;
4767
0
                formatter->addStep("longlat");
4768
0
                sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4769
0
                formatter->addParam("pm", projPMName);
4770
0
            }
4771
0
        }
4772
0
        if (!done) {
4773
            // To actually add the offset, we must use the reverse longlat
4774
            // operation.
4775
0
            formatter->startInversion();
4776
0
            formatter->addStep("longlat");
4777
0
            sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
4778
0
            datum::PrimeMeridian::create(util::PropertyMap(),
4779
0
                                         common::Angle(offsetDeg))
4780
0
                ->_exportToPROJString(formatter);
4781
0
            formatter->stopInversion();
4782
0
        }
4783
4784
0
        targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4785
4786
0
        return true;
4787
0
    }
4788
4789
0
    if (methodEPSGCode == EPSG_CODE_METHOD_NEW_ZEALAND_DEFORMATION_MODEL) {
4790
0
        auto sourceCRSGeog =
4791
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
4792
0
        if (!sourceCRSGeog) {
4793
0
            throw io::FormattingException(
4794
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4795
0
        }
4796
4797
0
        auto targetCRSGeog =
4798
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
4799
0
        if (!targetCRSGeog) {
4800
0
            throw io::FormattingException(
4801
0
                concat("Can apply ", methodName, " only to GeographicCRS"));
4802
0
        }
4803
4804
0
        auto fileParameter =
4805
0
            parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
4806
0
                           EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE);
4807
0
        if (fileParameter &&
4808
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
4809
4810
0
            formatter->startInversion();
4811
0
            sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4812
0
            formatter->stopInversion();
4813
4814
0
            double sourceYear =
4815
0
                sourceCoordinateEpoch().has_value()
4816
0
                    ? getRoundedEpochInDecimalYear(
4817
0
                          sourceCoordinateEpoch()
4818
0
                              ->coordinateEpoch()
4819
0
                              .convertToUnit(common::UnitOfMeasure::YEAR))
4820
0
                    : 0;
4821
0
            double targetYear =
4822
0
                targetCoordinateEpoch().has_value()
4823
0
                    ? getRoundedEpochInDecimalYear(
4824
0
                          targetCoordinateEpoch()
4825
0
                              ->coordinateEpoch()
4826
0
                              .convertToUnit(common::UnitOfMeasure::YEAR))
4827
0
                    : 0;
4828
0
            if (sourceYear > 0 && targetYear == 0)
4829
0
                targetYear = sourceYear;
4830
0
            else if (targetYear > 0 && sourceYear == 0)
4831
0
                sourceYear = targetYear;
4832
0
            if (sourceYear > 0) {
4833
0
                formatter->addStep("set");
4834
0
                formatter->addParam("v_4", sourceYear);
4835
0
            }
4836
4837
0
            if (isMethodInverseOf) {
4838
0
                formatter->startInversion();
4839
0
            }
4840
4841
            // Operations are registered in EPSG with inverse order as
4842
            // the +proj=defmodel implementation
4843
0
            formatter->startInversion();
4844
0
            formatter->addStep("defmodel");
4845
0
            formatter->addParam("model", fileParameter->valueFile());
4846
0
            formatter->stopInversion();
4847
4848
0
            if (isMethodInverseOf) {
4849
0
                formatter->stopInversion();
4850
0
            }
4851
4852
0
            if (targetYear > 0) {
4853
0
                formatter->addStep("set");
4854
0
                formatter->addParam("v_4", targetYear);
4855
0
            }
4856
4857
0
            targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
4858
4859
0
            return true;
4860
0
        }
4861
0
    }
4862
4863
0
    if (methodEPSGCode ==
4864
0
        EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON) {
4865
0
        auto sourceCRSProj =
4866
0
            dynamic_cast<const crs::ProjectedCRS *>(sourceCRS().get());
4867
0
        if (!sourceCRSProj) {
4868
0
            throw io::FormattingException(
4869
0
                concat("Can apply ", methodName, " only to ProjectedCRS"));
4870
0
        }
4871
4872
0
        auto targetCRSProj =
4873
0
            dynamic_cast<const crs::ProjectedCRS *>(targetCRS().get());
4874
0
        if (!targetCRSProj) {
4875
0
            throw io::FormattingException(
4876
0
                concat("Can apply ", methodName, " only to ProjectedCRS"));
4877
0
        }
4878
4879
0
        auto fileParameter =
4880
0
            parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE,
4881
0
                           EPSG_CODE_PARAMETER_TIN_OFFSET_FILE);
4882
0
        if (fileParameter &&
4883
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
4884
4885
0
            formatter->startInversion();
4886
0
            sourceCRSProj->addUnitConvertAndAxisSwap(formatter, false);
4887
0
            formatter->stopInversion();
4888
4889
0
            if (isMethodInverseOf) {
4890
0
                formatter->startInversion();
4891
0
            }
4892
4893
0
            formatter->addStep("tinshift");
4894
0
            formatter->addParam("file", fileParameter->valueFile());
4895
4896
0
            if (isMethodInverseOf) {
4897
0
                formatter->stopInversion();
4898
0
            }
4899
4900
0
            targetCRSProj->addUnitConvertAndAxisSwap(formatter, false);
4901
4902
0
            return true;
4903
0
        }
4904
0
    }
4905
4906
0
    if (methodEPSGCode ==
4907
0
        EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON) {
4908
0
        auto sourceCRSGeog =
4909
0
            dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
4910
0
        if (!sourceCRSGeog) {
4911
0
            throw io::FormattingException(concat(
4912
0
                "Can apply ", methodName, " only to source GeographicCRS"));
4913
0
        }
4914
4915
0
        const auto hasDegreeUnit = [](const crs::GeographicCRS *crs) {
4916
0
            const auto &axisList = crs->coordinateSystem()->axisList();
4917
0
            return axisList[0]->unit() == common::UnitOfMeasure::DEGREE;
4918
0
        };
4919
4920
0
        if (!hasDegreeUnit(sourceCRSGeog)) {
4921
0
            throw io::FormattingException(
4922
0
                concat("Can apply ", methodName,
4923
0
                       " only to source GeographicCRS with degree axis unit"));
4924
0
        }
4925
4926
0
        auto targetCRSGeog =
4927
0
            dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
4928
0
        if (!targetCRSGeog) {
4929
0
            throw io::FormattingException(concat(
4930
0
                "Can apply ", methodName, " only to target GeographicCRS"));
4931
0
        }
4932
4933
0
        if (!hasDegreeUnit(targetCRSGeog)) {
4934
0
            throw io::FormattingException(
4935
0
                concat("Can apply ", methodName,
4936
0
                       " only to target GeographicCRS with degree axis unit"));
4937
0
        }
4938
4939
0
        auto fileParameter =
4940
0
            parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE,
4941
0
                           EPSG_CODE_PARAMETER_TIN_OFFSET_FILE);
4942
0
        if (fileParameter &&
4943
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
4944
4945
0
            formatter->startInversion();
4946
0
            sourceCRSGeog->addAxisSwap(formatter);
4947
0
            formatter->stopInversion();
4948
4949
0
            if (isMethodInverseOf) {
4950
0
                formatter->startInversion();
4951
0
            }
4952
4953
0
            formatter->addStep("tinshift");
4954
0
            formatter->addParam("file", fileParameter->valueFile());
4955
4956
0
            if (isMethodInverseOf) {
4957
0
                formatter->stopInversion();
4958
0
            }
4959
4960
0
            targetCRSGeog->addAxisSwap(formatter);
4961
4962
0
            return true;
4963
0
        }
4964
0
    }
4965
4966
0
    if (methodEPSGCode ==
4967
0
        EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON) {
4968
0
        auto sourceCRSVert =
4969
0
            dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get());
4970
0
        if (!sourceCRSVert) {
4971
0
            throw io::FormattingException(
4972
0
                concat("Can apply ", methodName, " only to VerticalCRS"));
4973
0
        }
4974
4975
0
        auto targetCRSVert =
4976
0
            dynamic_cast<const crs::VerticalCRS *>(targetCRS().get());
4977
0
        if (!targetCRSVert) {
4978
0
            throw io::FormattingException(
4979
0
                concat("Can apply ", methodName, " only to VerticalCRS"));
4980
0
        }
4981
4982
0
        auto fileParameter =
4983
0
            parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE,
4984
0
                           EPSG_CODE_PARAMETER_TIN_OFFSET_FILE);
4985
4986
0
        if (fileParameter &&
4987
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
4988
4989
0
            if (isMethodInverseOf) {
4990
0
                formatter->startInversion();
4991
0
            }
4992
4993
0
            formatter->addStep("tinshift");
4994
0
            formatter->addParam("file", fileParameter->valueFile());
4995
4996
0
            if (isMethodInverseOf) {
4997
0
                formatter->stopInversion();
4998
0
            }
4999
5000
0
            return true;
5001
0
        }
5002
0
    }
5003
5004
0
    if (methodEPSGCode ==
5005
0
        EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) {
5006
0
        auto l_sourceCRS = sourceCRS();
5007
0
        auto l_targetCRS = targetCRS();
5008
0
        auto sourceCRSGeod =
5009
0
            dynamic_cast<const crs::GeodeticCRS *>(l_sourceCRS.get());
5010
0
        auto targetCRSGeod =
5011
0
            dynamic_cast<const crs::GeodeticCRS *>(l_targetCRS.get());
5012
0
        if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) {
5013
0
            throw io::FormattingException(
5014
0
                concat("Can apply ", methodName, " only to a geocentric CRS"));
5015
0
        }
5016
0
        if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) {
5017
0
            throw io::FormattingException(
5018
0
                concat("Can apply ", methodName, " only to a geocentric CRS"));
5019
0
        }
5020
5021
0
        auto fileParameter =
5022
0
            parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
5023
0
                           EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE);
5024
0
        if (!(fileParameter &&
5025
0
              fileParameter->type() == ParameterValue::Type::FILENAME)) {
5026
0
            throw io::FormattingException(
5027
0
                "Missing parameter Point motion velocity grid file");
5028
0
        }
5029
5030
0
        if (isMethodInverseOf) {
5031
0
            formatter->startInversion();
5032
0
        }
5033
5034
0
        if (l_sourceCRS) {
5035
0
            setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS),
5036
0
                                       false, "Helmert");
5037
0
        }
5038
5039
0
        formatter->addStep("helmert");
5040
0
        double x =
5041
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
5042
0
        double y =
5043
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
5044
0
        double z =
5045
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
5046
5047
0
        formatter->addParam("x", x);
5048
0
        formatter->addParam("y", y);
5049
0
        formatter->addParam("z", z);
5050
5051
0
        double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
5052
0
                                          common::UnitOfMeasure::ARC_SECOND);
5053
0
        double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
5054
0
                                          common::UnitOfMeasure::ARC_SECOND);
5055
0
        double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
5056
0
                                          common::UnitOfMeasure::ARC_SECOND);
5057
0
        double scaleDiff =
5058
0
            parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
5059
0
                                  common::UnitOfMeasure::PARTS_PER_MILLION);
5060
0
        formatter->addParam("rx", rx);
5061
0
        formatter->addParam("ry", ry);
5062
0
        formatter->addParam("rz", rz);
5063
0
        formatter->addParam("s", scaleDiff);
5064
0
        formatter->addParam("convention", "position_vector");
5065
5066
0
        formatter->addStep("deformation");
5067
5068
0
        const bool hasTargetEpoch = [this]() {
5069
0
            const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH);
5070
0
            return val && val->type() == ParameterValue::Type::MEASURE;
5071
0
        }();
5072
0
        if (hasTargetEpoch) {
5073
0
            const double targetYear = parameterValueNumeric(
5074
0
                EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR);
5075
0
            const bool hasSourceEpoch = [this]() {
5076
0
                const auto &val =
5077
0
                    parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH);
5078
0
                return val && val->type() == ParameterValue::Type::MEASURE;
5079
0
            }();
5080
0
            if (hasSourceEpoch) {
5081
0
                const double sourceYear =
5082
0
                    parameterValueNumeric(EPSG_CODE_PARAMETER_SOURCE_EPOCH,
5083
0
                                          common::UnitOfMeasure::YEAR);
5084
0
                formatter->addParam("dt", targetYear - sourceYear);
5085
0
            } else {
5086
0
                formatter->addParam("t_epoch", targetYear);
5087
0
            }
5088
0
        } else {
5089
0
            throw io::FormattingException("Missing parameter target epoch");
5090
0
        }
5091
5092
0
        formatter->addParam("grids", fileParameter->valueFile());
5093
0
        sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter);
5094
5095
0
        if (l_targetCRS) {
5096
0
            setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS),
5097
0
                                       false, "Helmert");
5098
0
        }
5099
5100
0
        if (isMethodInverseOf) {
5101
0
            formatter->stopInversion();
5102
0
        }
5103
5104
0
        return true;
5105
0
    }
5106
5107
0
    if (methodEPSGCode ==
5108
0
        EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG) {
5109
0
        auto l_sourceCRS = sourceCRS();
5110
0
        auto l_targetCRS = targetCRS();
5111
0
        auto sourceCRSGeod =
5112
0
            dynamic_cast<const crs::GeodeticCRS *>(l_sourceCRS.get());
5113
0
        auto targetCRSGeod =
5114
0
            dynamic_cast<const crs::GeodeticCRS *>(l_targetCRS.get());
5115
0
        if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) {
5116
0
            throw io::FormattingException(
5117
0
                concat("Can apply ", methodName, " only to a geocentric CRS"));
5118
0
        }
5119
0
        if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) {
5120
0
            throw io::FormattingException(
5121
0
                concat("Can apply ", methodName, " only to a geocentric CRS"));
5122
0
        }
5123
5124
0
        auto fileParameter =
5125
0
            parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
5126
0
                           EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE);
5127
0
        if (!(fileParameter &&
5128
0
              fileParameter->type() == ParameterValue::Type::FILENAME)) {
5129
0
            throw io::FormattingException(
5130
0
                "Missing parameter Point motion velocity grid file");
5131
0
        }
5132
5133
0
        if (isMethodInverseOf) {
5134
0
            formatter->startInversion();
5135
0
        }
5136
5137
0
        if (l_sourceCRS) {
5138
0
            setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS),
5139
0
                                       false, "Helmert");
5140
0
        }
5141
5142
0
        const bool hasSourceEpoch = [this]() {
5143
0
            const auto &val = parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH);
5144
0
            return val && val->type() == ParameterValue::Type::MEASURE;
5145
0
        }();
5146
0
        if (hasSourceEpoch) {
5147
0
            throw io::FormattingException("Unsupported parameter source epoch");
5148
0
        }
5149
5150
0
        const bool hasTargetEpoch = [this]() {
5151
0
            const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH);
5152
0
            return val && val->type() == ParameterValue::Type::MEASURE;
5153
0
        }();
5154
0
        if (!hasTargetEpoch) {
5155
0
            throw io::FormattingException("Missing parameter target epoch");
5156
0
        }
5157
5158
0
        formatter->startInversion();
5159
0
        formatter->addStep("deformation");
5160
5161
0
        const double targetYear = parameterValueNumeric(
5162
0
            EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR);
5163
0
        formatter->addParam("t_epoch", targetYear);
5164
5165
0
        formatter->addParam("grids", fileParameter->valueFile());
5166
0
        sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter);
5167
0
        formatter->stopInversion();
5168
5169
0
        if (l_targetCRS) {
5170
0
            setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS),
5171
0
                                       false, "Helmert");
5172
0
        }
5173
5174
0
        if (isMethodInverseOf) {
5175
0
            formatter->stopInversion();
5176
0
        }
5177
5178
0
        return true;
5179
0
    }
5180
5181
0
    if (methodEPSGCode ==
5182
0
        EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) {
5183
0
        auto l_sourceCRS = sourceCRS();
5184
0
        auto l_targetCRS = targetCRS();
5185
0
        auto sourceCRSGeod =
5186
0
            dynamic_cast<const crs::GeodeticCRS *>(l_sourceCRS.get());
5187
0
        auto targetCRSGeod =
5188
0
            dynamic_cast<const crs::GeodeticCRS *>(l_targetCRS.get());
5189
0
        if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) {
5190
0
            throw io::FormattingException(
5191
0
                concat("Can apply ", methodName, " only to a geocentric CRS"));
5192
0
        }
5193
0
        if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) {
5194
0
            throw io::FormattingException(
5195
0
                concat("Can apply ", methodName, " only to a geocentric CRS"));
5196
0
        }
5197
5198
0
        auto fileParameterGTF =
5199
0
            parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE,
5200
0
                           EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE);
5201
0
        if (!(fileParameterGTF &&
5202
0
              fileParameterGTF->type() == ParameterValue::Type::FILENAME)) {
5203
0
            throw io::FormattingException(
5204
0
                "Missing parameter Geocentric translation file");
5205
0
        }
5206
5207
0
        auto fileParameterPMV =
5208
0
            parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE,
5209
0
                           EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE);
5210
0
        if (!(fileParameterPMV &&
5211
0
              fileParameterPMV->type() == ParameterValue::Type::FILENAME)) {
5212
0
            throw io::FormattingException(
5213
0
                "Missing parameter Point motion velocity grid file");
5214
0
        }
5215
5216
0
        if (isMethodInverseOf) {
5217
0
            formatter->startInversion();
5218
0
        }
5219
5220
0
        if (l_sourceCRS) {
5221
0
            setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS),
5222
0
                                       false, "Helmert");
5223
0
        }
5224
5225
0
        const bool hasSourceEpoch = [this]() {
5226
0
            const auto &val = parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH);
5227
0
            return val && val->type() == ParameterValue::Type::MEASURE;
5228
0
        }();
5229
0
        if (!hasSourceEpoch) {
5230
0
            throw io::FormattingException("Missing parameter source epoch");
5231
0
        }
5232
5233
0
        const bool hasTargetEpoch = [this]() {
5234
0
            const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH);
5235
0
            return val && val->type() == ParameterValue::Type::MEASURE;
5236
0
        }();
5237
0
        if (!hasTargetEpoch) {
5238
0
            throw io::FormattingException("Missing parameter target epoch");
5239
0
        }
5240
5241
0
        formatter->addStep("xyzgridshift");
5242
0
        formatter->addParam("grids", fileParameterGTF->valueFile());
5243
5244
0
        formatter->addStep("deformation");
5245
5246
0
        const double sourceYear = parameterValueNumeric(
5247
0
            EPSG_CODE_PARAMETER_SOURCE_EPOCH, common::UnitOfMeasure::YEAR);
5248
0
        const double targetYear = parameterValueNumeric(
5249
0
            EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR);
5250
0
        formatter->addParam("dt", targetYear - sourceYear);
5251
5252
0
        formatter->addParam("grids", fileParameterPMV->valueFile());
5253
0
        sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter);
5254
5255
0
        if (l_targetCRS) {
5256
0
            setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS),
5257
0
                                       false, "Helmert");
5258
0
        }
5259
5260
0
        if (isMethodInverseOf) {
5261
0
            formatter->stopInversion();
5262
0
        }
5263
5264
0
        return true;
5265
0
    }
5266
5267
0
    const char *prefix = "PROJ-based operation method: ";
5268
0
    if (starts_with(method()->nameStr(), prefix)) {
5269
0
        auto projString = method()->nameStr().substr(strlen(prefix));
5270
0
        try {
5271
0
            formatter->ingestPROJString(projString);
5272
0
            return true;
5273
0
        } catch (const io::ParsingException &e) {
5274
0
            throw io::FormattingException(
5275
0
                std::string("ingestPROJString() failed: ") + e.what());
5276
0
        }
5277
0
    }
5278
5279
0
    return false;
5280
0
}
5281
5282
// ---------------------------------------------------------------------------
5283
5284
//! @cond Doxygen_Suppress
5285
5286
0
InverseCoordinateOperation::~InverseCoordinateOperation() = default;
5287
5288
// ---------------------------------------------------------------------------
5289
5290
InverseCoordinateOperation::InverseCoordinateOperation(
5291
    const CoordinateOperationNNPtr &forwardOperationIn,
5292
    bool wktSupportsInversion)
5293
0
    : forwardOperation_(forwardOperationIn),
5294
0
      wktSupportsInversion_(wktSupportsInversion) {}
5295
5296
// ---------------------------------------------------------------------------
5297
5298
0
void InverseCoordinateOperation::setPropertiesFromForward() {
5299
0
    setProperties(
5300
0
        createPropertiesForInverse(forwardOperation_.get(), false, false));
5301
0
    setAccuracies(forwardOperation_->coordinateOperationAccuracies());
5302
0
    if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) {
5303
0
        setCRSs(forwardOperation_.get(), true);
5304
0
    }
5305
0
    setHasBallparkTransformation(
5306
0
        forwardOperation_->hasBallparkTransformation());
5307
0
    setRequiresPerCoordinateInputTime(
5308
0
        forwardOperation_->requiresPerCoordinateInputTime());
5309
0
    if (auto sourceEpoch = forwardOperation_->sourceCoordinateEpoch()) {
5310
0
        setTargetCoordinateEpoch(sourceEpoch);
5311
0
    }
5312
0
    if (auto targetEpoch = forwardOperation_->targetCoordinateEpoch()) {
5313
0
        setSourceCoordinateEpoch(targetEpoch);
5314
0
    }
5315
0
}
5316
5317
// ---------------------------------------------------------------------------
5318
5319
0
CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const {
5320
0
    return forwardOperation_;
5321
0
}
5322
5323
// ---------------------------------------------------------------------------
5324
5325
void InverseCoordinateOperation::_exportToPROJString(
5326
0
    io::PROJStringFormatter *formatter) const {
5327
0
    formatter->startInversion();
5328
0
    forwardOperation_->_exportToPROJString(formatter);
5329
0
    formatter->stopInversion();
5330
0
}
5331
5332
// ---------------------------------------------------------------------------
5333
5334
bool InverseCoordinateOperation::_isEquivalentTo(
5335
    const util::IComparable *other, util::IComparable::Criterion criterion,
5336
0
    const io::DatabaseContextPtr &dbContext) const {
5337
0
    auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other);
5338
0
    if (otherICO == nullptr ||
5339
0
        !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) {
5340
0
        return false;
5341
0
    }
5342
0
    return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion,
5343
0
                                      dbContext);
5344
0
}
5345
5346
//! @endcond
5347
5348
// ---------------------------------------------------------------------------
5349
5350
//! @cond Doxygen_Suppress
5351
0
PointMotionOperation::~PointMotionOperation() = default;
5352
//! @endcond
5353
5354
// ---------------------------------------------------------------------------
5355
5356
/** \brief Instantiate a point motion operation from a vector of
5357
 * GeneralParameterValue.
5358
 *
5359
 * @param properties See \ref general_properties. At minimum the name should be
5360
 * defined.
5361
 * @param crsIn Source and target CRS.
5362
 * @param methodIn Operation method.
5363
 * @param values Vector of GeneralOperationParameterNNPtr.
5364
 * @param accuracies Vector of positional accuracy (might be empty).
5365
 * @return new PointMotionOperation.
5366
 * @throws InvalidOperation if the object cannot be constructed.
5367
 */
5368
PointMotionOperationNNPtr PointMotionOperation::create(
5369
    const util::PropertyMap &properties, const crs::CRSNNPtr &crsIn,
5370
    const OperationMethodNNPtr &methodIn,
5371
    const std::vector<GeneralParameterValueNNPtr> &values,
5372
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
5373
0
    if (methodIn->parameters().size() != values.size()) {
5374
0
        throw InvalidOperation(
5375
0
            "Inconsistent number of parameters and parameter values");
5376
0
    }
5377
0
    auto pmo = PointMotionOperation::nn_make_shared<PointMotionOperation>(
5378
0
        crsIn, methodIn, values, accuracies);
5379
0
    pmo->assignSelf(pmo);
5380
0
    pmo->setProperties(properties);
5381
5382
0
    const std::string l_name = pmo->nameStr();
5383
0
    auto pos = l_name.find(" from epoch ");
5384
0
    if (pos != std::string::npos) {
5385
0
        pos += strlen(" from epoch ");
5386
0
        const auto pos2 = l_name.find(" to epoch ", pos);
5387
0
        if (pos2 != std::string::npos) {
5388
0
            const double sourceYear = std::stod(l_name.substr(pos, pos2 - pos));
5389
0
            const double targetYear =
5390
0
                std::stod(l_name.substr(pos2 + strlen(" to epoch ")));
5391
0
            pmo->setSourceCoordinateEpoch(
5392
0
                util::optional<common::DataEpoch>(common::DataEpoch(
5393
0
                    common::Measure(sourceYear, common::UnitOfMeasure::YEAR))));
5394
0
            pmo->setTargetCoordinateEpoch(
5395
0
                util::optional<common::DataEpoch>(common::DataEpoch(
5396
0
                    common::Measure(targetYear, common::UnitOfMeasure::YEAR))));
5397
0
        }
5398
0
    }
5399
5400
0
    return pmo;
5401
0
}
5402
5403
// ---------------------------------------------------------------------------
5404
5405
/** \brief Instantiate a point motion operation and its OperationMethod.
5406
 *
5407
 * @param propertiesOperation The \ref general_properties of the
5408
 * PointMotionOperation.
5409
 * At minimum the name should be defined.
5410
 * @param crsIn Source and target CRS.
5411
 * @param propertiesOperationMethod The \ref general_properties of the
5412
 * OperationMethod.
5413
 * At minimum the name should be defined.
5414
 * @param parameters Vector of parameters of the operation method.
5415
 * @param values Vector of ParameterValueNNPtr. Constraint:
5416
 * values.size() == parameters.size()
5417
 * @param accuracies Vector of positional accuracy (might be empty).
5418
 * @return new PointMotionOperation.
5419
 * @throws InvalidOperation if the object cannot be constructed.
5420
 */
5421
PointMotionOperationNNPtr PointMotionOperation::create(
5422
    const util::PropertyMap &propertiesOperation, const crs::CRSNNPtr &crsIn,
5423
    const util::PropertyMap &propertiesOperationMethod,
5424
    const std::vector<OperationParameterNNPtr> &parameters,
5425
    const std::vector<ParameterValueNNPtr> &values,
5426
    const std::vector<metadata::PositionalAccuracyNNPtr>
5427
        &accuracies) // throw InvalidOperation
5428
0
{
5429
0
    OperationMethodNNPtr op(
5430
0
        OperationMethod::create(propertiesOperationMethod, parameters));
5431
5432
0
    if (parameters.size() != values.size()) {
5433
0
        throw InvalidOperation(
5434
0
            "Inconsistent number of parameters and parameter values");
5435
0
    }
5436
0
    std::vector<GeneralParameterValueNNPtr> generalParameterValues;
5437
0
    generalParameterValues.reserve(values.size());
5438
0
    for (size_t i = 0; i < values.size(); i++) {
5439
0
        generalParameterValues.push_back(
5440
0
            OperationParameterValue::create(parameters[i], values[i]));
5441
0
    }
5442
0
    return create(propertiesOperation, crsIn, op, generalParameterValues,
5443
0
                  accuracies);
5444
0
}
5445
5446
// ---------------------------------------------------------------------------
5447
5448
PointMotionOperation::PointMotionOperation(
5449
    const crs::CRSNNPtr &crsIn, const OperationMethodNNPtr &methodIn,
5450
    const std::vector<GeneralParameterValueNNPtr> &values,
5451
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies)
5452
0
    : SingleOperation(methodIn) {
5453
0
    setParameterValues(values);
5454
0
    setCRSs(crsIn, crsIn, nullptr);
5455
0
    setAccuracies(accuracies);
5456
0
}
Unexecuted instantiation: osgeo::proj::operation::PointMotionOperation::PointMotionOperation(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::CRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::OperationMethod> > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> > > > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> > > > const&)
Unexecuted instantiation: osgeo::proj::operation::PointMotionOperation::PointMotionOperation(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::CRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::OperationMethod> > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> > > > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> > > > const&)
5457
5458
// ---------------------------------------------------------------------------
5459
5460
PointMotionOperation::PointMotionOperation(const PointMotionOperation &other)
5461
0
    : CoordinateOperation(other), SingleOperation(other) {}
Unexecuted instantiation: osgeo::proj::operation::PointMotionOperation::PointMotionOperation(osgeo::proj::operation::PointMotionOperation const&)
Unexecuted instantiation: osgeo::proj::operation::PointMotionOperation::PointMotionOperation(osgeo::proj::operation::PointMotionOperation const&)
5462
5463
// ---------------------------------------------------------------------------
5464
5465
0
CoordinateOperationNNPtr PointMotionOperation::inverse() const {
5466
0
    auto inverse = shallowClone();
5467
0
    if (sourceCoordinateEpoch().has_value()) {
5468
        // Switch source and target epochs
5469
0
        inverse->setSourceCoordinateEpoch(targetCoordinateEpoch());
5470
0
        inverse->setTargetCoordinateEpoch(sourceCoordinateEpoch());
5471
5472
0
        auto l_name = inverse->nameStr();
5473
0
        auto pos = l_name.find(" from epoch ");
5474
0
        if (pos != std::string::npos)
5475
0
            l_name.resize(pos);
5476
5477
0
        const double sourceYear = getRoundedEpochInDecimalYear(
5478
0
            inverse->sourceCoordinateEpoch()->coordinateEpoch().convertToUnit(
5479
0
                common::UnitOfMeasure::YEAR));
5480
0
        const double targetYear = getRoundedEpochInDecimalYear(
5481
0
            inverse->targetCoordinateEpoch()->coordinateEpoch().convertToUnit(
5482
0
                common::UnitOfMeasure::YEAR));
5483
5484
0
        l_name += " from epoch ";
5485
0
        l_name += toString(sourceYear);
5486
0
        l_name += " to epoch ";
5487
0
        l_name += toString(targetYear);
5488
0
        util::PropertyMap newProperties;
5489
0
        newProperties.set(IdentifiedObject::NAME_KEY, l_name);
5490
0
        inverse->setProperties(newProperties);
5491
0
    }
5492
0
    return inverse;
5493
0
}
5494
5495
// ---------------------------------------------------------------------------
5496
5497
/** \brief Return an equivalent transformation to the current one, but using
5498
 * PROJ alternative grid names.
5499
 */
5500
PointMotionOperationNNPtr
5501
PointMotionOperation::substitutePROJAlternativeGridNames(
5502
0
    io::DatabaseContextNNPtr databaseContext) const {
5503
0
    auto self = NN_NO_CHECK(std::dynamic_pointer_cast<PointMotionOperation>(
5504
0
        shared_from_this().as_nullable()));
5505
5506
0
    const auto &l_method = method();
5507
0
    const int methodEPSGCode = l_method->getEPSGCode();
5508
5509
0
    const char *const paramName =
5510
0
        methodEPSGCode ==
5511
0
                EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT
5512
0
            ? EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE
5513
0
            : EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE;
5514
0
    const int paramCode =
5515
0
        methodEPSGCode ==
5516
0
                EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT
5517
0
            ? EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE
5518
0
            : EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE;
5519
5520
0
    std::string filename;
5521
0
    if (methodEPSGCode ==
5522
0
            EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL ||
5523
0
        methodEPSGCode ==
5524
0
            EPSG_CODE_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL ||
5525
0
        methodEPSGCode ==
5526
0
            EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT) {
5527
0
        const auto &fileParameter = parameterValue(paramName, paramCode);
5528
0
        if (fileParameter &&
5529
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
5530
0
            filename = fileParameter->valueFile();
5531
0
        }
5532
0
    }
5533
5534
0
    std::string projFilename;
5535
0
    std::string projGridFormat;
5536
0
    bool inverseDirection = false;
5537
0
    if (!filename.empty() &&
5538
0
        databaseContext->lookForGridAlternative(
5539
0
            filename, projFilename, projGridFormat, inverseDirection)) {
5540
5541
0
        if (filename == projFilename) {
5542
0
            return self;
5543
0
        }
5544
5545
0
        const VectorOfParameters parameters{
5546
0
            createOpParamNameEPSGCode(paramCode)};
5547
0
        const VectorOfValues values{
5548
0
            ParameterValue::createFilename(projFilename)};
5549
0
        return PointMotionOperation::create(
5550
0
            createSimilarPropertiesOperation(self), sourceCRS(),
5551
0
            createSimilarPropertiesMethod(method()), parameters, values,
5552
0
            coordinateOperationAccuracies());
5553
0
    }
5554
5555
0
    return self;
5556
0
}
5557
5558
// ---------------------------------------------------------------------------
5559
5560
/** \brief Return the source crs::CRS of the operation.
5561
 *
5562
 * @return the source CRS.
5563
 */
5564
0
const crs::CRSNNPtr &PointMotionOperation::sourceCRS() PROJ_PURE_DEFN {
5565
0
    return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_;
5566
0
}
5567
5568
// ---------------------------------------------------------------------------
5569
5570
//! @cond Doxygen_Suppress
5571
5572
0
PointMotionOperationNNPtr PointMotionOperation::shallowClone() const {
5573
0
    auto pmo =
5574
0
        PointMotionOperation::nn_make_shared<PointMotionOperation>(*this);
5575
0
    pmo->assignSelf(pmo);
5576
0
    pmo->setCRSs(this, false);
5577
0
    return pmo;
5578
0
}
5579
5580
0
CoordinateOperationNNPtr PointMotionOperation::_shallowClone() const {
5581
0
    return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
5582
0
}
5583
5584
// ---------------------------------------------------------------------------
5585
5586
PointMotionOperationNNPtr PointMotionOperation::cloneWithEpochs(
5587
    const common::DataEpoch &sourceEpoch,
5588
0
    const common::DataEpoch &targetEpoch) const {
5589
0
    auto pmo =
5590
0
        PointMotionOperation::nn_make_shared<PointMotionOperation>(*this);
5591
5592
0
    pmo->assignSelf(pmo);
5593
0
    pmo->setCRSs(this, false);
5594
5595
0
    pmo->setSourceCoordinateEpoch(
5596
0
        util::optional<common::DataEpoch>(sourceEpoch));
5597
0
    pmo->setTargetCoordinateEpoch(
5598
0
        util::optional<common::DataEpoch>(targetEpoch));
5599
5600
0
    const double sourceYear = getRoundedEpochInDecimalYear(
5601
0
        sourceEpoch.coordinateEpoch().convertToUnit(
5602
0
            common::UnitOfMeasure::YEAR));
5603
0
    const double targetYear = getRoundedEpochInDecimalYear(
5604
0
        targetEpoch.coordinateEpoch().convertToUnit(
5605
0
            common::UnitOfMeasure::YEAR));
5606
5607
0
    auto l_name = nameStr();
5608
0
    l_name += " from epoch ";
5609
0
    l_name += toString(sourceYear);
5610
0
    l_name += " to epoch ";
5611
0
    l_name += toString(targetYear);
5612
0
    util::PropertyMap newProperties;
5613
0
    newProperties.set(IdentifiedObject::NAME_KEY, l_name);
5614
0
    pmo->setProperties(newProperties);
5615
5616
0
    return pmo;
5617
0
}
5618
5619
// ---------------------------------------------------------------------------
5620
5621
0
void PointMotionOperation::_exportToWKT(io::WKTFormatter *formatter) const {
5622
0
    if (formatter->version() != io::WKTFormatter::Version::WKT2 ||
5623
0
        !formatter->use2019Keywords()) {
5624
0
        throw io::FormattingException(
5625
0
            "Transformation can only be exported to WKT2:2019");
5626
0
    }
5627
5628
0
    formatter->startNode(io::WKTConstants::POINTMOTIONOPERATION,
5629
0
                         !identifiers().empty());
5630
5631
0
    formatter->addQuotedString(nameStr());
5632
5633
0
    const auto &version = operationVersion();
5634
0
    if (version.has_value()) {
5635
0
        formatter->startNode(io::WKTConstants::VERSION, false);
5636
0
        formatter->addQuotedString(*version);
5637
0
        formatter->endNode();
5638
0
    }
5639
5640
0
    auto l_sourceCRS = sourceCRS();
5641
0
    const bool canExportCRSId =
5642
0
        !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId());
5643
5644
0
    const bool hasDomains = !domains().empty();
5645
0
    if (hasDomains) {
5646
0
        formatter->pushDisableUsage();
5647
0
    }
5648
5649
0
    formatter->startNode(io::WKTConstants::SOURCECRS, false);
5650
0
    if (canExportCRSId && !l_sourceCRS->identifiers().empty()) {
5651
        // fake that top node has no id, so that the sourceCRS id is
5652
        // considered
5653
0
        formatter->pushHasId(false);
5654
0
        l_sourceCRS->_exportToWKT(formatter);
5655
0
        formatter->popHasId();
5656
0
    } else {
5657
0
        l_sourceCRS->_exportToWKT(formatter);
5658
0
    }
5659
0
    formatter->endNode();
5660
5661
0
    if (hasDomains) {
5662
0
        formatter->popDisableUsage();
5663
0
    }
5664
5665
0
    const auto &l_method = method();
5666
0
    l_method->_exportToWKT(formatter);
5667
5668
0
    for (const auto &paramValue : parameterValues()) {
5669
0
        paramValue->_exportToWKT(formatter, nullptr);
5670
0
    }
5671
5672
0
    if (!coordinateOperationAccuracies().empty()) {
5673
0
        formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false);
5674
0
        formatter->add(coordinateOperationAccuracies()[0]->value());
5675
0
        formatter->endNode();
5676
0
    }
5677
5678
0
    ObjectUsage::baseExportToWKT(formatter);
5679
0
    formatter->endNode();
5680
0
}
5681
5682
// ---------------------------------------------------------------------------
5683
5684
void PointMotionOperation::_exportToPROJString(
5685
    io::PROJStringFormatter *formatter) const // throw(FormattingException)
5686
0
{
5687
0
    if (formatter->convention() ==
5688
0
        io::PROJStringFormatter::Convention::PROJ_4) {
5689
0
        throw io::FormattingException(
5690
0
            "PointMotionOperation cannot be exported as a PROJ.4 string");
5691
0
    }
5692
5693
0
    const int methodEPSGCode = method()->getEPSGCode();
5694
0
    if (methodEPSGCode ==
5695
0
            EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL ||
5696
0
        methodEPSGCode ==
5697
0
            EPSG_CODE_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL ||
5698
0
        methodEPSGCode ==
5699
0
            EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT) {
5700
0
        if (!sourceCoordinateEpoch().has_value()) {
5701
0
            throw io::FormattingException(
5702
0
                "CoordinateOperationNNPtr::_exportToPROJString() unimplemented "
5703
0
                "when source coordinate epoch is missing");
5704
0
        }
5705
0
        if (!targetCoordinateEpoch().has_value()) {
5706
0
            throw io::FormattingException(
5707
0
                "CoordinateOperationNNPtr::_exportToPROJString() unimplemented "
5708
0
                "when target coordinate epoch is missing");
5709
0
        }
5710
5711
0
        auto l_sourceCRS =
5712
0
            dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get());
5713
0
        if (!l_sourceCRS) {
5714
0
            throw io::FormattingException("Can apply PointMotionOperation "
5715
0
                                          "VelocityGrid only to GeodeticCRS");
5716
0
        }
5717
5718
0
        if (!l_sourceCRS->isGeocentric()) {
5719
0
            formatter->startInversion();
5720
0
            l_sourceCRS->_exportToPROJString(formatter);
5721
0
            formatter->stopInversion();
5722
5723
0
            formatter->addStep("cart");
5724
0
            l_sourceCRS->ellipsoid()->_exportToPROJString(formatter);
5725
0
        } else {
5726
0
            formatter->startInversion();
5727
0
            l_sourceCRS->addGeocentricUnitConversionIntoPROJString(formatter);
5728
0
            formatter->stopInversion();
5729
0
        }
5730
5731
0
        const double sourceYear = getRoundedEpochInDecimalYear(
5732
0
            sourceCoordinateEpoch()->coordinateEpoch().convertToUnit(
5733
0
                common::UnitOfMeasure::YEAR));
5734
0
        const double targetYear = getRoundedEpochInDecimalYear(
5735
0
            targetCoordinateEpoch()->coordinateEpoch().convertToUnit(
5736
0
                common::UnitOfMeasure::YEAR));
5737
5738
0
        formatter->addStep("set");
5739
0
        formatter->addParam("v_4", sourceYear);
5740
0
        formatter->addParam("omit_fwd");
5741
5742
0
        formatter->addStep("deformation");
5743
0
        formatter->addParam("dt", targetYear - sourceYear);
5744
5745
0
        const char *const paramName =
5746
0
            methodEPSGCode ==
5747
0
                    EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT
5748
0
                ? EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE
5749
0
                : EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE;
5750
0
        const int paramCode =
5751
0
            methodEPSGCode ==
5752
0
                    EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT
5753
0
                ? EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE
5754
0
                : EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE;
5755
0
        const auto &fileParameter = parameterValue(paramName, paramCode);
5756
0
        if (fileParameter &&
5757
0
            fileParameter->type() == ParameterValue::Type::FILENAME) {
5758
0
            formatter->addParam("grids", fileParameter->valueFile());
5759
0
        } else {
5760
0
            throw io::FormattingException(
5761
0
                "CoordinateOperationNNPtr::_exportToPROJString(): missing "
5762
0
                "velocity grid file parameter");
5763
0
        }
5764
0
        l_sourceCRS->ellipsoid()->_exportToPROJString(formatter);
5765
5766
0
        formatter->addStep("set");
5767
0
        formatter->addParam("v_4", targetYear);
5768
0
        formatter->addParam("omit_inv");
5769
5770
0
        if (!l_sourceCRS->isGeocentric()) {
5771
0
            formatter->startInversion();
5772
0
            formatter->addStep("cart");
5773
0
            l_sourceCRS->ellipsoid()->_exportToPROJString(formatter);
5774
0
            formatter->stopInversion();
5775
5776
0
            l_sourceCRS->_exportToPROJString(formatter);
5777
0
        } else {
5778
0
            l_sourceCRS->addGeocentricUnitConversionIntoPROJString(formatter);
5779
0
        }
5780
5781
0
    } else {
5782
0
        throw io::FormattingException(
5783
0
            "CoordinateOperationNNPtr::_exportToPROJString() unimplemented for "
5784
0
            "this method");
5785
0
    }
5786
0
}
5787
5788
// ---------------------------------------------------------------------------
5789
5790
void PointMotionOperation::_exportToJSON(
5791
    io::JSONFormatter *formatter) const // throw(FormattingException)
5792
0
{
5793
0
    auto writer = formatter->writer();
5794
0
    auto objectContext(formatter->MakeObjectContext("PointMotionOperation",
5795
0
                                                    !identifiers().empty()));
5796
5797
0
    writer->AddObjKey("name");
5798
0
    const auto &l_name = nameStr();
5799
0
    if (l_name.empty()) {
5800
0
        writer->Add("unnamed");
5801
0
    } else {
5802
0
        writer->Add(l_name);
5803
0
    }
5804
5805
0
    writer->AddObjKey("source_crs");
5806
0
    formatter->setAllowIDInImmediateChild();
5807
0
    sourceCRS()->_exportToJSON(formatter);
5808
5809
0
    writer->AddObjKey("method");
5810
0
    formatter->setOmitTypeInImmediateChild();
5811
0
    formatter->setAllowIDInImmediateChild();
5812
0
    method()->_exportToJSON(formatter);
5813
5814
0
    writer->AddObjKey("parameters");
5815
0
    {
5816
0
        auto parametersContext(writer->MakeArrayContext(false));
5817
0
        for (const auto &genOpParamvalue : parameterValues()) {
5818
0
            formatter->setAllowIDInImmediateChild();
5819
0
            formatter->setOmitTypeInImmediateChild();
5820
0
            genOpParamvalue->_exportToJSON(formatter);
5821
0
        }
5822
0
    }
5823
5824
0
    if (!coordinateOperationAccuracies().empty()) {
5825
0
        writer->AddObjKey("accuracy");
5826
0
        writer->Add(coordinateOperationAccuracies()[0]->value());
5827
0
    }
5828
5829
0
    ObjectUsage::baseExportToJSON(formatter);
5830
0
}
5831
5832
//! @endcond
5833
5834
// ---------------------------------------------------------------------------
5835
5836
} // namespace operation
5837
5838
NS_PROJ_END