Coverage Report

Created: 2025-06-13 06:18

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