Coverage Report

Created: 2025-07-23 06:58

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