Coverage Report

Created: 2026-08-13 07:07

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
418k
    : SingleOperation(methodIn), d(std::make_unique<Private>()) {
95
418k
    setParameterValues(values);
96
418k
    setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
97
418k
    setAccuracies(accuracies);
98
418k
}
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
58.6k
    : SingleOperation(methodIn), d(std::make_unique<Private>()) {
95
58.6k
    setParameterValues(values);
96
58.6k
    setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
97
58.6k
    setAccuracies(accuracies);
98
58.6k
}
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
359k
    : SingleOperation(methodIn), d(std::make_unique<Private>()) {
95
359k
    setParameterValues(values);
96
359k
    setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
97
359k
    setAccuracies(accuracies);
98
359k
}
99
100
// ---------------------------------------------------------------------------
101
102
//! @cond Doxygen_Suppress
103
553k
Transformation::~Transformation() = default;
104
//! @endcond
105
106
// ---------------------------------------------------------------------------
107
108
Transformation::Transformation(const Transformation &other)
109
135k
    : CoordinateOperation(other), SingleOperation(other),
110
135k
      d(std::make_unique<Private>(*other.d)) {}
Unexecuted instantiation: osgeo::proj::operation::Transformation::Transformation(osgeo::proj::operation::Transformation const&)
osgeo::proj::operation::Transformation::Transformation(osgeo::proj::operation::Transformation const&)
Line
Count
Source
109
135k
    : CoordinateOperation(other), SingleOperation(other),
110
135k
      d(std::make_unique<Private>(*other.d)) {}
111
112
// ---------------------------------------------------------------------------
113
114
/** \brief Return the source crs::CRS of the transformation.
115
 *
116
 * @return the source CRS.
117
 */
118
196k
const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_PURE_DEFN {
119
196k
    return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_;
120
196k
}
121
122
// ---------------------------------------------------------------------------
123
124
/** \brief Return the target crs::CRS of the transformation.
125
 *
126
 * @return the target CRS.
127
 */
128
195k
const crs::CRSNNPtr &Transformation::targetCRS() PROJ_PURE_DEFN {
129
195k
    return CoordinateOperation::getPrivate()->strongRef_->targetCRS_;
130
195k
}
131
132
// ---------------------------------------------------------------------------
133
134
//! @cond Doxygen_Suppress
135
135k
TransformationNNPtr Transformation::shallowClone() const {
136
135k
    auto transf = Transformation::nn_make_shared<Transformation>(*this);
137
135k
    transf->assignSelf(transf);
138
135k
    transf->setCRSs(this, false);
139
135k
    if (transf->d->forwardOperation_) {
140
20.0k
        transf->d->forwardOperation_ =
141
20.0k
            transf->d->forwardOperation_->shallowClone().as_nullable();
142
20.0k
    }
143
135k
    return transf;
144
135k
}
145
146
36.5k
CoordinateOperationNNPtr Transformation::_shallowClone() const {
147
36.5k
    return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
148
36.5k
}
149
150
// ---------------------------------------------------------------------------
151
152
TransformationNNPtr
153
Transformation::promoteTo3D(const std::string &,
154
12
                            const io::DatabaseContextPtr &dbContext) const {
155
12
    auto transf = shallowClone();
156
12
    transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext),
157
12
                    targetCRS()->promoteTo3D(std::string(), dbContext),
158
12
                    interpolationCRS());
159
12
    return transf;
160
12
}
161
162
// ---------------------------------------------------------------------------
163
164
TransformationNNPtr
165
Transformation::demoteTo2D(const std::string &,
166
234
                           const io::DatabaseContextPtr &dbContext) const {
167
234
    auto transf = shallowClone();
168
234
    transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext),
169
234
                    targetCRS()->demoteTo2D(std::string(), dbContext),
170
234
                    interpolationCRS());
171
234
    return transf;
172
234
}
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
3.75k
{
197
    // GDAL WKT1 assumes EPSG:9606 / Position Vector convention
198
199
3.75k
    bool sevenParamsTransform = false;
200
3.75k
    bool threeParamsTransform = false;
201
3.75k
    bool invertRotSigns = false;
202
3.75k
    const auto &l_method = method();
203
3.75k
    const auto &methodName = l_method->nameStr();
204
3.75k
    const int methodEPSGCode = l_method->getEPSGCode();
205
3.75k
    const auto paramCount = parameterValues().size();
206
3.75k
    if ((paramCount == 7 &&
207
1.27k
         ci_find(methodName, "Coordinate Frame") != std::string::npos) ||
208
3.12k
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
209
3.12k
        methodEPSGCode ==
210
3.12k
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC ||
211
3.12k
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
212
3.12k
        methodEPSGCode ==
213
3.12k
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D ||
214
3.12k
        methodEPSGCode ==
215
3.12k
            EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D ||
216
3.12k
        methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D ||
217
3.12k
        methodEPSGCode ==
218
3.12k
            EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND) {
219
623
        sevenParamsTransform = true;
220
623
        invertRotSigns = true;
221
3.12k
    } else if ((paramCount == 7 &&
222
653
                ci_find(methodName, "Position Vector") != std::string::npos) ||
223
2.47k
               methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
224
2.47k
               methodEPSGCode ==
225
2.47k
                   EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
226
2.47k
               methodEPSGCode ==
227
2.47k
                   EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
228
653
        sevenParamsTransform = true;
229
653
        invertRotSigns = false;
230
2.47k
    } else if ((paramCount == 3 &&
231
2.15k
                ci_find(methodName, "Geocentric translations") !=
232
2.15k
                    std::string::npos) ||
233
319
               methodEPSGCode ==
234
319
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
235
319
               methodEPSGCode ==
236
319
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
237
319
               methodEPSGCode ==
238
2.15k
                   EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
239
2.15k
        threeParamsTransform = true;
240
2.15k
    }
241
242
3.75k
    if (threeParamsTransform || sevenParamsTransform) {
243
3.43k
        std::vector<double> params(7, 0.0);
244
3.43k
        bool foundX = false;
245
3.43k
        bool foundY = false;
246
3.43k
        bool foundZ = false;
247
3.43k
        bool foundRotX = false;
248
3.43k
        bool foundRotY = false;
249
3.43k
        bool foundRotZ = false;
250
3.43k
        bool foundScale = false;
251
3.43k
        const double rotSign = invertRotSigns ? -1.0 : 1.0;
252
253
3.82k
        const auto fixNegativeZero = [](double x) {
254
3.82k
            if (x == 0.0)
255
342
                return 0.0;
256
3.48k
            return x;
257
3.82k
        };
258
259
15.3k
        for (const auto &genOpParamvalue : parameterValues()) {
260
15.3k
            auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
261
15.3k
                genOpParamvalue.get());
262
15.3k
            if (opParamvalue) {
263
15.3k
                const auto &parameter = opParamvalue->parameter();
264
15.3k
                const auto epsg_code = parameter->getEPSGCode();
265
15.3k
                const auto &l_parameterValue = opParamvalue->parameterValue();
266
15.3k
                if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
267
15.3k
                    const auto &measure = l_parameterValue->value();
268
15.3k
                    if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
269
3.43k
                        params[0] = measure.getSIValue();
270
3.43k
                        foundX = true;
271
11.9k
                    } else if (epsg_code ==
272
11.9k
                               EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
273
3.43k
                        params[1] = measure.getSIValue();
274
3.43k
                        foundY = true;
275
8.53k
                    } else if (epsg_code ==
276
8.53k
                               EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
277
3.43k
                        params[2] = measure.getSIValue();
278
3.43k
                        foundZ = true;
279
5.10k
                    } else if (epsg_code ==
280
5.10k
                               EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
281
1.27k
                        params[3] = fixNegativeZero(
282
1.27k
                            rotSign * measure.convertToUnit(
283
1.27k
                                          common::UnitOfMeasure::ARC_SECOND));
284
1.27k
                        foundRotX = true;
285
3.82k
                    } else if (epsg_code ==
286
3.82k
                               EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
287
1.27k
                        params[4] = fixNegativeZero(
288
1.27k
                            rotSign * measure.convertToUnit(
289
1.27k
                                          common::UnitOfMeasure::ARC_SECOND));
290
1.27k
                        foundRotY = true;
291
2.55k
                    } else if (epsg_code ==
292
2.55k
                               EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
293
1.27k
                        params[5] = fixNegativeZero(
294
1.27k
                            rotSign * measure.convertToUnit(
295
1.27k
                                          common::UnitOfMeasure::ARC_SECOND));
296
1.27k
                        foundRotZ = true;
297
1.27k
                    } else if (epsg_code ==
298
1.27k
                               EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
299
1.27k
                        params[6] = measure.convertToUnit(
300
1.27k
                            common::UnitOfMeasure::PARTS_PER_MILLION);
301
1.27k
                        foundScale = true;
302
1.27k
                    }
303
15.3k
                }
304
15.3k
            }
305
15.3k
        }
306
3.43k
        if (foundX && foundY && foundZ &&
307
3.43k
            (threeParamsTransform ||
308
3.43k
             (foundRotX && foundRotY && foundRotZ && foundScale))) {
309
3.43k
            return params;
310
3.43k
        } else {
311
0
            if (!canThrowException)
312
0
                return {};
313
0
            throw io::FormattingException(
314
0
                "Missing required parameter values in transformation");
315
0
        }
316
3.43k
    }
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
319
    if (!canThrowException)
338
319
        return {};
339
0
    throw io::FormattingException(
340
0
        "Transformation cannot be formatted as WKT1 TOWGS84 parameters");
341
319
}
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
359k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
365
359k
    if (methodIn->parameters().size() != values.size()) {
366
0
        throw InvalidOperation(
367
0
            "Inconsistent number of parameters and parameter values");
368
0
    }
369
359k
    auto transf = Transformation::nn_make_shared<Transformation>(
370
359k
        sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values,
371
359k
        accuracies);
372
359k
    transf->assignSelf(transf);
373
359k
    transf->setProperties(properties);
374
359k
    std::string name;
375
359k
    if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) &&
376
359k
        ci_find(name, "ballpark") != std::string::npos) {
377
35.0k
        transf->setHasBallparkTransformation(true);
378
35.0k
    }
379
359k
    return transf;
380
359k
}
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
359k
{
413
359k
    OperationMethodNNPtr op(
414
359k
        OperationMethod::create(propertiesOperationMethod, parameters));
415
416
359k
    if (parameters.size() != values.size()) {
417
0
        throw InvalidOperation(
418
0
            "Inconsistent number of parameters and parameter values");
419
0
    }
420
359k
    std::vector<GeneralParameterValueNNPtr> generalParameterValues;
421
359k
    generalParameterValues.reserve(values.size());
422
1.15M
    for (size_t i = 0; i < values.size(); i++) {
423
795k
        generalParameterValues.push_back(
424
795k
            OperationParameterValue::create(parameters[i], values[i]));
425
795k
    }
426
359k
    return create(propertiesTransformation, sourceCRSIn, targetCRSIn,
427
359k
                  interpolationCRSIn, op, generalParameterValues, accuracies);
428
359k
}
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
109
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
444
109
    return Transformation::create(
445
109
        properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
446
109
        VectorOfParameters{
447
109
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
448
109
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
449
109
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
450
109
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
451
109
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
452
109
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
453
109
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
454
109
        },
455
109
        createParams(common::Length(translationXMetre),
456
109
                     common::Length(translationYMetre),
457
109
                     common::Length(translationZMetre),
458
109
                     common::Angle(rotationXArcSecond,
459
109
                                   common::UnitOfMeasure::ARC_SECOND),
460
109
                     common::Angle(rotationYArcSecond,
461
109
                                   common::UnitOfMeasure::ARC_SECOND),
462
109
                     common::Angle(rotationZArcSecond,
463
109
                                   common::UnitOfMeasure::ARC_SECOND),
464
109
                     common::Scale(scaleDifferencePPM,
465
109
                                   common::UnitOfMeasure::PARTS_PER_MILLION)),
466
109
        accuracies);
467
109
}
468
469
// ---------------------------------------------------------------------------
470
471
static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn,
472
                                  const crs::CRSNNPtr &targetCRSIn,
473
                                  bool &isGeocentric, bool &isGeog2D,
474
15.3k
                                  bool &isGeog3D) {
475
15.3k
    auto sourceCRSGeod =
476
15.3k
        dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get());
477
15.3k
    auto targetCRSGeod =
478
15.3k
        dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get());
479
15.3k
    isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() &&
480
14.1k
                   targetCRSGeod && targetCRSGeod->isGeocentric();
481
15.3k
    if (isGeocentric) {
482
14.1k
        isGeog2D = false;
483
14.1k
        isGeog3D = false;
484
14.1k
        return;
485
14.1k
    }
486
1.17k
    isGeocentric = false;
487
488
1.17k
    auto sourceCRSGeog =
489
1.17k
        dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get());
490
1.17k
    auto targetCRSGeog =
491
1.17k
        dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get());
492
1.17k
    if (!(sourceCRSGeog ||
493
32
          (sourceCRSGeod && sourceCRSGeod->isSphericalPlanetocentric())) ||
494
1.17k
        !(targetCRSGeog ||
495
0
          (targetCRSGeod && targetCRSGeod->isSphericalPlanetocentric()))) {
496
0
        throw InvalidOperation("Inconsistent CRS type");
497
0
    }
498
1.17k
    const auto nSrcAxisCount =
499
1.17k
        sourceCRSGeod->coordinateSystem()->axisList().size();
500
1.17k
    const auto nTargetAxisCount =
501
1.17k
        targetCRSGeod->coordinateSystem()->axisList().size();
502
1.17k
    isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2;
503
1.17k
    isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2;
504
1.17k
}
505
506
// ---------------------------------------------------------------------------
507
508
static int
509
useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties,
510
74.1k
                                    int nDefaultOperationMethodEPSGCode) {
511
74.1k
    const auto *operationMethodEPSGCode =
512
74.1k
        properties.get("OPERATION_METHOD_EPSG_CODE");
513
74.1k
    if (operationMethodEPSGCode) {
514
58.7k
        const auto boxedValue = dynamic_cast<const util::BoxedValue *>(
515
58.7k
            (*operationMethodEPSGCode).get());
516
58.7k
        if (boxedValue &&
517
58.7k
            boxedValue->type() == util::BoxedValue::Type::INTEGER) {
518
58.7k
            return boxedValue->integerValue();
519
58.7k
        }
520
58.7k
    }
521
15.3k
    return nDefaultOperationMethodEPSGCode;
522
74.1k
}
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
15.2k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
544
15.2k
    bool isGeocentric;
545
15.2k
    bool isGeog2D;
546
15.2k
    bool isGeog3D;
547
15.2k
    getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
548
15.2k
                          isGeog3D);
549
15.2k
    return create(
550
15.2k
        properties, sourceCRSIn, targetCRSIn, nullptr,
551
15.2k
        createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
552
15.2k
            properties,
553
15.2k
            isGeocentric ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC
554
15.2k
            : isGeog2D
555
1.07k
                ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D
556
1.07k
                : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)),
557
15.2k
        VectorOfParameters{
558
15.2k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
559
15.2k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
560
15.2k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
561
15.2k
        },
562
15.2k
        createParams(common::Length(translationXMetre),
563
15.2k
                     common::Length(translationYMetre),
564
15.2k
                     common::Length(translationZMetre)),
565
15.2k
        accuracies);
566
15.2k
}
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
109
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
601
602
109
    bool isGeocentric;
603
109
    bool isGeog2D;
604
109
    bool isGeog3D;
605
109
    getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
606
109
                          isGeog3D);
607
109
    return createSevenParamsTransform(
608
109
        properties,
609
109
        createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
610
109
            properties,
611
109
            isGeocentric ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC
612
109
            : isGeog2D   ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
613
102
                         : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)),
614
109
        sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
615
109
        translationZMetre, rotationXArcSecond, rotationYArcSecond,
616
109
        rotationZArcSecond, scaleDifferencePPM, accuracies);
617
109
}
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
1.24k
{
1020
1.24k
    if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) {
1021
20
        throw InvalidOperation(
1022
20
            "Invalid number of elements in TOWGS84Parameters");
1023
20
    }
1024
1025
1.22k
    auto transformSourceGeodCRS = sourceCRSIn->extractGeodeticCRS();
1026
1.22k
    if (!transformSourceGeodCRS) {
1027
0
        throw InvalidOperation(
1028
0
            "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation");
1029
0
    }
1030
1031
1.22k
    util::PropertyMap properties;
1032
1.22k
    properties.set(common::IdentifiedObject::NAME_KEY,
1033
1.22k
                   concat("Transformation from ",
1034
1.22k
                          transformSourceGeodCRS->nameStr(), " to WGS84"));
1035
1036
1.22k
    auto targetCRS = dynamic_cast<const crs::GeographicCRS *>(
1037
1.22k
                         transformSourceGeodCRS.get()) ||
1038
80
                             transformSourceGeodCRS->isSphericalPlanetocentric()
1039
1.22k
                         ? util::nn_static_pointer_cast<crs::CRS>(
1040
1.17k
                               crs::GeographicCRS::EPSG_4326)
1041
1.22k
                         : util::nn_static_pointer_cast<crs::CRS>(
1042
48
                               crs::GeodeticCRS::EPSG_4978);
1043
1044
1.22k
    crs::CRSNNPtr transformSourceCRS = NN_NO_CHECK(transformSourceGeodCRS);
1045
1.22k
    if (TOWGS84Parameters.size() == 3) {
1046
1.11k
        return createGeocentricTranslations(
1047
1.11k
            properties, transformSourceCRS, targetCRS, TOWGS84Parameters[0],
1048
1.11k
            TOWGS84Parameters[1], TOWGS84Parameters[2], {});
1049
1.11k
    }
1050
1051
109
    return createPositionVector(
1052
109
        properties, transformSourceCRS, targetCRS, TOWGS84Parameters[0],
1053
109
        TOWGS84Parameters[1], TOWGS84Parameters[2], TOWGS84Parameters[3],
1054
109
        TOWGS84Parameters[4], TOWGS84Parameters[5], TOWGS84Parameters[6], {});
1055
1.22k
}
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
1.89k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1072
1073
1.89k
    return create(properties, sourceCRSIn, targetCRSIn, nullptr,
1074
1.89k
                  createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2),
1075
1.89k
                  VectorOfParameters{createOpParamNameEPSGCode(
1076
1.89k
                      EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)},
1077
1.89k
                  VectorOfValues{ParameterValue::createFilename(filename)},
1078
1.89k
                  accuracies);
1079
1.89k
}
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
7.41k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1089
1090
7.41k
    return Transformation::create(
1091
7.41k
        properties, sourceCRSIn, targetCRSIn, interpolationCRSIn,
1092
7.41k
        util::PropertyMap().set(
1093
7.41k
            common::IdentifiedObject::NAME_KEY,
1094
7.41k
            inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D
1095
7.41k
                    : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D),
1096
7.41k
        VectorOfParameters{createOpParamNameEPSGCode(
1097
7.41k
            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)},
1098
7.41k
        VectorOfValues{ParameterValue::createFilename(filename)}, accuracies);
1099
7.41k
}
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
7.41k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1120
1121
7.41k
    return _createGravityRelatedHeightToGeographic3D(
1122
7.41k
        properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn,
1123
7.41k
        filename, accuracies);
1124
7.41k
}
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
7.27k
buildAccuracyZero() {
1156
7.27k
    return std::vector<metadata::PositionalAccuracyNNPtr>{
1157
7.27k
        metadata::PositionalAccuracy::create("0")};
1158
7.27k
}
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
7.27k
    const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) {
1180
1181
7.27k
    return create(
1182
7.27k
        properties, sourceCRSIn, targetCRSIn, nullptr,
1183
7.27k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION),
1184
7.27k
        VectorOfParameters{
1185
7.27k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
1186
7.27k
        VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero());
1187
7.27k
}
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
22.6k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1211
22.6k
    return create(
1212
22.6k
        properties, sourceCRSIn, targetCRSIn, nullptr,
1213
22.6k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS),
1214
22.6k
        VectorOfParameters{
1215
22.6k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
1216
22.6k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
1217
22.6k
        VectorOfValues{offsetLat, offsetLong}, accuracies);
1218
22.6k
}
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
42.4k
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1243
42.4k
    return create(
1244
42.4k
        properties, sourceCRSIn, targetCRSIn, nullptr,
1245
42.4k
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS),
1246
42.4k
        VectorOfParameters{
1247
42.4k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
1248
42.4k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
1249
42.4k
            createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
1250
42.4k
        VectorOfValues{offsetLat, offsetLong, offsetHeight}, accuracies);
1251
42.4k
}
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
762
    const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1369
762
    return create(
1370
762
        properties, sourceCRSIn, targetCRSIn, nullptr,
1371
762
        createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
1372
762
        VectorOfParameters{
1373
762
            createOpParamNameEPSGCode(
1374
762
                EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
1375
762
        },
1376
762
        VectorOfValues{
1377
762
            factor,
1378
762
        },
1379
762
        accuracies);
1380
762
}
1381
1382
// ---------------------------------------------------------------------------
1383
1384
// to avoid -0...
1385
214k
static double negate(double val) {
1386
214k
    if (val != 0) {
1387
125k
        return -val;
1388
125k
    }
1389
88.2k
    return 0.0;
1390
214k
}
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
75.8k
                                     TransformationNNPtr invTransform) {
1544
75.8k
    invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable();
1545
75.8k
    invTransform->setHasBallparkTransformation(
1546
75.8k
        thisIn->hasBallparkTransformation());
1547
75.8k
    invTransform->setRequiresPerCoordinateInputTime(
1548
75.8k
        thisIn->requiresPerCoordinateInputTime());
1549
75.8k
    return invTransform;
1550
75.8k
}
1551
//! @endcond
1552
1553
// ---------------------------------------------------------------------------
1554
1555
198k
CoordinateOperationNNPtr Transformation::inverse() const {
1556
198k
    return inverseAsTransformation();
1557
198k
}
1558
1559
// ---------------------------------------------------------------------------
1560
1561
223k
TransformationNNPtr Transformation::inverseAsTransformation() const {
1562
1563
223k
    if (d->forwardOperation_) {
1564
90.8k
        return NN_NO_CHECK(d->forwardOperation_);
1565
90.8k
    }
1566
132k
    const auto &l_method = method();
1567
132k
    const auto &methodName = l_method->nameStr();
1568
132k
    const int methodEPSGCode = l_method->getEPSGCode();
1569
132k
    const auto &l_sourceCRS = sourceCRS();
1570
132k
    const auto &l_targetCRS = targetCRS();
1571
1572
    // For geocentric translation, the inverse is exactly the negation of
1573
    // the parameters.
1574
132k
    if ((ci_find(methodName, "Geocentric translations") != std::string::npos &&
1575
58.8k
         ci_find(methodName, "grid") == std::string::npos) ||
1576
73.4k
        methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
1577
73.4k
        methodEPSGCode ==
1578
73.4k
            EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
1579
73.4k
        methodEPSGCode ==
1580
73.4k
            EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
1581
58.7k
        double x =
1582
58.7k
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
1583
58.7k
        double y =
1584
58.7k
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
1585
58.7k
        double z =
1586
58.7k
            parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
1587
58.7k
        auto properties = createPropertiesForInverse(this, false, false);
1588
58.7k
        return Private::registerInv(
1589
58.7k
            this, create(properties, l_targetCRS, l_sourceCRS, nullptr,
1590
58.7k
                         createMethodMapNameEPSGCode(
1591
58.7k
                             useOperationMethodEPSGCodeIfPresent(
1592
58.7k
                                 properties, methodEPSGCode)),
1593
58.7k
                         VectorOfParameters{
1594
58.7k
                             createOpParamNameEPSGCode(
1595
58.7k
                                 EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
1596
58.7k
                             createOpParamNameEPSGCode(
1597
58.7k
                                 EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
1598
58.7k
                             createOpParamNameEPSGCode(
1599
58.7k
                                 EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
1600
58.7k
                         },
1601
58.7k
                         createParams(common::Length(negate(x)),
1602
58.7k
                                      common::Length(negate(y)),
1603
58.7k
                                      common::Length(negate(z))),
1604
58.7k
                         coordinateOperationAccuracies()));
1605
58.7k
    }
1606
1607
73.4k
    if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
1608
73.4k
        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
73.4k
    if (isLongitudeRotation()) {
1638
5.04k
        const auto &offset =
1639
5.04k
            parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1640
5.04k
        const common::Angle newOffset(negate(offset.value()), offset.unit());
1641
5.04k
        return Private::registerInv(
1642
5.04k
            this, createLongitudeRotation(
1643
5.04k
                      createPropertiesForInverse(this, false, false),
1644
5.04k
                      l_targetCRS, l_sourceCRS, newOffset));
1645
5.04k
    }
1646
1647
68.3k
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
1648
2.97k
        const auto &offsetLat =
1649
2.97k
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
1650
2.97k
        const common::Angle newOffsetLat(negate(offsetLat.value()),
1651
2.97k
                                         offsetLat.unit());
1652
1653
2.97k
        const auto &offsetLong =
1654
2.97k
            parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1655
2.97k
        const common::Angle newOffsetLong(negate(offsetLong.value()),
1656
2.97k
                                          offsetLong.unit());
1657
1658
2.97k
        return Private::registerInv(
1659
2.97k
            this, createGeographic2DOffsets(
1660
2.97k
                      createPropertiesForInverse(this, false, false),
1661
2.97k
                      l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
1662
2.97k
                      coordinateOperationAccuracies()));
1663
2.97k
    }
1664
1665
65.4k
    if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
1666
8.90k
        const auto &offsetLat =
1667
8.90k
            parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
1668
8.90k
        const common::Angle newOffsetLat(negate(offsetLat.value()),
1669
8.90k
                                         offsetLat.unit());
1670
1671
8.90k
        const auto &offsetLong =
1672
8.90k
            parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1673
8.90k
        const common::Angle newOffsetLong(negate(offsetLong.value()),
1674
8.90k
                                          offsetLong.unit());
1675
1676
8.90k
        const auto &offsetHeight =
1677
8.90k
            parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
1678
8.90k
        const common::Length newOffsetHeight(negate(offsetHeight.value()),
1679
8.90k
                                             offsetHeight.unit());
1680
1681
8.90k
        return Private::registerInv(
1682
8.90k
            this, createGeographic3DOffsets(
1683
8.90k
                      createPropertiesForInverse(this, false, false),
1684
8.90k
                      l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
1685
8.90k
                      newOffsetHeight, coordinateOperationAccuracies()));
1686
8.90k
    }
1687
1688
56.5k
    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
56.5k
    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
56.5k
    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
56.5k
    if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
1743
129
        const double convFactor = parameterValueNumericAsSI(
1744
129
            EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
1745
        // coverity[divide_by_zero]
1746
129
        const double invConvFactor = convFactor == 0.0 ? 0.0 : 1.0 / convFactor;
1747
129
        return Private::registerInv(
1748
129
            this, createChangeVerticalUnit(
1749
129
                      createPropertiesForInverse(this, false, false),
1750
129
                      l_targetCRS, l_sourceCRS, common::Scale(invConvFactor),
1751
129
                      coordinateOperationAccuracies()));
1752
129
    }
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
56.3k
    return InverseTransformation::create(NN_NO_CHECK(
1766
56.3k
        util::nn_dynamic_pointer_cast<Transformation>(shared_from_this())));
1767
56.5k
}
1768
1769
// ---------------------------------------------------------------------------
1770
1771
//! @cond Doxygen_Suppress
1772
1773
// ---------------------------------------------------------------------------
1774
1775
InverseTransformation::InverseTransformation(const TransformationNNPtr &forward)
1776
58.6k
    : Transformation(
1777
58.6k
          forward->targetCRS(), forward->sourceCRS(),
1778
58.6k
          forward->interpolationCRS(),
1779
58.6k
          OperationMethod::create(createPropertiesForInverse(forward->method()),
1780
58.6k
                                  forward->method()->parameters()),
1781
58.6k
          forward->parameterValues(), forward->coordinateOperationAccuracies()),
1782
58.6k
      InverseCoordinateOperation(forward, true) {
1783
58.6k
    setPropertiesFromForward();
1784
58.6k
}
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
58.6k
    : Transformation(
1777
58.6k
          forward->targetCRS(), forward->sourceCRS(),
1778
58.6k
          forward->interpolationCRS(),
1779
58.6k
          OperationMethod::create(createPropertiesForInverse(forward->method()),
1780
58.6k
                                  forward->method()->parameters()),
1781
58.6k
          forward->parameterValues(), forward->coordinateOperationAccuracies()),
1782
58.6k
      InverseCoordinateOperation(forward, true) {
1783
58.6k
    setPropertiesFromForward();
1784
58.6k
}
1785
1786
// ---------------------------------------------------------------------------
1787
1788
58.6k
InverseTransformation::~InverseTransformation() = default;
1789
1790
// ---------------------------------------------------------------------------
1791
1792
TransformationNNPtr
1793
56.3k
InverseTransformation::create(const TransformationNNPtr &forward) {
1794
56.3k
    auto conv = util::nn_make_shared<InverseTransformation>(forward);
1795
56.3k
    conv->assignSelf(conv);
1796
56.3k
    return conv;
1797
56.3k
}
1798
1799
// ---------------------------------------------------------------------------
1800
1801
2.22k
TransformationNNPtr InverseTransformation::inverseAsTransformation() const {
1802
2.22k
    return NN_NO_CHECK(
1803
2.22k
        util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_));
1804
2.22k
}
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
2.22k
CoordinateOperationNNPtr InverseTransformation::_shallowClone() const {
1822
2.22k
    auto op = InverseTransformation::nn_make_shared<InverseTransformation>(
1823
2.22k
        inverseAsTransformation()->shallowClone());
1824
2.22k
    op->assignSelf(op);
1825
2.22k
    op->setCRSs(this, false);
1826
2.22k
    return util::nn_static_pointer_cast<CoordinateOperation>(op);
1827
2.22k
}
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
428k
{
1920
428k
    if (formatter->convention() ==
1921
428k
        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
428k
    if (exportToPROJStringGeneric(formatter)) {
1927
427k
        return;
1928
427k
    }
1929
1930
425
    throw io::FormattingException("Unimplemented " + nameStr());
1931
428k
}
1932
1933
} // namespace operation
1934
NS_PROJ_END