Coverage Report

Created: 2026-04-01 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proj/src/iso19111/operation/transformation.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PROJ
4
 * Purpose:  ISO19111:2019 implementation
5
 * Author:   Even Rouault <even dot rouault at spatialys dot com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com>
9
 *
10
 * Permission is hereby granted, free of charge, to any person obtaining a
11
 * copy of this software and associated documentation files (the "Software"),
12
 * to deal in the Software without restriction, including without limitation
13
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14
 * and/or sell copies of the Software, and to permit persons to whom the
15
 * Software is furnished to do so, subject to the following conditions:
16
 *
17
 * The above copyright notice and this permission notice shall be included
18
 * in all copies or substantial portions of the Software.
19
 *
20
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26
 * DEALINGS IN THE SOFTWARE.
27
 ****************************************************************************/
28
29
#ifndef FROM_PROJ_CPP
30
#define FROM_PROJ_CPP
31
#endif
32
33
#include "proj/common.hpp"
34
#include "proj/coordinateoperation.hpp"
35
#include "proj/crs.hpp"
36
#include "proj/io.hpp"
37
#include "proj/metadata.hpp"
38
#include "proj/util.hpp"
39
40
#include "proj/internal/internal.hpp"
41
42
#include "coordinateoperation_internal.hpp"
43
#include "coordinateoperation_private.hpp"
44
#include "esriparammappings.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
57
#include "proj_json_streaming_writer.hpp"
58
59
#include <algorithm>
60
#include <cassert>
61
#include <cmath>
62
#include <cstring>
63
#include <memory>
64
#include <set>
65
#include <string>
66
#include <vector>
67
68
using namespace NS_PROJ::internal;
69
70
// ---------------------------------------------------------------------------
71
72
NS_PROJ_START
73
namespace operation {
74
75
// ---------------------------------------------------------------------------
76
77
//! @cond Doxygen_Suppress
78
struct Transformation::Private {
79
80
    TransformationPtr forwardOperation_{};
81
82
    static TransformationNNPtr registerInv(const Transformation *thisIn,
83
                                           TransformationNNPtr invTransform);
84
};
85
//! @endcond
86
87
// ---------------------------------------------------------------------------
88
89
Transformation::Transformation(
90
    const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
91
    const crs::CRSPtr &interpolationCRSIn, const OperationMethodNNPtr &methodIn,
92
    const std::vector<GeneralParameterValueNNPtr> &values,
93
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies)
94
12.9k
    : SingleOperation(methodIn), d(std::make_unique<Private>()) {
95
12.9k
    setParameterValues(values);
96
12.9k
    setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
97
12.9k
    setAccuracies(accuracies);
98
12.9k
}
osgeo::proj::operation::Transformation::Transformation(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::CRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::CRS> > const&, 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&)
Line
Count
Source
94
26
    : SingleOperation(methodIn), d(std::make_unique<Private>()) {
95
26
    setParameterValues(values);
96
26
    setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
97
26
    setAccuracies(accuracies);
98
26
}
osgeo::proj::operation::Transformation::Transformation(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::CRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::CRS> > const&, 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&)
Line
Count
Source
94
12.9k
    : SingleOperation(methodIn), d(std::make_unique<Private>()) {
95
12.9k
    setParameterValues(values);
96
12.9k
    setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
97
12.9k
    setAccuracies(accuracies);
98
12.9k
}
99
100
// ---------------------------------------------------------------------------
101
102
//! @cond Doxygen_Suppress
103
12.9k
Transformation::~Transformation() = default;
104
//! @endcond
105
106
// ---------------------------------------------------------------------------
107
108
Transformation::Transformation(const Transformation &other)
109
0
    : CoordinateOperation(other), SingleOperation(other),
110
0
      d(std::make_unique<Private>(*other.d)) {}
Unexecuted instantiation: osgeo::proj::operation::Transformation::Transformation(osgeo::proj::operation::Transformation const&)
Unexecuted instantiation: osgeo::proj::operation::Transformation::Transformation(osgeo::proj::operation::Transformation const&)
111
112
// ---------------------------------------------------------------------------
113
114
/** \brief Return the source crs::CRS of the transformation.
115
 *
116
 * @return the source CRS.
117
 */
118
52
const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_PURE_DEFN {
119
52
    return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_;
120
52
}
121
122
// ---------------------------------------------------------------------------
123
124
/** \brief Return the target crs::CRS of the transformation.
125
 *
126
 * @return the target CRS.
127
 */
128
3.71k
const crs::CRSNNPtr &Transformation::targetCRS() PROJ_PURE_DEFN {
129
3.71k
    return CoordinateOperation::getPrivate()->strongRef_->targetCRS_;
130
3.71k
}
131
132
// ---------------------------------------------------------------------------
133
134
//! @cond Doxygen_Suppress
135
0
TransformationNNPtr Transformation::shallowClone() const {
136
0
    auto transf = Transformation::nn_make_shared<Transformation>(*this);
137
0
    transf->assignSelf(transf);
138
0
    transf->setCRSs(this, false);
139
0
    if (transf->d->forwardOperation_) {
140
0
        transf->d->forwardOperation_ =
141
0
            transf->d->forwardOperation_->shallowClone().as_nullable();
142
0
    }
143
0
    return transf;
144
0
}
145
146
0
CoordinateOperationNNPtr Transformation::_shallowClone() const {
147
0
    return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
148
0
}
149
150
// ---------------------------------------------------------------------------
151
152
TransformationNNPtr
153
Transformation::promoteTo3D(const std::string &,
154
0
                            const io::DatabaseContextPtr &dbContext) const {
155
0
    auto transf = shallowClone();
156
0
    transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext),
157
0
                    targetCRS()->promoteTo3D(std::string(), dbContext),
158
0
                    interpolationCRS());
159
0
    return transf;
160
0
}
161
162
// ---------------------------------------------------------------------------
163
164
TransformationNNPtr
165
Transformation::demoteTo2D(const std::string &,
166
0
                           const io::DatabaseContextPtr &dbContext) const {
167
0
    auto transf = shallowClone();
168
0
    transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext),
169
0
                    targetCRS()->demoteTo2D(std::string(), dbContext),
170
0
                    interpolationCRS());
171
0
    return transf;
172
0
}
173
174
//! @endcond
175
176
// ---------------------------------------------------------------------------
177
178
//! @cond Doxygen_Suppress
179
/** \brief Return the TOWGS84 parameters of the transformation.
180
 *
181
 * If this transformation uses Coordinate Frame Rotation, Position Vector
182
 * transformation or Geocentric translations, a vector of 7 double values
183
 * using the Position Vector convention (EPSG:9606) is returned. Those values
184
 * can be used as the value of the WKT1 TOWGS84 parameter or
185
 * PROJ +towgs84 parameter.
186
 *
187
 * @param canThrowException if true, an exception is thrown if the method fails,
188
 * otherwise an empty vector is returned in case of failure.
189
 * @return a vector of 7 values if valid, otherwise a io::FormattingException
190
 * is thrown.
191
 * @throws io::FormattingException in case of error, if canThrowException is
192
 * true
193
 */
194
std::vector<double> Transformation::getTOWGS84Parameters(
195
    bool canThrowException) const // throw(io::FormattingException)
196
928
{
197
    // GDAL WKT1 assumes EPSG:9606 / Position Vector convention
198
199
928
    bool sevenParamsTransform = false;
200
928
    bool threeParamsTransform = false;
201
928
    bool invertRotSigns = false;
202
928
    const auto &l_method = method();
203
928
    const auto &methodName = l_method->nameStr();
204
928
    const int methodEPSGCode = l_method->getEPSGCode();
205
928
    const auto paramCount = parameterValues().size();
206
928
    if ((paramCount == 7 &&
207
5
         ci_find(methodName, "Coordinate Frame") != std::string::npos) ||
208
928
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
209
928
        methodEPSGCode ==
210
928
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC ||
211
928
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
212
928
        methodEPSGCode ==
213
928
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D ||
214
928
        methodEPSGCode ==
215
928
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D ||
216
928
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D ||
217
928
        methodEPSGCode ==
218
928
            EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND) {
219
0
        sevenParamsTransform = true;
220
0
        invertRotSigns = true;
221
928
    } else if ((paramCount == 7 &&
222
5
                ci_find(methodName, "Position Vector") != std::string::npos) ||
223
923
               methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
224
923
               methodEPSGCode ==
225
923
                   EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
226
923
               methodEPSGCode ==
227
923
                   EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
228
5
        sevenParamsTransform = true;
229
5
        invertRotSigns = false;
230
923
    } else if ((paramCount == 3 &&
231
923
                ci_find(methodName, "Geocentric translations") !=
232
923
                    std::string::npos) ||
233
0
               methodEPSGCode ==
234
0
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
235
0
               methodEPSGCode ==
236
0
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
237
0
               methodEPSGCode ==
238
923
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
239
923
        threeParamsTransform = true;
240
923
    }
241
242
928
    if (threeParamsTransform || sevenParamsTransform) {
243
928
        std::vector<double> params(7, 0.0);
244
928
        bool foundX = false;
245
928
        bool foundY = false;
246
928
        bool foundZ = false;
247
928
        bool foundRotX = false;
248
928
        bool foundRotY = false;
249
928
        bool foundRotZ = false;
250
928
        bool foundScale = false;
251
928
        const double rotSign = invertRotSigns ? -1.0 : 1.0;
252
253
928
        const auto fixNegativeZero = [](double x) {
254
15
            if (x == 0.0)
255
4
                return 0.0;
256
11
            return x;
257
15
        };
258
259
2.80k
        for (const auto &genOpParamvalue : parameterValues()) {
260
2.80k
            auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
261
2.80k
                genOpParamvalue.get());
262
2.80k
            if (opParamvalue) {
263
2.80k
                const auto &parameter = opParamvalue->parameter();
264
2.80k
                const auto epsg_code = parameter->getEPSGCode();
265
2.80k
                const auto &l_parameterValue = opParamvalue->parameterValue();
266
2.80k
                if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
267
2.80k
                    const auto &measure = l_parameterValue->value();
268
2.80k
                    if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
269
928
                        params[0] = measure.getSIValue();
270
928
                        foundX = true;
271
1.87k
                    } else if (epsg_code ==
272
1.87k
                               EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
273
928
                        params[1] = measure.getSIValue();
274
928
                        foundY = true;
275
948
                    } else if (epsg_code ==
276
948
                               EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
277
928
                        params[2] = measure.getSIValue();
278
928
                        foundZ = true;
279
928
                    } else if (epsg_code ==
280
20
                               EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
281
5
                        params[3] = fixNegativeZero(
282
5
                            rotSign * measure.convertToUnit(
283
5
                                          common::UnitOfMeasure::ARC_SECOND));
284
5
                        foundRotX = true;
285
15
                    } else if (epsg_code ==
286
15
                               EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
287
5
                        params[4] = fixNegativeZero(
288
5
                            rotSign * measure.convertToUnit(
289
5
                                          common::UnitOfMeasure::ARC_SECOND));
290
5
                        foundRotY = true;
291
10
                    } else if (epsg_code ==
292
10
                               EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
293
5
                        params[5] = fixNegativeZero(
294
5
                            rotSign * measure.convertToUnit(
295
5
                                          common::UnitOfMeasure::ARC_SECOND));
296
5
                        foundRotZ = true;
297
5
                    } else if (epsg_code ==
298
5
                               EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
299
5
                        params[6] = measure.convertToUnit(
300
5
                            common::UnitOfMeasure::PARTS_PER_MILLION);
301
5
                        foundScale = true;
302
5
                    }
303
2.80k
                }
304
2.80k
            }
305
2.80k
        }
306
928
        if (foundX && foundY && foundZ &&
307
928
            (threeParamsTransform ||
308
928
             (foundRotX && foundRotY && foundRotZ && foundScale))) {
309
928
            return params;
310
928
        } else {
311
0
            if (!canThrowException)
312
0
                return {};
313
0
            throw io::FormattingException(
314
0
                "Missing required parameter values in transformation");
315
0
        }
316
928
    }
317
318
#if 0
319
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS ||
320
        methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
321
        auto offsetLat =
322
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
323
        auto offsetLong =
324
            parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
325
326
        auto offsetHeight =
327
            parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
328
329
        if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 &&
330
            offsetHeight.getSIValue() == 0.0) {
331
            std::vector<double> params(7, 0.0);
332
            return params;
333
        }
334
    }
335
#endif
336
337
0
    if (!canThrowException)
338
0
        return {};
339
0
    throw io::FormattingException(
340
0
        "Transformation cannot be formatted as WKT1 TOWGS84 parameters");
341
0
}
342
//! @endcond
343
344
// ---------------------------------------------------------------------------
345
346
/** \brief Instantiate a transformation from a vector of GeneralParameterValue.
347
 *
348
 * @param properties See \ref general_properties. At minimum the name should be
349
 * defined.
350
 * @param sourceCRSIn Source CRS.
351
 * @param targetCRSIn Target CRS.
352
 * @param interpolationCRSIn Interpolation CRS (might be null)
353
 * @param methodIn Operation method.
354
 * @param values Vector of GeneralOperationParameterNNPtr.
355
 * @param accuracies Vector of positional accuracy (might be empty).
356
 * @return new Transformation.
357
 * @throws InvalidOperation if the object cannot be constructed.
358
 */
359
TransformationNNPtr Transformation::create(
360
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
361
    const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
362
    const OperationMethodNNPtr &methodIn,
363
    const std::vector<GeneralParameterValueNNPtr> &values,
364
12.9k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
365
12.9k
    if (methodIn->parameters().size() != values.size()) {
366
0
        throw InvalidOperation(
367
0
            "Inconsistent number of parameters and parameter values");
368
0
    }
369
12.9k
    auto transf = Transformation::nn_make_shared<Transformation>(
370
12.9k
        sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values,
371
12.9k
        accuracies);
372
12.9k
    transf->assignSelf(transf);
373
12.9k
    transf->setProperties(properties);
374
12.9k
    std::string name;
375
12.9k
    if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) &&
376
12.9k
        ci_find(name, "ballpark") != std::string::npos) {
377
3
        transf->setHasBallparkTransformation(true);
378
3
    }
379
12.9k
    return transf;
380
12.9k
}
381
382
// ---------------------------------------------------------------------------
383
384
/** \brief Instantiate a transformation and its OperationMethod.
385
 *
386
 * @param propertiesTransformation The \ref general_properties of the
387
 * Transformation.
388
 * At minimum the name should be defined.
389
 * @param sourceCRSIn Source CRS.
390
 * @param targetCRSIn Target CRS.
391
 * @param interpolationCRSIn Interpolation CRS (might be null)
392
 * @param propertiesOperationMethod The \ref general_properties of the
393
 * OperationMethod.
394
 * At minimum the name should be defined.
395
 * @param parameters Vector of parameters of the operation method.
396
 * @param values Vector of ParameterValueNNPtr. Constraint:
397
 * values.size() == parameters.size()
398
 * @param accuracies Vector of positional accuracy (might be empty).
399
 * @return new Transformation.
400
 * @throws InvalidOperation if the object cannot be constructed.
401
 */
402
TransformationNNPtr
403
Transformation::create(const util::PropertyMap &propertiesTransformation,
404
                       const crs::CRSNNPtr &sourceCRSIn,
405
                       const crs::CRSNNPtr &targetCRSIn,
406
                       const crs::CRSPtr &interpolationCRSIn,
407
                       const util::PropertyMap &propertiesOperationMethod,
408
                       const std::vector<OperationParameterNNPtr> &parameters,
409
                       const std::vector<ParameterValueNNPtr> &values,
410
                       const std::vector<metadata::PositionalAccuracyNNPtr>
411
                           &accuracies) // throw InvalidOperation
412
12.2k
{
413
12.2k
    OperationMethodNNPtr op(
414
12.2k
        OperationMethod::create(propertiesOperationMethod, parameters));
415
416
12.2k
    if (parameters.size() != values.size()) {
417
0
        throw InvalidOperation(
418
0
            "Inconsistent number of parameters and parameter values");
419
0
    }
420
12.2k
    std::vector<GeneralParameterValueNNPtr> generalParameterValues;
421
12.2k
    generalParameterValues.reserve(values.size());
422
42.1k
    for (size_t i = 0; i < values.size(); i++) {
423
29.8k
        generalParameterValues.push_back(
424
29.8k
            OperationParameterValue::create(parameters[i], values[i]));
425
29.8k
    }
426
12.2k
    return create(propertiesTransformation, sourceCRSIn, targetCRSIn,
427
12.2k
                  interpolationCRSIn, op, generalParameterValues, accuracies);
428
12.2k
}
429
430
// ---------------------------------------------------------------------------
431
432
//! @cond Doxygen_Suppress
433
434
// ---------------------------------------------------------------------------
435
436
static TransformationNNPtr createSevenParamsTransform(
437
    const util::PropertyMap &properties,
438
    const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
439
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
440
    double translationYMetre, double translationZMetre,
441
    double rotationXArcSecond, double rotationYArcSecond,
442
    double rotationZArcSecond, double scaleDifferencePPM,
443
1.64k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
444
1.64k
    return Transformation::create(
445
1.64k
        properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
446
1.64k
        VectorOfParameters{
447
1.64k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
448
1.64k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
449
1.64k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
450
1.64k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
451
1.64k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
452
1.64k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
453
1.64k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
454
1.64k
        },
455
1.64k
        createParams(common::Length(translationXMetre),
456
1.64k
                     common::Length(translationYMetre),
457
1.64k
                     common::Length(translationZMetre),
458
1.64k
                     common::Angle(rotationXArcSecond,
459
1.64k
                                   common::UnitOfMeasure::ARC_SECOND),
460
1.64k
                     common::Angle(rotationYArcSecond,
461
1.64k
                                   common::UnitOfMeasure::ARC_SECOND),
462
1.64k
                     common::Angle(rotationZArcSecond,
463
1.64k
                                   common::UnitOfMeasure::ARC_SECOND),
464
1.64k
                     common::Scale(scaleDifferencePPM,
465
1.64k
                                   common::UnitOfMeasure::PARTS_PER_MILLION)),
466
1.64k
        accuracies);
467
1.64k
}
468
469
// ---------------------------------------------------------------------------
470
471
static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn,
472
                                  const crs::CRSNNPtr &targetCRSIn,
473
                                  bool &isGeocentric, bool &isGeog2D,
474
3.66k
                                  bool &isGeog3D) {
475
3.66k
    auto sourceCRSGeod =
476
3.66k
        dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get());
477
3.66k
    auto targetCRSGeod =
478
3.66k
        dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get());
479
3.66k
    isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() &&
480
278
                   targetCRSGeod && targetCRSGeod->isGeocentric();
481
3.66k
    if (isGeocentric) {
482
278
        isGeog2D = false;
483
278
        isGeog3D = false;
484
278
        return;
485
278
    }
486
3.38k
    isGeocentric = false;
487
488
3.38k
    auto sourceCRSGeog =
489
3.38k
        dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get());
490
3.38k
    auto targetCRSGeog =
491
3.38k
        dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get());
492
3.38k
    if (!(sourceCRSGeog ||
493
72
          (sourceCRSGeod && sourceCRSGeod->isSphericalPlanetocentric())) ||
494
3.38k
        !(targetCRSGeog ||
495
0
          (targetCRSGeod && targetCRSGeod->isSphericalPlanetocentric()))) {
496
0
        throw InvalidOperation("Inconsistent CRS type");
497
0
    }
498
3.38k
    const auto nSrcAxisCount =
499
3.38k
        sourceCRSGeod->coordinateSystem()->axisList().size();
500
3.38k
    const auto nTargetAxisCount =
501
3.38k
        targetCRSGeod->coordinateSystem()->axisList().size();
502
3.38k
    isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2;
503
3.38k
    isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2;
504
3.38k
}
505
506
// ---------------------------------------------------------------------------
507
508
static int
509
useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties,
510
3.66k
                                    int nDefaultOperationMethodEPSGCode) {
511
3.66k
    const auto *operationMethodEPSGCode =
512
3.66k
        properties.get("OPERATION_METHOD_EPSG_CODE");
513
3.66k
    if (operationMethodEPSGCode) {
514
0
        const auto boxedValue = dynamic_cast<const util::BoxedValue *>(
515
0
            (*operationMethodEPSGCode).get());
516
0
        if (boxedValue &&
517
0
            boxedValue->type() == util::BoxedValue::Type::INTEGER) {
518
0
            return boxedValue->integerValue();
519
0
        }
520
0
    }
521
3.66k
    return nDefaultOperationMethodEPSGCode;
522
3.66k
}
523
//! @endcond
524
525
// ---------------------------------------------------------------------------
526
527
/** \brief Instantiate a transformation with Geocentric Translations method.
528
 *
529
 * @param properties See \ref general_properties of the Transformation.
530
 * At minimum the name should be defined.
531
 * @param sourceCRSIn Source CRS.
532
 * @param targetCRSIn Target CRS.
533
 * @param translationXMetre Value of the Translation_X parameter (in metre).
534
 * @param translationYMetre Value of the Translation_Y parameter (in metre).
535
 * @param translationZMetre Value of the Translation_Z parameter (in metre).
536
 * @param accuracies Vector of positional accuracy (might be empty).
537
 * @return new Transformation.
538
 */
539
TransformationNNPtr Transformation::createGeocentricTranslations(
540
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
541
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
542
    double translationYMetre, double translationZMetre,
543
2.02k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
544
2.02k
    bool isGeocentric;
545
2.02k
    bool isGeog2D;
546
2.02k
    bool isGeog3D;
547
2.02k
    getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
548
2.02k
                          isGeog3D);
549
2.02k
    return create(
550
2.02k
        properties, sourceCRSIn, targetCRSIn, nullptr,
551
2.02k
        createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
552
2.02k
            properties,
553
2.02k
            isGeocentric ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC
554
2.02k
            : isGeog2D
555
1.77k
                ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D
556
1.77k
                : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)),
557
2.02k
        VectorOfParameters{
558
2.02k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
559
2.02k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
560
2.02k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
561
2.02k
        },
562
2.02k
        createParams(common::Length(translationXMetre),
563
2.02k
                     common::Length(translationYMetre),
564
2.02k
                     common::Length(translationZMetre)),
565
2.02k
        accuracies);
566
2.02k
}
567
568
// ---------------------------------------------------------------------------
569
570
/** \brief Instantiate a transformation with Position vector transformation
571
 * method.
572
 *
573
 * This is similar to createCoordinateFrameRotation(), except that the sign of
574
 * the rotation terms is inverted.
575
 *
576
 * @param properties See \ref general_properties of the Transformation.
577
 * At minimum the name should be defined.
578
 * @param sourceCRSIn Source CRS.
579
 * @param targetCRSIn Target CRS.
580
 * @param translationXMetre Value of the Translation_X parameter (in metre).
581
 * @param translationYMetre Value of the Translation_Y parameter (in metre).
582
 * @param translationZMetre Value of the Translation_Z parameter (in metre).
583
 * @param rotationXArcSecond Value of the Rotation_X parameter (in
584
 * arc-second).
585
 * @param rotationYArcSecond Value of the Rotation_Y parameter (in
586
 * arc-second).
587
 * @param rotationZArcSecond Value of the Rotation_Z parameter (in
588
 * arc-second).
589
 * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
590
 * parts-per-million).
591
 * @param accuracies Vector of positional accuracy (might be empty).
592
 * @return new Transformation.
593
 */
594
TransformationNNPtr Transformation::createPositionVector(
595
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
596
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
597
    double translationYMetre, double translationZMetre,
598
    double rotationXArcSecond, double rotationYArcSecond,
599
    double rotationZArcSecond, double scaleDifferencePPM,
600
1.64k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
601
602
1.64k
    bool isGeocentric;
603
1.64k
    bool isGeog2D;
604
1.64k
    bool isGeog3D;
605
1.64k
    getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
606
1.64k
                          isGeog3D);
607
1.64k
    return createSevenParamsTransform(
608
1.64k
        properties,
609
1.64k
        createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
610
1.64k
            properties,
611
1.64k
            isGeocentric ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC
612
1.64k
            : isGeog2D   ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
613
1.60k
                         : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)),
614
1.64k
        sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
615
1.64k
        translationZMetre, rotationXArcSecond, rotationYArcSecond,
616
1.64k
        rotationZArcSecond, scaleDifferencePPM, accuracies);
617
1.64k
}
618
619
// ---------------------------------------------------------------------------
620
621
/** \brief Instantiate a transformation with Coordinate Frame Rotation method.
622
 *
623
 * This is similar to createPositionVector(), except that the sign of
624
 * the rotation terms is inverted.
625
 *
626
 * @param properties See \ref general_properties of the Transformation.
627
 * At minimum the name should be defined.
628
 * @param sourceCRSIn Source CRS.
629
 * @param targetCRSIn Target CRS.
630
 * @param translationXMetre Value of the Translation_X parameter (in metre).
631
 * @param translationYMetre Value of the Translation_Y parameter (in metre).
632
 * @param translationZMetre Value of the Translation_Z parameter (in metre).
633
 * @param rotationXArcSecond Value of the Rotation_X parameter (in
634
 * arc-second).
635
 * @param rotationYArcSecond Value of the Rotation_Y parameter (in
636
 * arc-second).
637
 * @param rotationZArcSecond Value of the Rotation_Z parameter (in
638
 * arc-second).
639
 * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
640
 * parts-per-million).
641
 * @param accuracies Vector of positional accuracy (might be empty).
642
 * @return new Transformation.
643
 */
644
TransformationNNPtr Transformation::createCoordinateFrameRotation(
645
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
646
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
647
    double translationYMetre, double translationZMetre,
648
    double rotationXArcSecond, double rotationYArcSecond,
649
    double rotationZArcSecond, double scaleDifferencePPM,
650
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
651
0
    bool isGeocentric;
652
0
    bool isGeog2D;
653
0
    bool isGeog3D;
654
0
    getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
655
0
                          isGeog3D);
656
0
    return createSevenParamsTransform(
657
0
        properties,
658
0
        createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
659
0
            properties,
660
0
            isGeocentric ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC
661
0
            : isGeog2D   ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D
662
0
                         : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D)),
663
0
        sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
664
0
        translationZMetre, rotationXArcSecond, rotationYArcSecond,
665
0
        rotationZArcSecond, scaleDifferencePPM, accuracies);
666
0
}
667
668
// ---------------------------------------------------------------------------
669
670
//! @cond Doxygen_Suppress
671
static TransformationNNPtr createFifteenParamsTransform(
672
    const util::PropertyMap &properties,
673
    const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
674
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
675
    double translationYMetre, double translationZMetre,
676
    double rotationXArcSecond, double rotationYArcSecond,
677
    double rotationZArcSecond, double scaleDifferencePPM,
678
    double rateTranslationX, double rateTranslationY, double rateTranslationZ,
679
    double rateRotationX, double rateRotationY, double rateRotationZ,
680
    double rateScaleDifference, double referenceEpochYear,
681
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
682
0
    return Transformation::create(
683
0
        properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
684
0
        VectorOfParameters{
685
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
686
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
687
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
688
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
689
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
690
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
691
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
692
693
0
            createOpParamNameEPSGCode(
694
0
                EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION),
695
0
            createOpParamNameEPSGCode(
696
0
                EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION),
697
0
            createOpParamNameEPSGCode(
698
0
                EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION),
699
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION),
700
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION),
701
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION),
702
0
            createOpParamNameEPSGCode(
703
0
                EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE),
704
705
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH),
706
0
        },
707
0
        VectorOfValues{
708
0
            common::Length(translationXMetre),
709
0
            common::Length(translationYMetre),
710
0
            common::Length(translationZMetre),
711
0
            common::Angle(rotationXArcSecond,
712
0
                          common::UnitOfMeasure::ARC_SECOND),
713
0
            common::Angle(rotationYArcSecond,
714
0
                          common::UnitOfMeasure::ARC_SECOND),
715
0
            common::Angle(rotationZArcSecond,
716
0
                          common::UnitOfMeasure::ARC_SECOND),
717
0
            common::Scale(scaleDifferencePPM,
718
0
                          common::UnitOfMeasure::PARTS_PER_MILLION),
719
0
            common::Measure(rateTranslationX,
720
0
                            common::UnitOfMeasure::METRE_PER_YEAR),
721
0
            common::Measure(rateTranslationY,
722
0
                            common::UnitOfMeasure::METRE_PER_YEAR),
723
0
            common::Measure(rateTranslationZ,
724
0
                            common::UnitOfMeasure::METRE_PER_YEAR),
725
0
            common::Measure(rateRotationX,
726
0
                            common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
727
0
            common::Measure(rateRotationY,
728
0
                            common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
729
0
            common::Measure(rateRotationZ,
730
0
                            common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
731
0
            common::Measure(rateScaleDifference,
732
0
                            common::UnitOfMeasure::PPM_PER_YEAR),
733
0
            common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR),
734
0
        },
735
0
        accuracies);
736
0
}
737
//! @endcond
738
739
// ---------------------------------------------------------------------------
740
741
/** \brief Instantiate a transformation with Time Dependent position vector
742
 * transformation method.
743
 *
744
 * This is similar to createTimeDependentCoordinateFrameRotation(), except that
745
 * the sign of
746
 * the rotation terms is inverted.
747
 *
748
 * This method is defined as
749
 * <a href="https://epsg.org/coord-operation-method_1053/index.html">
750
 * EPSG:1053</a>.
751
 *
752
 * @param properties See \ref general_properties of the Transformation.
753
 * At minimum the name should be defined.
754
 * @param sourceCRSIn Source CRS.
755
 * @param targetCRSIn Target CRS.
756
 * @param translationXMetre Value of the Translation_X parameter (in metre).
757
 * @param translationYMetre Value of the Translation_Y parameter (in metre).
758
 * @param translationZMetre Value of the Translation_Z parameter (in metre).
759
 * @param rotationXArcSecond Value of the Rotation_X parameter (in
760
 * arc-second).
761
 * @param rotationYArcSecond Value of the Rotation_Y parameter (in
762
 * arc-second).
763
 * @param rotationZArcSecond Value of the Rotation_Z parameter (in
764
 * arc-second).
765
 * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
766
 * parts-per-million).
767
 * @param rateTranslationX Value of the rate of change of X-axis translation (in
768
 * metre/year)
769
 * @param rateTranslationY Value of the rate of change of Y-axis translation (in
770
 * metre/year)
771
 * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
772
 * metre/year)
773
 * @param rateRotationX Value of the rate of change of X-axis rotation (in
774
 * arc-second/year)
775
 * @param rateRotationY Value of the rate of change of Y-axis rotation (in
776
 * arc-second/year)
777
 * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
778
 * arc-second/year)
779
 * @param rateScaleDifference Value of the rate of change of scale difference
780
 * (in PPM/year)
781
 * @param referenceEpochYear Parameter reference epoch (in decimal year)
782
 * @param accuracies Vector of positional accuracy (might be empty).
783
 * @return new Transformation.
784
 */
785
TransformationNNPtr Transformation::createTimeDependentPositionVector(
786
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
787
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
788
    double translationYMetre, double translationZMetre,
789
    double rotationXArcSecond, double rotationYArcSecond,
790
    double rotationZArcSecond, double scaleDifferencePPM,
791
    double rateTranslationX, double rateTranslationY, double rateTranslationZ,
792
    double rateRotationX, double rateRotationY, double rateRotationZ,
793
    double rateScaleDifference, double referenceEpochYear,
794
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
795
0
    bool isGeocentric;
796
0
    bool isGeog2D;
797
0
    bool isGeog3D;
798
0
    getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
799
0
                          isGeog3D);
800
0
    return createFifteenParamsTransform(
801
0
        properties,
802
0
        createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
803
0
            properties,
804
0
            isGeocentric
805
0
                ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC
806
0
            : isGeog2D
807
0
                ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D
808
0
                : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D)),
809
0
        sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
810
0
        translationZMetre, rotationXArcSecond, rotationYArcSecond,
811
0
        rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
812
0
        rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
813
0
        rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
814
0
}
815
816
// ---------------------------------------------------------------------------
817
818
/** \brief Instantiate a transformation with Time Dependent Position coordinate
819
 * frame rotation transformation method.
820
 *
821
 * This is similar to createTimeDependentPositionVector(), except that the sign
822
 * of
823
 * the rotation terms is inverted.
824
 *
825
 * This method is defined as
826
 * <a href="https://epsg.org/coord-operation-method_1056/index.html">
827
 * EPSG:1056</a>.
828
 *
829
 * @param properties See \ref general_properties of the Transformation.
830
 * At minimum the name should be defined.
831
 * @param sourceCRSIn Source CRS.
832
 * @param targetCRSIn Target CRS.
833
 * @param translationXMetre Value of the Translation_X parameter (in metre).
834
 * @param translationYMetre Value of the Translation_Y parameter (in metre).
835
 * @param translationZMetre Value of the Translation_Z parameter (in metre).
836
 * @param rotationXArcSecond Value of the Rotation_X parameter (in
837
 * arc-second).
838
 * @param rotationYArcSecond Value of the Rotation_Y parameter (in
839
 * arc-second).
840
 * @param rotationZArcSecond Value of the Rotation_Z parameter (in
841
 * arc-second).
842
 * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
843
 * parts-per-million).
844
 * @param rateTranslationX Value of the rate of change of X-axis translation (in
845
 * metre/year)
846
 * @param rateTranslationY Value of the rate of change of Y-axis translation (in
847
 * metre/year)
848
 * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
849
 * metre/year)
850
 * @param rateRotationX Value of the rate of change of X-axis rotation (in
851
 * arc-second/year)
852
 * @param rateRotationY Value of the rate of change of Y-axis rotation (in
853
 * arc-second/year)
854
 * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
855
 * arc-second/year)
856
 * @param rateScaleDifference Value of the rate of change of scale difference
857
 * (in PPM/year)
858
 * @param referenceEpochYear Parameter reference epoch (in decimal year)
859
 * @param accuracies Vector of positional accuracy (might be empty).
860
 * @return new Transformation.
861
 * @throws InvalidOperation if the object cannot be constructed.
862
 */
863
TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation(
864
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
865
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
866
    double translationYMetre, double translationZMetre,
867
    double rotationXArcSecond, double rotationYArcSecond,
868
    double rotationZArcSecond, double scaleDifferencePPM,
869
    double rateTranslationX, double rateTranslationY, double rateTranslationZ,
870
    double rateRotationX, double rateRotationY, double rateRotationZ,
871
    double rateScaleDifference, double referenceEpochYear,
872
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
873
874
0
    bool isGeocentric;
875
0
    bool isGeog2D;
876
0
    bool isGeog3D;
877
0
    getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
878
0
                          isGeog3D);
879
0
    return createFifteenParamsTransform(
880
0
        properties,
881
0
        createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
882
0
            properties,
883
0
            isGeocentric
884
0
                ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC
885
0
            : isGeog2D
886
0
                ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D
887
0
                : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D)),
888
0
        sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
889
0
        translationZMetre, rotationXArcSecond, rotationYArcSecond,
890
0
        rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
891
0
        rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
892
0
        rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
893
0
}
894
895
// ---------------------------------------------------------------------------
896
897
//! @cond Doxygen_Suppress
898
static TransformationNNPtr _createMolodensky(
899
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
900
    const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode,
901
    double translationXMetre, double translationYMetre,
902
    double translationZMetre, double semiMajorAxisDifferenceMetre,
903
    double flattingDifference,
904
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
905
0
    return Transformation::create(
906
0
        properties, sourceCRSIn, targetCRSIn, nullptr,
907
0
        createMethodMapNameEPSGCode(methodEPSGCode),
908
0
        VectorOfParameters{
909
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
910
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
911
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
912
0
            createOpParamNameEPSGCode(
913
0
                EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE),
914
0
            createOpParamNameEPSGCode(
915
0
                EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE),
916
0
        },
917
0
        createParams(
918
0
            common::Length(translationXMetre),
919
0
            common::Length(translationYMetre),
920
0
            common::Length(translationZMetre),
921
0
            common::Length(semiMajorAxisDifferenceMetre),
922
0
            common::Measure(flattingDifference, common::UnitOfMeasure::NONE)),
923
0
        accuracies);
924
0
}
925
//! @endcond
926
927
// ---------------------------------------------------------------------------
928
929
/** \brief Instantiate a transformation with Molodensky method.
930
 *
931
 * @see createAbridgedMolodensky() for a related method.
932
 *
933
 * This method is defined as
934
 * <a href="https://epsg.org/coord-operation-method_9604/index.html">
935
 * EPSG:9604</a>.
936
 *
937
 * @param properties See \ref general_properties of the Transformation.
938
 * At minimum the name should be defined.
939
 * @param sourceCRSIn Source CRS.
940
 * @param targetCRSIn Target CRS.
941
 * @param translationXMetre Value of the Translation_X parameter (in metre).
942
 * @param translationYMetre Value of the Translation_Y parameter (in metre).
943
 * @param translationZMetre Value of the Translation_Z parameter (in metre).
944
 * @param semiMajorAxisDifferenceMetre The difference between the semi-major
945
 * axis values of the ellipsoids used in the target and source CRS (in metre).
946
 * @param flattingDifference The difference  between the flattening values of
947
 * the ellipsoids used in the target and source CRS.
948
 * @param accuracies Vector of positional accuracy (might be empty).
949
 * @return new Transformation.
950
 * @throws InvalidOperation if the object cannot be constructed.
951
 */
952
TransformationNNPtr Transformation::createMolodensky(
953
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
954
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
955
    double translationYMetre, double translationZMetre,
956
    double semiMajorAxisDifferenceMetre, double flattingDifference,
957
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
958
0
    return _createMolodensky(
959
0
        properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY,
960
0
        translationXMetre, translationYMetre, translationZMetre,
961
0
        semiMajorAxisDifferenceMetre, flattingDifference, accuracies);
962
0
}
963
964
// ---------------------------------------------------------------------------
965
966
/** \brief Instantiate a transformation with Abridged Molodensky method.
967
 *
968
 * @see createdMolodensky() for a related method.
969
 *
970
 * This method is defined as
971
 * <a href="https://epsg.org/coord-operation-method_9605/index.html">
972
 * EPSG:9605</a>.
973
 *
974
 * @param properties See \ref general_properties of the Transformation.
975
 * At minimum the name should be defined.
976
 * @param sourceCRSIn Source CRS.
977
 * @param targetCRSIn Target CRS.
978
 * @param translationXMetre Value of the Translation_X parameter (in metre).
979
 * @param translationYMetre Value of the Translation_Y parameter (in metre).
980
 * @param translationZMetre Value of the Translation_Z parameter (in metre).
981
 * @param semiMajorAxisDifferenceMetre The difference between the semi-major
982
 * axis values of the ellipsoids used in the target and source CRS (in metre).
983
 * @param flattingDifference The difference  between the flattening values of
984
 * the ellipsoids used in the target and source CRS.
985
 * @param accuracies Vector of positional accuracy (might be empty).
986
 * @return new Transformation.
987
 * @throws InvalidOperation if the object cannot be constructed.
988
 */
989
TransformationNNPtr Transformation::createAbridgedMolodensky(
990
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
991
    const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
992
    double translationYMetre, double translationZMetre,
993
    double semiMajorAxisDifferenceMetre, double flattingDifference,
994
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
995
0
    return _createMolodensky(properties, sourceCRSIn, targetCRSIn,
996
0
                             EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY,
997
0
                             translationXMetre, translationYMetre,
998
0
                             translationZMetre, semiMajorAxisDifferenceMetre,
999
0
                             flattingDifference, accuracies);
1000
0
}
1001
1002
// ---------------------------------------------------------------------------
1003
1004
/** \brief Instantiate a transformation from TOWGS84 parameters.
1005
 *
1006
 * This is a helper of createPositionVector() with the source CRS being the
1007
 * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326
1008
 *
1009
 * @param sourceCRSIn Source CRS.
1010
 * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z)
1011
 * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference)
1012
 * passed to createPositionVector()
1013
 * @return new Transformation.
1014
 * @throws InvalidOperation if the object cannot be constructed.
1015
 */
1016
TransformationNNPtr Transformation::createTOWGS84(
1017
    const crs::CRSNNPtr &sourceCRSIn,
1018
    const std::vector<double> &TOWGS84Parameters) // throw InvalidOperation
1019
3.67k
{
1020
3.67k
    if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) {
1021
17
        throw InvalidOperation(
1022
17
            "Invalid number of elements in TOWGS84Parameters");
1023
17
    }
1024
1025
3.66k
    auto transformSourceGeodCRS = sourceCRSIn->extractGeodeticCRS();
1026
3.66k
    if (!transformSourceGeodCRS) {
1027
0
        throw InvalidOperation(
1028
0
            "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation");
1029
0
    }
1030
1031
3.66k
    util::PropertyMap properties;
1032
3.66k
    properties.set(common::IdentifiedObject::NAME_KEY,
1033
3.66k
                   concat("Transformation from ",
1034
3.66k
                          transformSourceGeodCRS->nameStr(), " to WGS84"));
1035
1036
3.66k
    auto targetCRS = dynamic_cast<const crs::GeographicCRS *>(
1037
3.66k
                         transformSourceGeodCRS.get()) ||
1038
350
                             transformSourceGeodCRS->isSphericalPlanetocentric()
1039
3.66k
                         ? util::nn_static_pointer_cast<crs::CRS>(
1040
3.38k
                               crs::GeographicCRS::EPSG_4326)
1041
3.66k
                         : util::nn_static_pointer_cast<crs::CRS>(
1042
278
                               crs::GeodeticCRS::EPSG_4978);
1043
1044
3.66k
    crs::CRSNNPtr transformSourceCRS = NN_NO_CHECK(transformSourceGeodCRS);
1045
3.66k
    if (TOWGS84Parameters.size() == 3) {
1046
2.02k
        return createGeocentricTranslations(
1047
2.02k
            properties, transformSourceCRS, targetCRS, TOWGS84Parameters[0],
1048
2.02k
            TOWGS84Parameters[1], TOWGS84Parameters[2], {});
1049
2.02k
    }
1050
1051
1.64k
    return createPositionVector(
1052
1.64k
        properties, transformSourceCRS, targetCRS, TOWGS84Parameters[0],
1053
1.64k
        TOWGS84Parameters[1], TOWGS84Parameters[2], TOWGS84Parameters[3],
1054
1.64k
        TOWGS84Parameters[4], TOWGS84Parameters[5], TOWGS84Parameters[6], {});
1055
3.66k
}
1056
1057
// ---------------------------------------------------------------------------
1058
/** \brief Instantiate a transformation with NTv2 method.
1059
 *
1060
 * @param properties See \ref general_properties of the Transformation.
1061
 * At minimum the name should be defined.
1062
 * @param sourceCRSIn Source CRS.
1063
 * @param targetCRSIn Target CRS.
1064
 * @param filename NTv2 filename.
1065
 * @param accuracies Vector of positional accuracy (might be empty).
1066
 * @return new Transformation.
1067
 */
1068
TransformationNNPtr Transformation::createNTv2(
1069
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1070
    const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
1071
3.01k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1072
1073
3.01k
    return create(properties, sourceCRSIn, targetCRSIn, nullptr,
1074
3.01k
                  createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2),
1075
3.01k
                  VectorOfParameters{createOpParamNameEPSGCode(
1076
3.01k
                      EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)},
1077
3.01k
                  VectorOfValues{ParameterValue::createFilename(filename)},
1078
3.01k
                  accuracies);
1079
3.01k
}
1080
1081
// ---------------------------------------------------------------------------
1082
1083
//! @cond Doxygen_Suppress
1084
static TransformationNNPtr _createGravityRelatedHeightToGeographic3D(
1085
    const util::PropertyMap &properties, bool inverse,
1086
    const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
1087
    const crs::CRSPtr &interpolationCRSIn, const std::string &filename,
1088
4.24k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1089
1090
4.24k
    return Transformation::create(
1091
4.24k
        properties, sourceCRSIn, targetCRSIn, interpolationCRSIn,
1092
4.24k
        util::PropertyMap().set(
1093
4.24k
            common::IdentifiedObject::NAME_KEY,
1094
4.24k
            inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D
1095
4.24k
                    : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D),
1096
4.24k
        VectorOfParameters{createOpParamNameEPSGCode(
1097
4.24k
            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)},
1098
4.24k
        VectorOfValues{ParameterValue::createFilename(filename)}, accuracies);
1099
4.24k
}
1100
//! @endcond
1101
1102
// ---------------------------------------------------------------------------
1103
/** \brief Instantiate a transformation from GravityRelatedHeight to
1104
 * Geographic3D
1105
 *
1106
 * @param properties See \ref general_properties of the Transformation.
1107
 * At minimum the name should be defined.
1108
 * @param sourceCRSIn Source CRS.
1109
 * @param targetCRSIn Target CRS.
1110
 * @param interpolationCRSIn Interpolation CRS. (might be null)
1111
 * @param filename GRID filename.
1112
 * @param accuracies Vector of positional accuracy (might be empty).
1113
 * @return new Transformation.
1114
 */
1115
TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D(
1116
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1117
    const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
1118
    const std::string &filename,
1119
4.24k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1120
1121
4.24k
    return _createGravityRelatedHeightToGeographic3D(
1122
4.24k
        properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn,
1123
4.24k
        filename, accuracies);
1124
4.24k
}
1125
1126
// ---------------------------------------------------------------------------
1127
1128
/** \brief Instantiate a transformation with method VERTCON
1129
 *
1130
 * @param properties See \ref general_properties of the Transformation.
1131
 * At minimum the name should be defined.
1132
 * @param sourceCRSIn Source CRS.
1133
 * @param targetCRSIn Target CRS.
1134
 * @param filename GRID filename.
1135
 * @param accuracies Vector of positional accuracy (might be empty).
1136
 * @return new Transformation.
1137
 */
1138
TransformationNNPtr Transformation::createVERTCON(
1139
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1140
    const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
1141
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1142
1143
0
    return create(properties, sourceCRSIn, targetCRSIn, nullptr,
1144
0
                  createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON),
1145
0
                  VectorOfParameters{createOpParamNameEPSGCode(
1146
0
                      EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)},
1147
0
                  VectorOfValues{ParameterValue::createFilename(filename)},
1148
0
                  accuracies);
1149
0
}
1150
1151
// ---------------------------------------------------------------------------
1152
1153
//! @cond Doxygen_Suppress
1154
static inline std::vector<metadata::PositionalAccuracyNNPtr>
1155
0
buildAccuracyZero() {
1156
0
    return std::vector<metadata::PositionalAccuracyNNPtr>{
1157
0
        metadata::PositionalAccuracy::create("0")};
1158
0
}
1159
1160
// ---------------------------------------------------------------------------
1161
1162
//! @endcond
1163
1164
/** \brief Instantiate a transformation with method Longitude rotation
1165
 *
1166
 * This method is defined as
1167
 * <a href="https://epsg.org/coord-operation-method_9601/index.html">
1168
 * EPSG:9601</a>.
1169
 *
1170
 * @param properties See \ref general_properties of the Transformation.
1171
 * At minimum the name should be defined.
1172
 * @param sourceCRSIn Source CRS.
1173
 * @param targetCRSIn Target CRS.
1174
 * @param offset Longitude offset to add.
1175
 * @return new Transformation.
1176
 */
1177
TransformationNNPtr Transformation::createLongitudeRotation(
1178
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1179
0
    const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) {
1180
1181
0
    return create(
1182
0
        properties, sourceCRSIn, targetCRSIn, nullptr,
1183
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION),
1184
0
        VectorOfParameters{
1185
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
1186
0
        VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero());
1187
0
}
1188
1189
// ---------------------------------------------------------------------------
1190
1191
/** \brief Instantiate a transformation with method Geographic 2D offsets
1192
 *
1193
 * This method is defined as
1194
 * <a href="https://epsg.org/coord-operation-method_9619/index.html">
1195
 * EPSG:9619</a>.
1196
 *
1197
 * @param properties See \ref general_properties of the Transformation.
1198
 * At minimum the name should be defined.
1199
 * @param sourceCRSIn Source CRS.
1200
 * @param targetCRSIn Target CRS.
1201
 * @param offsetLat Latitude offset to add.
1202
 * @param offsetLong Longitude offset to add.
1203
 * @param accuracies Vector of positional accuracy (might be empty).
1204
 * @return new Transformation.
1205
 */
1206
TransformationNNPtr Transformation::createGeographic2DOffsets(
1207
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1208
    const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
1209
    const common::Angle &offsetLong,
1210
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1211
0
    return create(
1212
0
        properties, sourceCRSIn, targetCRSIn, nullptr,
1213
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS),
1214
0
        VectorOfParameters{
1215
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
1216
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
1217
0
        VectorOfValues{offsetLat, offsetLong}, accuracies);
1218
0
}
1219
1220
// ---------------------------------------------------------------------------
1221
1222
/** \brief Instantiate a transformation with method Geographic 3D offsets
1223
 *
1224
 * This method is defined as
1225
 * <a href="https://epsg.org/coord-operation-method_9660/index.html">
1226
 * EPSG:9660</a>.
1227
 *
1228
 * @param properties See \ref general_properties of the Transformation.
1229
 * At minimum the name should be defined.
1230
 * @param sourceCRSIn Source CRS.
1231
 * @param targetCRSIn Target CRS.
1232
 * @param offsetLat Latitude offset to add.
1233
 * @param offsetLong Longitude offset to add.
1234
 * @param offsetHeight Height offset to add.
1235
 * @param accuracies Vector of positional accuracy (might be empty).
1236
 * @return new Transformation.
1237
 */
1238
TransformationNNPtr Transformation::createGeographic3DOffsets(
1239
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1240
    const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
1241
    const common::Angle &offsetLong, const common::Length &offsetHeight,
1242
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1243
0
    return create(
1244
0
        properties, sourceCRSIn, targetCRSIn, nullptr,
1245
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS),
1246
0
        VectorOfParameters{
1247
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
1248
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
1249
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
1250
0
        VectorOfValues{offsetLat, offsetLong, offsetHeight}, accuracies);
1251
0
}
1252
1253
// ---------------------------------------------------------------------------
1254
1255
/** \brief Instantiate a transformation with method Geographic 2D with
1256
 * height
1257
 * offsets
1258
 *
1259
 * This method is defined as
1260
 * <a href="https://epsg.org/coord-operation-method_9618/index.html">
1261
 * EPSG:9618</a>.
1262
 *
1263
 * @param properties See \ref general_properties of the Transformation.
1264
 * At minimum the name should be defined.
1265
 * @param sourceCRSIn Source CRS.
1266
 * @param targetCRSIn Target CRS.
1267
 * @param offsetLat Latitude offset to add.
1268
 * @param offsetLong Longitude offset to add.
1269
 * @param offsetHeight Geoid undulation to add.
1270
 * @param accuracies Vector of positional accuracy (might be empty).
1271
 * @return new Transformation.
1272
 */
1273
TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets(
1274
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1275
    const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
1276
    const common::Angle &offsetLong, const common::Length &offsetHeight,
1277
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1278
0
    return create(
1279
0
        properties, sourceCRSIn, targetCRSIn, nullptr,
1280
0
        createMethodMapNameEPSGCode(
1281
0
            EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS),
1282
0
        VectorOfParameters{
1283
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
1284
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
1285
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_HEIGHT)},
1286
0
        VectorOfValues{offsetLat, offsetLong, offsetHeight}, accuracies);
1287
0
}
1288
1289
// ---------------------------------------------------------------------------
1290
1291
/** \brief Instantiate a transformation with method Cartesian grid offsets
1292
 *
1293
 * This method is defined as
1294
 * <a href="https://epsg.org/coord-operation-method_9656/index.html">
1295
 * EPSG:9656</a>.
1296
 *
1297
 * @param properties See \ref general_properties of the Transformation.
1298
 * At minimum the name should be defined.
1299
 * @param sourceCRSIn Source CRS.
1300
 * @param targetCRSIn Target CRS.
1301
 * @param eastingOffset Easting offset to add.
1302
 * @param northingOffset Northing offset to add.
1303
 * @param accuracies Vector of positional accuracy (might be empty).
1304
 * @return new Transformation.
1305
 * @since PROJ 9.5.0
1306
 */
1307
TransformationNNPtr Transformation::createCartesianGridOffsets(
1308
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1309
    const crs::CRSNNPtr &targetCRSIn, const common::Length &eastingOffset,
1310
    const common::Length &northingOffset,
1311
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1312
0
    return create(
1313
0
        properties, sourceCRSIn, targetCRSIn, nullptr,
1314
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS),
1315
0
        VectorOfParameters{
1316
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_EASTING_OFFSET),
1317
0
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_NORTHING_OFFSET)},
1318
0
        VectorOfValues{eastingOffset, northingOffset}, accuracies);
1319
0
}
1320
1321
// ---------------------------------------------------------------------------
1322
1323
/** \brief Instantiate a transformation with method Vertical Offset.
1324
 *
1325
 * This method is defined as
1326
 * <a href="https://epsg.org/coord-operation-method_9616/index.html">
1327
 * EPSG:9616</a>.
1328
 *
1329
 * @param properties See \ref general_properties of the Transformation.
1330
 * At minimum the name should be defined.
1331
 * @param sourceCRSIn Source CRS.
1332
 * @param targetCRSIn Target CRS.
1333
 * @param offsetHeight Geoid undulation to add.
1334
 * @param accuracies Vector of positional accuracy (might be empty).
1335
 * @return new Transformation.
1336
 */
1337
TransformationNNPtr Transformation::createVerticalOffset(
1338
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1339
    const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight,
1340
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1341
0
    return create(properties, sourceCRSIn, targetCRSIn, nullptr,
1342
0
                  createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET),
1343
0
                  VectorOfParameters{createOpParamNameEPSGCode(
1344
0
                      EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
1345
0
                  VectorOfValues{offsetHeight}, accuracies);
1346
0
}
1347
1348
// ---------------------------------------------------------------------------
1349
1350
/** \brief Instantiate a transformation based on the Change of Vertical Unit
1351
 * method.
1352
 *
1353
 * This method is defined as
1354
 * <a href="https://epsg.org/coord-operation-method_1069/index.html">
1355
 * EPSG:1069</a> [DEPRECATED].
1356
 *
1357
 * @param properties See \ref general_properties of the conversion. If the name
1358
 * is not provided, it is automatically set.
1359
 * @param sourceCRSIn Source CRS.
1360
 * @param targetCRSIn Target CRS.
1361
 * @param factor Conversion factor
1362
 * @param accuracies Vector of positional accuracy (might be empty).
1363
 * @return a new Transformation.
1364
 */
1365
TransformationNNPtr Transformation::createChangeVerticalUnit(
1366
    const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1367
    const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor,
1368
0
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1369
0
    return create(
1370
0
        properties, sourceCRSIn, targetCRSIn, nullptr,
1371
0
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
1372
0
        VectorOfParameters{
1373
0
            createOpParamNameEPSGCode(
1374
0
                EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
1375
0
        },
1376
0
        VectorOfValues{
1377
0
            factor,
1378
0
        },
1379
0
        accuracies);
1380
0
}
1381
1382
// ---------------------------------------------------------------------------
1383
1384
// to avoid -0...
1385
0
static double negate(double val) {
1386
0
    if (val != 0) {
1387
0
        return -val;
1388
0
    }
1389
0
    return 0.0;
1390
0
}
1391
1392
// ---------------------------------------------------------------------------
1393
1394
static CoordinateOperationPtr
1395
0
createApproximateInverseIfPossible(const Transformation *op) {
1396
0
    bool sevenParamsTransform = false;
1397
0
    bool fifteenParamsTransform = false;
1398
0
    const auto &method = op->method();
1399
0
    const auto &methodName = method->nameStr();
1400
0
    const int methodEPSGCode = method->getEPSGCode();
1401
0
    const auto paramCount = op->parameterValues().size();
1402
0
    const bool isPositionVector =
1403
0
        ci_find(methodName, "Position Vector") != std::string::npos;
1404
0
    const bool isCoordinateFrame =
1405
0
        ci_find(methodName, "Coordinate Frame") != std::string::npos;
1406
1407
    // See end of "2.4.3.3 Helmert 7-parameter transformations"
1408
    // in EPSG 7-2 guidance
1409
    // For practical purposes, the inverse of 7- or 15-parameters Helmert
1410
    // can be obtained by using the forward method with all parameters
1411
    // negated
1412
    // (except reference epoch!)
1413
    // So for WKT export use that. But for PROJ string, we use the +inv flag
1414
    // so as to get "perfect" round-tripability.
1415
0
    if ((paramCount == 7 && isCoordinateFrame &&
1416
0
         !isTimeDependent(methodName)) ||
1417
0
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
1418
0
        methodEPSGCode ==
1419
0
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC ||
1420
0
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
1421
0
        methodEPSGCode ==
1422
0
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D ||
1423
0
        methodEPSGCode ==
1424
0
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D ||
1425
0
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D ||
1426
0
        methodEPSGCode ==
1427
0
            EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND) {
1428
0
        sevenParamsTransform = true;
1429
0
    } else if (
1430
0
        (paramCount == 15 && isCoordinateFrame &&
1431
0
         isTimeDependent(methodName)) ||
1432
0
        methodEPSGCode ==
1433
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
1434
0
        methodEPSGCode ==
1435
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
1436
0
        methodEPSGCode ==
1437
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
1438
0
        fifteenParamsTransform = true;
1439
0
    } else if ((paramCount == 7 && isPositionVector &&
1440
0
                !isTimeDependent(methodName)) ||
1441
0
               methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
1442
0
               methodEPSGCode ==
1443
0
                   EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
1444
0
               methodEPSGCode ==
1445
0
                   EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
1446
0
        sevenParamsTransform = true;
1447
0
    } else if (
1448
0
        (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) ||
1449
0
        methodEPSGCode ==
1450
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
1451
0
        methodEPSGCode ==
1452
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
1453
0
        methodEPSGCode ==
1454
0
            EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
1455
0
        fifteenParamsTransform = true;
1456
0
    }
1457
0
    if (sevenParamsTransform || fifteenParamsTransform) {
1458
0
        double neg_x = negate(op->parameterValueNumericAsSI(
1459
0
            EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
1460
0
        double neg_y = negate(op->parameterValueNumericAsSI(
1461
0
            EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
1462
0
        double neg_z = negate(op->parameterValueNumericAsSI(
1463
0
            EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
1464
0
        double neg_rx = negate(
1465
0
            op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
1466
0
                                      common::UnitOfMeasure::ARC_SECOND));
1467
0
        double neg_ry = negate(
1468
0
            op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
1469
0
                                      common::UnitOfMeasure::ARC_SECOND));
1470
0
        double neg_rz = negate(
1471
0
            op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
1472
0
                                      common::UnitOfMeasure::ARC_SECOND));
1473
0
        double neg_scaleDiff = negate(op->parameterValueNumeric(
1474
0
            EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
1475
0
            common::UnitOfMeasure::PARTS_PER_MILLION));
1476
0
        auto methodProperties = util::PropertyMap().set(
1477
0
            common::IdentifiedObject::NAME_KEY, methodName);
1478
0
        int method_epsg_code = method->getEPSGCode();
1479
0
        if (method_epsg_code) {
1480
0
            methodProperties
1481
0
                .set(metadata::Identifier::CODESPACE_KEY,
1482
0
                     metadata::Identifier::EPSG)
1483
0
                .set(metadata::Identifier::CODE_KEY, method_epsg_code);
1484
0
        }
1485
0
        bool exactInverse =
1486
0
            (neg_rx == 0 && neg_ry == 0 && neg_rz == 0 && neg_scaleDiff == 0);
1487
0
        if (fifteenParamsTransform) {
1488
0
            double neg_rate_x = negate(op->parameterValueNumeric(
1489
0
                EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
1490
0
                common::UnitOfMeasure::METRE_PER_YEAR));
1491
0
            double neg_rate_y = negate(op->parameterValueNumeric(
1492
0
                EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
1493
0
                common::UnitOfMeasure::METRE_PER_YEAR));
1494
0
            double neg_rate_z = negate(op->parameterValueNumeric(
1495
0
                EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
1496
0
                common::UnitOfMeasure::METRE_PER_YEAR));
1497
0
            double neg_rate_rx = negate(op->parameterValueNumeric(
1498
0
                EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
1499
0
                common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
1500
0
            double neg_rate_ry = negate(op->parameterValueNumeric(
1501
0
                EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
1502
0
                common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
1503
0
            double neg_rate_rz = negate(op->parameterValueNumeric(
1504
0
                EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
1505
0
                common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
1506
0
            double neg_rate_scaleDiff = negate(op->parameterValueNumeric(
1507
0
                EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
1508
0
                common::UnitOfMeasure::PPM_PER_YEAR));
1509
0
            double referenceEpochYear =
1510
0
                op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
1511
0
                                          common::UnitOfMeasure::YEAR);
1512
0
            exactInverse &= (neg_rate_rx == 0 && neg_rate_ry == 0 &&
1513
0
                             neg_rate_rz == 0 && neg_rate_scaleDiff == 0);
1514
0
            return util::nn_static_pointer_cast<CoordinateOperation>(
1515
0
                       createFifteenParamsTransform(
1516
0
                           createPropertiesForInverse(op, false, !exactInverse),
1517
0
                           methodProperties, op->targetCRS(), op->sourceCRS(),
1518
0
                           neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
1519
0
                           neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z,
1520
0
                           neg_rate_rx, neg_rate_ry, neg_rate_rz,
1521
0
                           neg_rate_scaleDiff, referenceEpochYear,
1522
0
                           op->coordinateOperationAccuracies()))
1523
0
                .as_nullable();
1524
0
        } else {
1525
0
            return util::nn_static_pointer_cast<CoordinateOperation>(
1526
0
                       createSevenParamsTransform(
1527
0
                           createPropertiesForInverse(op, false, !exactInverse),
1528
0
                           methodProperties, op->targetCRS(), op->sourceCRS(),
1529
0
                           neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
1530
0
                           neg_scaleDiff, op->coordinateOperationAccuracies()))
1531
0
                .as_nullable();
1532
0
        }
1533
0
    }
1534
1535
0
    return nullptr;
1536
0
}
1537
1538
// ---------------------------------------------------------------------------
1539
1540
//! @cond Doxygen_Suppress
1541
TransformationNNPtr
1542
Transformation::Private::registerInv(const Transformation *thisIn,
1543
0
                                     TransformationNNPtr invTransform) {
1544
0
    invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable();
1545
0
    invTransform->setHasBallparkTransformation(
1546
0
        thisIn->hasBallparkTransformation());
1547
0
    invTransform->setRequiresPerCoordinateInputTime(
1548
0
        thisIn->requiresPerCoordinateInputTime());
1549
0
    return invTransform;
1550
0
}
1551
//! @endcond
1552
1553
// ---------------------------------------------------------------------------
1554
1555
13
CoordinateOperationNNPtr Transformation::inverse() const {
1556
13
    return inverseAsTransformation();
1557
13
}
1558
1559
// ---------------------------------------------------------------------------
1560
1561
26
TransformationNNPtr Transformation::inverseAsTransformation() const {
1562
1563
26
    if (d->forwardOperation_) {
1564
0
        return NN_NO_CHECK(d->forwardOperation_);
1565
0
    }
1566
26
    const auto &l_method = method();
1567
26
    const auto &methodName = l_method->nameStr();
1568
26
    const int methodEPSGCode = l_method->getEPSGCode();
1569
26
    const auto &l_sourceCRS = sourceCRS();
1570
26
    const auto &l_targetCRS = targetCRS();
1571
1572
    // For geocentric translation, the inverse is exactly the negation of
1573
    // the parameters.
1574
26
    if ((ci_find(methodName, "Geocentric translations") != std::string::npos &&
1575
0
         ci_find(methodName, "grid") == std::string::npos) ||
1576
26
        methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
1577
26
        methodEPSGCode ==
1578
26
            EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
1579
26
        methodEPSGCode ==
1580
26
            EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
1581
0
        double x =
1582
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
1583
0
        double y =
1584
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
1585
0
        double z =
1586
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
1587
0
        auto properties = createPropertiesForInverse(this, false, false);
1588
0
        return Private::registerInv(
1589
0
            this, create(properties, l_targetCRS, l_sourceCRS, nullptr,
1590
0
                         createMethodMapNameEPSGCode(
1591
0
                             useOperationMethodEPSGCodeIfPresent(
1592
0
                                 properties, methodEPSGCode)),
1593
0
                         VectorOfParameters{
1594
0
                             createOpParamNameEPSGCode(
1595
0
                                 EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
1596
0
                             createOpParamNameEPSGCode(
1597
0
                                 EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
1598
0
                             createOpParamNameEPSGCode(
1599
0
                                 EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
1600
0
                         },
1601
0
                         createParams(common::Length(negate(x)),
1602
0
                                      common::Length(negate(y)),
1603
0
                                      common::Length(negate(z))),
1604
0
                         coordinateOperationAccuracies()));
1605
0
    }
1606
1607
26
    if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
1608
26
        methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
1609
0
        double x =
1610
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
1611
0
        double y =
1612
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
1613
0
        double z =
1614
0
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
1615
0
        double da = parameterValueNumericAsSI(
1616
0
            EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
1617
0
        double df = parameterValueNumericAsSI(
1618
0
            EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
1619
1620
0
        if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
1621
0
            return Private::registerInv(
1622
0
                this,
1623
0
                createAbridgedMolodensky(
1624
0
                    createPropertiesForInverse(this, false, false), l_targetCRS,
1625
0
                    l_sourceCRS, negate(x), negate(y), negate(z), negate(da),
1626
0
                    negate(df), coordinateOperationAccuracies()));
1627
0
        } else {
1628
0
            return Private::registerInv(
1629
0
                this,
1630
0
                createMolodensky(createPropertiesForInverse(this, false, false),
1631
0
                                 l_targetCRS, l_sourceCRS, negate(x), negate(y),
1632
0
                                 negate(z), negate(da), negate(df),
1633
0
                                 coordinateOperationAccuracies()));
1634
0
        }
1635
0
    }
1636
1637
26
    if (isLongitudeRotation()) {
1638
0
        const auto &offset =
1639
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1640
0
        const common::Angle newOffset(negate(offset.value()), offset.unit());
1641
0
        return Private::registerInv(
1642
0
            this, createLongitudeRotation(
1643
0
                      createPropertiesForInverse(this, false, false),
1644
0
                      l_targetCRS, l_sourceCRS, newOffset));
1645
0
    }
1646
1647
26
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
1648
0
        const auto &offsetLat =
1649
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
1650
0
        const common::Angle newOffsetLat(negate(offsetLat.value()),
1651
0
                                         offsetLat.unit());
1652
1653
0
        const auto &offsetLong =
1654
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1655
0
        const common::Angle newOffsetLong(negate(offsetLong.value()),
1656
0
                                          offsetLong.unit());
1657
1658
0
        return Private::registerInv(
1659
0
            this, createGeographic2DOffsets(
1660
0
                      createPropertiesForInverse(this, false, false),
1661
0
                      l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
1662
0
                      coordinateOperationAccuracies()));
1663
0
    }
1664
1665
26
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
1666
0
        const auto &offsetLat =
1667
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
1668
0
        const common::Angle newOffsetLat(negate(offsetLat.value()),
1669
0
                                         offsetLat.unit());
1670
1671
0
        const auto &offsetLong =
1672
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1673
0
        const common::Angle newOffsetLong(negate(offsetLong.value()),
1674
0
                                          offsetLong.unit());
1675
1676
0
        const auto &offsetHeight =
1677
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
1678
0
        const common::Length newOffsetHeight(negate(offsetHeight.value()),
1679
0
                                             offsetHeight.unit());
1680
1681
0
        return Private::registerInv(
1682
0
            this, createGeographic3DOffsets(
1683
0
                      createPropertiesForInverse(this, false, false),
1684
0
                      l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
1685
0
                      newOffsetHeight, coordinateOperationAccuracies()));
1686
0
    }
1687
1688
26
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
1689
0
        const auto &offsetLat =
1690
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
1691
0
        const common::Angle newOffsetLat(negate(offsetLat.value()),
1692
0
                                         offsetLat.unit());
1693
1694
0
        const auto &offsetLong =
1695
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1696
0
        const common::Angle newOffsetLong(negate(offsetLong.value()),
1697
0
                                          offsetLong.unit());
1698
1699
0
        const auto &offsetHeight =
1700
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_HEIGHT);
1701
0
        const common::Length newOffsetHeight(negate(offsetHeight.value()),
1702
0
                                             offsetHeight.unit());
1703
1704
0
        return Private::registerInv(
1705
0
            this, createGeographic2DWithHeightOffsets(
1706
0
                      createPropertiesForInverse(this, false, false),
1707
0
                      l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
1708
0
                      newOffsetHeight, coordinateOperationAccuracies()));
1709
0
    }
1710
1711
26
    if (methodEPSGCode == EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS) {
1712
0
        const auto &eastingOffset =
1713
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_EASTING_OFFSET);
1714
0
        const common::Length newEastingOffset(negate(eastingOffset.value()),
1715
0
                                              eastingOffset.unit());
1716
1717
0
        const auto &northingOffset =
1718
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_NORTHING_OFFSET);
1719
0
        const common::Length newNorthingOffset(negate(northingOffset.value()),
1720
0
                                               northingOffset.unit());
1721
0
        return Private::registerInv(
1722
0
            this, createCartesianGridOffsets(
1723
0
                      createPropertiesForInverse(this, false, false),
1724
0
                      l_targetCRS, l_sourceCRS, newEastingOffset,
1725
0
                      newNorthingOffset, coordinateOperationAccuracies()));
1726
0
    }
1727
1728
26
    if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
1729
1730
0
        const auto &offsetHeight =
1731
0
            parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
1732
0
        const common::Length newOffsetHeight(negate(offsetHeight.value()),
1733
0
                                             offsetHeight.unit());
1734
1735
0
        return Private::registerInv(
1736
0
            this,
1737
0
            createVerticalOffset(createPropertiesForInverse(this, false, false),
1738
0
                                 l_targetCRS, l_sourceCRS, newOffsetHeight,
1739
0
                                 coordinateOperationAccuracies()));
1740
0
    }
1741
1742
26
    if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
1743
0
        const double convFactor = parameterValueNumericAsSI(
1744
0
            EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
1745
        // coverity[divide_by_zero]
1746
0
        const double invConvFactor = convFactor == 0.0 ? 0.0 : 1.0 / convFactor;
1747
0
        return Private::registerInv(
1748
0
            this, createChangeVerticalUnit(
1749
0
                      createPropertiesForInverse(this, false, false),
1750
0
                      l_targetCRS, l_sourceCRS, common::Scale(invConvFactor),
1751
0
                      coordinateOperationAccuracies()));
1752
0
    }
1753
1754
#ifdef notdef
1755
    // We don't need that currently, but we might...
1756
    if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
1757
        return Private::registerInv(
1758
            this,
1759
            createHeightDepthReversal(
1760
                createPropertiesForInverse(this, false, false), l_targetCRS,
1761
                l_sourceCRS, coordinateOperationAccuracies()));
1762
    }
1763
#endif
1764
1765
26
    return InverseTransformation::create(NN_NO_CHECK(
1766
26
        util::nn_dynamic_pointer_cast<Transformation>(shared_from_this())));
1767
26
}
1768
1769
// ---------------------------------------------------------------------------
1770
1771
//! @cond Doxygen_Suppress
1772
1773
// ---------------------------------------------------------------------------
1774
1775
InverseTransformation::InverseTransformation(const TransformationNNPtr &forward)
1776
26
    : Transformation(
1777
26
          forward->targetCRS(), forward->sourceCRS(),
1778
26
          forward->interpolationCRS(),
1779
26
          OperationMethod::create(createPropertiesForInverse(forward->method()),
1780
26
                                  forward->method()->parameters()),
1781
26
          forward->parameterValues(), forward->coordinateOperationAccuracies()),
1782
26
      InverseCoordinateOperation(forward, true) {
1783
26
    setPropertiesFromForward();
1784
26
}
Unexecuted instantiation: osgeo::proj::operation::InverseTransformation::InverseTransformation(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Transformation> > const&)
osgeo::proj::operation::InverseTransformation::InverseTransformation(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::Transformation> > const&)
Line
Count
Source
1776
26
    : Transformation(
1777
26
          forward->targetCRS(), forward->sourceCRS(),
1778
26
          forward->interpolationCRS(),
1779
26
          OperationMethod::create(createPropertiesForInverse(forward->method()),
1780
26
                                  forward->method()->parameters()),
1781
26
          forward->parameterValues(), forward->coordinateOperationAccuracies()),
1782
26
      InverseCoordinateOperation(forward, true) {
1783
26
    setPropertiesFromForward();
1784
26
}
1785
1786
// ---------------------------------------------------------------------------
1787
1788
26
InverseTransformation::~InverseTransformation() = default;
1789
1790
// ---------------------------------------------------------------------------
1791
1792
TransformationNNPtr
1793
26
InverseTransformation::create(const TransformationNNPtr &forward) {
1794
26
    auto conv = util::nn_make_shared<InverseTransformation>(forward);
1795
26
    conv->assignSelf(conv);
1796
26
    return conv;
1797
26
}
1798
1799
// ---------------------------------------------------------------------------
1800
1801
0
TransformationNNPtr InverseTransformation::inverseAsTransformation() const {
1802
0
    return NN_NO_CHECK(
1803
0
        util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_));
1804
0
}
1805
1806
// ---------------------------------------------------------------------------
1807
1808
0
void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const {
1809
1810
0
    auto approxInverse = createApproximateInverseIfPossible(
1811
0
        util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_).get());
1812
0
    if (approxInverse) {
1813
0
        approxInverse->_exportToWKT(formatter);
1814
0
    } else {
1815
0
        Transformation::_exportToWKT(formatter);
1816
0
    }
1817
0
}
1818
1819
// ---------------------------------------------------------------------------
1820
1821
0
CoordinateOperationNNPtr InverseTransformation::_shallowClone() const {
1822
0
    auto op = InverseTransformation::nn_make_shared<InverseTransformation>(
1823
0
        inverseAsTransformation()->shallowClone());
1824
0
    op->assignSelf(op);
1825
0
    op->setCRSs(this, false);
1826
0
    return util::nn_static_pointer_cast<CoordinateOperation>(op);
1827
0
}
1828
1829
//! @endcond
1830
1831
// ---------------------------------------------------------------------------
1832
1833
//! @cond Doxygen_Suppress
1834
0
void Transformation::_exportToWKT(io::WKTFormatter *formatter) const {
1835
0
    exportTransformationToWKT(formatter);
1836
0
}
1837
//! @endcond
1838
1839
// ---------------------------------------------------------------------------
1840
1841
//! @cond Doxygen_Suppress
1842
void Transformation::_exportToJSON(
1843
    io::JSONFormatter *formatter) const // throw(FormattingException)
1844
0
{
1845
0
    auto writer = formatter->writer();
1846
0
    auto objectContext(formatter->MakeObjectContext(
1847
0
        formatter->abridgedTransformation() ? "AbridgedTransformation"
1848
0
                                            : "Transformation",
1849
0
        !identifiers().empty()));
1850
1851
0
    writer->AddObjKey("name");
1852
0
    const auto &l_name = nameStr();
1853
0
    if (l_name.empty()) {
1854
0
        writer->Add("unnamed");
1855
0
    } else {
1856
0
        writer->Add(l_name);
1857
0
    }
1858
1859
0
    if (!formatter->abridgedTransformation()) {
1860
0
        writer->AddObjKey("source_crs");
1861
0
        formatter->setAllowIDInImmediateChild();
1862
0
        sourceCRS()->_exportToJSON(formatter);
1863
1864
0
        writer->AddObjKey("target_crs");
1865
0
        formatter->setAllowIDInImmediateChild();
1866
0
        targetCRS()->_exportToJSON(formatter);
1867
1868
0
        const auto &l_interpolationCRS = interpolationCRS();
1869
0
        if (l_interpolationCRS) {
1870
0
            writer->AddObjKey("interpolation_crs");
1871
0
            formatter->setAllowIDInImmediateChild();
1872
0
            l_interpolationCRS->_exportToJSON(formatter);
1873
0
        }
1874
0
    } else {
1875
0
        if (formatter->abridgedTransformationWriteSourceCRS()) {
1876
0
            writer->AddObjKey("source_crs");
1877
0
            formatter->setAllowIDInImmediateChild();
1878
0
            sourceCRS()->_exportToJSON(formatter);
1879
0
        }
1880
0
    }
1881
1882
0
    writer->AddObjKey("method");
1883
0
    formatter->setOmitTypeInImmediateChild();
1884
0
    formatter->setAllowIDInImmediateChild();
1885
0
    method()->_exportToJSON(formatter);
1886
1887
0
    writer->AddObjKey("parameters");
1888
0
    {
1889
0
        auto parametersContext(writer->MakeArrayContext(false));
1890
0
        for (const auto &genOpParamvalue : parameterValues()) {
1891
0
            formatter->setAllowIDInImmediateChild();
1892
0
            formatter->setOmitTypeInImmediateChild();
1893
0
            genOpParamvalue->_exportToJSON(formatter);
1894
0
        }
1895
0
    }
1896
1897
0
    if (!formatter->abridgedTransformation()) {
1898
0
        if (!coordinateOperationAccuracies().empty()) {
1899
0
            writer->AddObjKey("accuracy");
1900
0
            writer->Add(coordinateOperationAccuracies()[0]->value());
1901
0
        }
1902
0
    }
1903
1904
0
    if (formatter->abridgedTransformation()) {
1905
0
        if (formatter->outputId()) {
1906
0
            formatID(formatter);
1907
0
        }
1908
0
    } else {
1909
0
        ObjectUsage::baseExportToJSON(formatter);
1910
0
    }
1911
0
}
1912
1913
//! @endcond
1914
1915
// ---------------------------------------------------------------------------
1916
1917
void Transformation::_exportToPROJString(
1918
    io::PROJStringFormatter *formatter) const // throw(FormattingException)
1919
185
{
1920
185
    if (formatter->convention() ==
1921
185
        io::PROJStringFormatter::Convention::PROJ_4) {
1922
0
        throw io::FormattingException(
1923
0
            "Transformation cannot be exported as a PROJ.4 string");
1924
0
    }
1925
1926
185
    if (exportToPROJStringGeneric(formatter)) {
1927
185
        return;
1928
185
    }
1929
1930
0
    throw io::FormattingException("Unimplemented " + nameStr());
1931
185
}
1932
1933
} // namespace operation
1934
NS_PROJ_END