Coverage Report

Created: 2026-05-30 06:46

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