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 
Transformation(const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const crs::CRSPtr & interpolationCRSIn,const OperationMethodNNPtr & methodIn,const std::vector<GeneralParameterValueNNPtr> & values,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)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     : SingleOperation(methodIn), d(internal::make_unique<Private>()) {
95     setParameterValues(values);
96     setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn);
97     setAccuracies(accuracies);
98 }
99 
100 // ---------------------------------------------------------------------------
101 
102 //! @cond Doxygen_Suppress
103 Transformation::~Transformation() = default;
104 //! @endcond
105 
106 // ---------------------------------------------------------------------------
107 
Transformation(const Transformation & other)108 Transformation::Transformation(const Transformation &other)
109     : CoordinateOperation(other), SingleOperation(other),
110       d(internal::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  */
sourceCRS()118 const crs::CRSNNPtr &Transformation::sourceCRS() PROJ_PURE_DEFN {
119     return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_;
120 }
121 
122 // ---------------------------------------------------------------------------
123 
124 /** \brief Return the target crs::CRS of the transformation.
125  *
126  * @return the target CRS.
127  */
targetCRS()128 const crs::CRSNNPtr &Transformation::targetCRS() PROJ_PURE_DEFN {
129     return CoordinateOperation::getPrivate()->strongRef_->targetCRS_;
130 }
131 
132 // ---------------------------------------------------------------------------
133 
134 //! @cond Doxygen_Suppress
shallowClone() const135 TransformationNNPtr Transformation::shallowClone() const {
136     auto transf = Transformation::nn_make_shared<Transformation>(*this);
137     transf->assignSelf(transf);
138     transf->setCRSs(this, false);
139     if (transf->d->forwardOperation_) {
140         transf->d->forwardOperation_ =
141             transf->d->forwardOperation_->shallowClone().as_nullable();
142     }
143     return transf;
144 }
145 
_shallowClone() const146 CoordinateOperationNNPtr Transformation::_shallowClone() const {
147     return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone());
148 }
149 
150 // ---------------------------------------------------------------------------
151 
152 TransformationNNPtr
promoteTo3D(const std::string &,const io::DatabaseContextPtr & dbContext) const153 Transformation::promoteTo3D(const std::string &,
154                             const io::DatabaseContextPtr &dbContext) const {
155     auto transf = shallowClone();
156     transf->setCRSs(sourceCRS()->promoteTo3D(std::string(), dbContext),
157                     targetCRS()->promoteTo3D(std::string(), dbContext),
158                     interpolationCRS());
159     return transf;
160 }
161 
162 // ---------------------------------------------------------------------------
163 
164 TransformationNNPtr
demoteTo2D(const std::string &,const io::DatabaseContextPtr & dbContext) const165 Transformation::demoteTo2D(const std::string &,
166                            const io::DatabaseContextPtr &dbContext) const {
167     auto transf = shallowClone();
168     transf->setCRSs(sourceCRS()->demoteTo2D(std::string(), dbContext),
169                     targetCRS()->demoteTo2D(std::string(), dbContext),
170                     interpolationCRS());
171     return transf;
172 }
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  * @return a vector of 7 values if valid, otherwise a io::FormattingException
188  * is thrown.
189  * @throws io::FormattingException
190  */
191 std::vector<double>
getTOWGS84Parameters() const192 Transformation::getTOWGS84Parameters() const // throw(io::FormattingException)
193 {
194     // GDAL WKT1 assumes EPSG:9606 / Position Vector convention
195 
196     bool sevenParamsTransform = false;
197     bool threeParamsTransform = false;
198     bool invertRotSigns = false;
199     const auto &l_method = method();
200     const auto &methodName = l_method->nameStr();
201     const int methodEPSGCode = l_method->getEPSGCode();
202     const auto paramCount = parameterValues().size();
203     if ((paramCount == 7 &&
204          ci_find(methodName, "Coordinate Frame") != std::string::npos) ||
205         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
206         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
207         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
208         sevenParamsTransform = true;
209         invertRotSigns = true;
210     } else if ((paramCount == 7 &&
211                 ci_find(methodName, "Position Vector") != std::string::npos) ||
212                methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
213                methodEPSGCode ==
214                    EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
215                methodEPSGCode ==
216                    EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
217         sevenParamsTransform = true;
218         invertRotSigns = false;
219     } else if ((paramCount == 3 &&
220                 ci_find(methodName, "Geocentric translations") !=
221                     std::string::npos) ||
222                methodEPSGCode ==
223                    EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
224                methodEPSGCode ==
225                    EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
226                methodEPSGCode ==
227                    EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
228         threeParamsTransform = true;
229     }
230 
231     if (threeParamsTransform || sevenParamsTransform) {
232         std::vector<double> params(7, 0.0);
233         bool foundX = false;
234         bool foundY = false;
235         bool foundZ = false;
236         bool foundRotX = false;
237         bool foundRotY = false;
238         bool foundRotZ = false;
239         bool foundScale = false;
240         const double rotSign = invertRotSigns ? -1.0 : 1.0;
241 
242         const auto fixNegativeZero = [](double x) {
243             if (x == 0.0)
244                 return 0.0;
245             return x;
246         };
247 
248         for (const auto &genOpParamvalue : parameterValues()) {
249             auto opParamvalue = dynamic_cast<const OperationParameterValue *>(
250                 genOpParamvalue.get());
251             if (opParamvalue) {
252                 const auto &parameter = opParamvalue->parameter();
253                 const auto epsg_code = parameter->getEPSGCode();
254                 const auto &l_parameterValue = opParamvalue->parameterValue();
255                 if (l_parameterValue->type() == ParameterValue::Type::MEASURE) {
256                     const auto &measure = l_parameterValue->value();
257                     if (epsg_code == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) {
258                         params[0] = measure.getSIValue();
259                         foundX = true;
260                     } else if (epsg_code ==
261                                EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) {
262                         params[1] = measure.getSIValue();
263                         foundY = true;
264                     } else if (epsg_code ==
265                                EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) {
266                         params[2] = measure.getSIValue();
267                         foundZ = true;
268                     } else if (epsg_code ==
269                                EPSG_CODE_PARAMETER_X_AXIS_ROTATION) {
270                         params[3] = fixNegativeZero(
271                             rotSign *
272                             measure.convertToUnit(
273                                 common::UnitOfMeasure::ARC_SECOND));
274                         foundRotX = true;
275                     } else if (epsg_code ==
276                                EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) {
277                         params[4] = fixNegativeZero(
278                             rotSign *
279                             measure.convertToUnit(
280                                 common::UnitOfMeasure::ARC_SECOND));
281                         foundRotY = true;
282                     } else if (epsg_code ==
283                                EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) {
284                         params[5] = fixNegativeZero(
285                             rotSign *
286                             measure.convertToUnit(
287                                 common::UnitOfMeasure::ARC_SECOND));
288                         foundRotZ = true;
289                     } else if (epsg_code ==
290                                EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) {
291                         params[6] = measure.convertToUnit(
292                             common::UnitOfMeasure::PARTS_PER_MILLION);
293                         foundScale = true;
294                     }
295                 }
296             }
297         }
298         if (foundX && foundY && foundZ &&
299             (threeParamsTransform ||
300              (foundRotX && foundRotY && foundRotZ && foundScale))) {
301             return params;
302         } else {
303             throw io::FormattingException(
304                 "Missing required parameter values in transformation");
305         }
306     }
307 
308 #if 0
309     if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS ||
310         methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
311         auto offsetLat =
312             parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
313         auto offsetLong =
314             parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
315 
316         auto offsetHeight =
317             parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
318 
319         if (offsetLat.getSIValue() == 0.0 && offsetLong.getSIValue() == 0.0 &&
320             offsetHeight.getSIValue() == 0.0) {
321             std::vector<double> params(7, 0.0);
322             return params;
323         }
324     }
325 #endif
326 
327     throw io::FormattingException(
328         "Transformation cannot be formatted as WKT1 TOWGS84 parameters");
329 }
330 //! @endcond
331 
332 // ---------------------------------------------------------------------------
333 
334 /** \brief Instantiate a transformation from a vector of GeneralParameterValue.
335  *
336  * @param properties See \ref general_properties. At minimum the name should be
337  * defined.
338  * @param sourceCRSIn Source CRS.
339  * @param targetCRSIn Target CRS.
340  * @param interpolationCRSIn Interpolation CRS (might be null)
341  * @param methodIn Operation method.
342  * @param values Vector of GeneralOperationParameterNNPtr.
343  * @param accuracies Vector of positional accuracy (might be empty).
344  * @return new Transformation.
345  * @throws InvalidOperation
346  */
create(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const crs::CRSPtr & interpolationCRSIn,const OperationMethodNNPtr & methodIn,const std::vector<GeneralParameterValueNNPtr> & values,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)347 TransformationNNPtr Transformation::create(
348     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
349     const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
350     const OperationMethodNNPtr &methodIn,
351     const std::vector<GeneralParameterValueNNPtr> &values,
352     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
353     if (methodIn->parameters().size() != values.size()) {
354         throw InvalidOperation(
355             "Inconsistent number of parameters and parameter values");
356     }
357     auto transf = Transformation::nn_make_shared<Transformation>(
358         sourceCRSIn, targetCRSIn, interpolationCRSIn, methodIn, values,
359         accuracies);
360     transf->assignSelf(transf);
361     transf->setProperties(properties);
362     std::string name;
363     if (properties.getStringValue(common::IdentifiedObject::NAME_KEY, name) &&
364         ci_find(name, "ballpark") != std::string::npos) {
365         transf->setHasBallparkTransformation(true);
366     }
367     return transf;
368 }
369 
370 // ---------------------------------------------------------------------------
371 
372 /** \brief Instantiate a transformation ands its OperationMethod.
373  *
374  * @param propertiesTransformation The \ref general_properties of the
375  * Transformation.
376  * At minimum the name should be defined.
377  * @param sourceCRSIn Source CRS.
378  * @param targetCRSIn Target CRS.
379  * @param interpolationCRSIn Interpolation CRS (might be null)
380  * @param propertiesOperationMethod The \ref general_properties of the
381  * OperationMethod.
382  * At minimum the name should be defined.
383  * @param parameters Vector of parameters of the operation method.
384  * @param values Vector of ParameterValueNNPtr. Constraint:
385  * values.size() == parameters.size()
386  * @param accuracies Vector of positional accuracy (might be empty).
387  * @return new Transformation.
388  * @throws InvalidOperation
389  */
390 TransformationNNPtr
create(const util::PropertyMap & propertiesTransformation,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const crs::CRSPtr & interpolationCRSIn,const util::PropertyMap & propertiesOperationMethod,const std::vector<OperationParameterNNPtr> & parameters,const std::vector<ParameterValueNNPtr> & values,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)391 Transformation::create(const util::PropertyMap &propertiesTransformation,
392                        const crs::CRSNNPtr &sourceCRSIn,
393                        const crs::CRSNNPtr &targetCRSIn,
394                        const crs::CRSPtr &interpolationCRSIn,
395                        const util::PropertyMap &propertiesOperationMethod,
396                        const std::vector<OperationParameterNNPtr> &parameters,
397                        const std::vector<ParameterValueNNPtr> &values,
398                        const std::vector<metadata::PositionalAccuracyNNPtr>
399                            &accuracies) // throw InvalidOperation
400 {
401     OperationMethodNNPtr op(
402         OperationMethod::create(propertiesOperationMethod, parameters));
403 
404     if (parameters.size() != values.size()) {
405         throw InvalidOperation(
406             "Inconsistent number of parameters and parameter values");
407     }
408     std::vector<GeneralParameterValueNNPtr> generalParameterValues;
409     generalParameterValues.reserve(values.size());
410     for (size_t i = 0; i < values.size(); i++) {
411         generalParameterValues.push_back(
412             OperationParameterValue::create(parameters[i], values[i]));
413     }
414     return create(propertiesTransformation, sourceCRSIn, targetCRSIn,
415                   interpolationCRSIn, op, generalParameterValues, accuracies);
416 }
417 
418 // ---------------------------------------------------------------------------
419 
420 //! @cond Doxygen_Suppress
421 
422 // ---------------------------------------------------------------------------
423 
createSevenParamsTransform(const util::PropertyMap & properties,const util::PropertyMap & methodProperties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,double rotationXArcSecond,double rotationYArcSecond,double rotationZArcSecond,double scaleDifferencePPM,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)424 static TransformationNNPtr createSevenParamsTransform(
425     const util::PropertyMap &properties,
426     const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
427     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
428     double translationYMetre, double translationZMetre,
429     double rotationXArcSecond, double rotationYArcSecond,
430     double rotationZArcSecond, double scaleDifferencePPM,
431     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
432     return Transformation::create(
433         properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
434         VectorOfParameters{
435             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
436             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
437             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
438             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
439             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
440             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
441             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
442         },
443         createParams(common::Length(translationXMetre),
444                      common::Length(translationYMetre),
445                      common::Length(translationZMetre),
446                      common::Angle(rotationXArcSecond,
447                                    common::UnitOfMeasure::ARC_SECOND),
448                      common::Angle(rotationYArcSecond,
449                                    common::UnitOfMeasure::ARC_SECOND),
450                      common::Angle(rotationZArcSecond,
451                                    common::UnitOfMeasure::ARC_SECOND),
452                      common::Scale(scaleDifferencePPM,
453                                    common::UnitOfMeasure::PARTS_PER_MILLION)),
454         accuracies);
455 }
456 
457 // ---------------------------------------------------------------------------
458 
getTransformationType(const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,bool & isGeocentric,bool & isGeog2D,bool & isGeog3D)459 static void getTransformationType(const crs::CRSNNPtr &sourceCRSIn,
460                                   const crs::CRSNNPtr &targetCRSIn,
461                                   bool &isGeocentric, bool &isGeog2D,
462                                   bool &isGeog3D) {
463     auto sourceCRSGeod =
464         dynamic_cast<const crs::GeodeticCRS *>(sourceCRSIn.get());
465     auto targetCRSGeod =
466         dynamic_cast<const crs::GeodeticCRS *>(targetCRSIn.get());
467     isGeocentric = sourceCRSGeod && sourceCRSGeod->isGeocentric() &&
468                    targetCRSGeod && targetCRSGeod->isGeocentric();
469     if (isGeocentric) {
470         isGeog2D = false;
471         isGeog3D = false;
472         return;
473     }
474     isGeocentric = false;
475 
476     auto sourceCRSGeog =
477         dynamic_cast<const crs::GeographicCRS *>(sourceCRSIn.get());
478     auto targetCRSGeog =
479         dynamic_cast<const crs::GeographicCRS *>(targetCRSIn.get());
480     if (!sourceCRSGeog || !targetCRSGeog) {
481         throw InvalidOperation("Inconsistent CRS type");
482     }
483     const auto nSrcAxisCount =
484         sourceCRSGeog->coordinateSystem()->axisList().size();
485     const auto nTargetAxisCount =
486         targetCRSGeog->coordinateSystem()->axisList().size();
487     isGeog2D = nSrcAxisCount == 2 && nTargetAxisCount == 2;
488     isGeog3D = !isGeog2D && nSrcAxisCount >= 2 && nTargetAxisCount >= 2;
489 }
490 
491 // ---------------------------------------------------------------------------
492 
493 static int
useOperationMethodEPSGCodeIfPresent(const util::PropertyMap & properties,int nDefaultOperationMethodEPSGCode)494 useOperationMethodEPSGCodeIfPresent(const util::PropertyMap &properties,
495                                     int nDefaultOperationMethodEPSGCode) {
496     const auto *operationMethodEPSGCode =
497         properties.get("OPERATION_METHOD_EPSG_CODE");
498     if (operationMethodEPSGCode) {
499         const auto boxedValue = dynamic_cast<const util::BoxedValue *>(
500             (*operationMethodEPSGCode).get());
501         if (boxedValue &&
502             boxedValue->type() == util::BoxedValue::Type::INTEGER) {
503             return boxedValue->integerValue();
504         }
505     }
506     return nDefaultOperationMethodEPSGCode;
507 }
508 //! @endcond
509 
510 // ---------------------------------------------------------------------------
511 
512 /** \brief Instantiate a transformation with Geocentric Translations method.
513  *
514  * @param properties See \ref general_properties of the Transformation.
515  * At minimum the name should be defined.
516  * @param sourceCRSIn Source CRS.
517  * @param targetCRSIn Target CRS.
518  * @param translationXMetre Value of the Translation_X parameter (in metre).
519  * @param translationYMetre Value of the Translation_Y parameter (in metre).
520  * @param translationZMetre Value of the Translation_Z parameter (in metre).
521  * @param accuracies Vector of positional accuracy (might be empty).
522  * @return new Transformation.
523  */
createGeocentricTranslations(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)524 TransformationNNPtr Transformation::createGeocentricTranslations(
525     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
526     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
527     double translationYMetre, double translationZMetre,
528     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
529     bool isGeocentric;
530     bool isGeog2D;
531     bool isGeog3D;
532     getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
533                           isGeog3D);
534     return create(
535         properties, sourceCRSIn, targetCRSIn, nullptr,
536         createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
537             properties,
538             isGeocentric
539                 ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC
540                 : isGeog2D
541                       ? EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D
542                       : EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D)),
543         VectorOfParameters{
544             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
545             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
546             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
547         },
548         createParams(common::Length(translationXMetre),
549                      common::Length(translationYMetre),
550                      common::Length(translationZMetre)),
551         accuracies);
552 }
553 
554 // ---------------------------------------------------------------------------
555 
556 /** \brief Instantiate a transformation with Position vector transformation
557  * method.
558  *
559  * This is similar to createCoordinateFrameRotation(), except that the sign of
560  * the rotation terms is inverted.
561  *
562  * @param properties See \ref general_properties of the Transformation.
563  * At minimum the name should be defined.
564  * @param sourceCRSIn Source CRS.
565  * @param targetCRSIn Target CRS.
566  * @param translationXMetre Value of the Translation_X parameter (in metre).
567  * @param translationYMetre Value of the Translation_Y parameter (in metre).
568  * @param translationZMetre Value of the Translation_Z parameter (in metre).
569  * @param rotationXArcSecond Value of the Rotation_X parameter (in
570  * arc-second).
571  * @param rotationYArcSecond Value of the Rotation_Y parameter (in
572  * arc-second).
573  * @param rotationZArcSecond Value of the Rotation_Z parameter (in
574  * arc-second).
575  * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
576  * parts-per-million).
577  * @param accuracies Vector of positional accuracy (might be empty).
578  * @return new Transformation.
579  */
createPositionVector(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,double rotationXArcSecond,double rotationYArcSecond,double rotationZArcSecond,double scaleDifferencePPM,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)580 TransformationNNPtr Transformation::createPositionVector(
581     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
582     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
583     double translationYMetre, double translationZMetre,
584     double rotationXArcSecond, double rotationYArcSecond,
585     double rotationZArcSecond, double scaleDifferencePPM,
586     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
587 
588     bool isGeocentric;
589     bool isGeog2D;
590     bool isGeog3D;
591     getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
592                           isGeog3D);
593     return createSevenParamsTransform(
594         properties,
595         createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
596             properties,
597             isGeocentric
598                 ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC
599                 : isGeog2D ? EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D
600                            : EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D)),
601         sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
602         translationZMetre, rotationXArcSecond, rotationYArcSecond,
603         rotationZArcSecond, scaleDifferencePPM, accuracies);
604 }
605 
606 // ---------------------------------------------------------------------------
607 
608 /** \brief Instantiate a transformation with Coordinate Frame Rotation method.
609  *
610  * This is similar to createPositionVector(), except that the sign of
611  * the rotation terms is inverted.
612  *
613  * @param properties See \ref general_properties of the Transformation.
614  * At minimum the name should be defined.
615  * @param sourceCRSIn Source CRS.
616  * @param targetCRSIn Target CRS.
617  * @param translationXMetre Value of the Translation_X parameter (in metre).
618  * @param translationYMetre Value of the Translation_Y parameter (in metre).
619  * @param translationZMetre Value of the Translation_Z parameter (in metre).
620  * @param rotationXArcSecond Value of the Rotation_X parameter (in
621  * arc-second).
622  * @param rotationYArcSecond Value of the Rotation_Y parameter (in
623  * arc-second).
624  * @param rotationZArcSecond Value of the Rotation_Z parameter (in
625  * arc-second).
626  * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
627  * parts-per-million).
628  * @param accuracies Vector of positional accuracy (might be empty).
629  * @return new Transformation.
630  */
createCoordinateFrameRotation(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,double rotationXArcSecond,double rotationYArcSecond,double rotationZArcSecond,double scaleDifferencePPM,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)631 TransformationNNPtr Transformation::createCoordinateFrameRotation(
632     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
633     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
634     double translationYMetre, double translationZMetre,
635     double rotationXArcSecond, double rotationYArcSecond,
636     double rotationZArcSecond, double scaleDifferencePPM,
637     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
638     bool isGeocentric;
639     bool isGeog2D;
640     bool isGeog3D;
641     getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
642                           isGeog3D);
643     return createSevenParamsTransform(
644         properties,
645         createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
646             properties,
647             isGeocentric
648                 ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC
649                 : isGeog2D ? EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D
650                            : EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D)),
651         sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
652         translationZMetre, rotationXArcSecond, rotationYArcSecond,
653         rotationZArcSecond, scaleDifferencePPM, accuracies);
654 }
655 
656 // ---------------------------------------------------------------------------
657 
658 //! @cond Doxygen_Suppress
createFifteenParamsTransform(const util::PropertyMap & properties,const util::PropertyMap & methodProperties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,double rotationXArcSecond,double rotationYArcSecond,double rotationZArcSecond,double scaleDifferencePPM,double rateTranslationX,double rateTranslationY,double rateTranslationZ,double rateRotationX,double rateRotationY,double rateRotationZ,double rateScaleDifference,double referenceEpochYear,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)659 static TransformationNNPtr createFifteenParamsTransform(
660     const util::PropertyMap &properties,
661     const util::PropertyMap &methodProperties, const crs::CRSNNPtr &sourceCRSIn,
662     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
663     double translationYMetre, double translationZMetre,
664     double rotationXArcSecond, double rotationYArcSecond,
665     double rotationZArcSecond, double scaleDifferencePPM,
666     double rateTranslationX, double rateTranslationY, double rateTranslationZ,
667     double rateRotationX, double rateRotationY, double rateRotationZ,
668     double rateScaleDifference, double referenceEpochYear,
669     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
670     return Transformation::create(
671         properties, sourceCRSIn, targetCRSIn, nullptr, methodProperties,
672         VectorOfParameters{
673             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
674             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
675             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
676             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_ROTATION),
677             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION),
678             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION),
679             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE),
680 
681             createOpParamNameEPSGCode(
682                 EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION),
683             createOpParamNameEPSGCode(
684                 EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION),
685             createOpParamNameEPSGCode(
686                 EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION),
687             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION),
688             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION),
689             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION),
690             createOpParamNameEPSGCode(
691                 EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE),
692 
693             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_REFERENCE_EPOCH),
694         },
695         VectorOfValues{
696             common::Length(translationXMetre),
697             common::Length(translationYMetre),
698             common::Length(translationZMetre),
699             common::Angle(rotationXArcSecond,
700                           common::UnitOfMeasure::ARC_SECOND),
701             common::Angle(rotationYArcSecond,
702                           common::UnitOfMeasure::ARC_SECOND),
703             common::Angle(rotationZArcSecond,
704                           common::UnitOfMeasure::ARC_SECOND),
705             common::Scale(scaleDifferencePPM,
706                           common::UnitOfMeasure::PARTS_PER_MILLION),
707             common::Measure(rateTranslationX,
708                             common::UnitOfMeasure::METRE_PER_YEAR),
709             common::Measure(rateTranslationY,
710                             common::UnitOfMeasure::METRE_PER_YEAR),
711             common::Measure(rateTranslationZ,
712                             common::UnitOfMeasure::METRE_PER_YEAR),
713             common::Measure(rateRotationX,
714                             common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
715             common::Measure(rateRotationY,
716                             common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
717             common::Measure(rateRotationZ,
718                             common::UnitOfMeasure::ARC_SECOND_PER_YEAR),
719             common::Measure(rateScaleDifference,
720                             common::UnitOfMeasure::PPM_PER_YEAR),
721             common::Measure(referenceEpochYear, common::UnitOfMeasure::YEAR),
722         },
723         accuracies);
724 }
725 //! @endcond
726 
727 // ---------------------------------------------------------------------------
728 
729 /** \brief Instantiate a transformation with Time Dependent position vector
730  * transformation method.
731  *
732  * This is similar to createTimeDependentCoordinateFrameRotation(), except that
733  * the sign of
734  * the rotation terms is inverted.
735  *
736  * This method is defined as [EPSG:1053]
737  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1053)
738  *
739  * @param properties See \ref general_properties of the Transformation.
740  * At minimum the name should be defined.
741  * @param sourceCRSIn Source CRS.
742  * @param targetCRSIn Target CRS.
743  * @param translationXMetre Value of the Translation_X parameter (in metre).
744  * @param translationYMetre Value of the Translation_Y parameter (in metre).
745  * @param translationZMetre Value of the Translation_Z parameter (in metre).
746  * @param rotationXArcSecond Value of the Rotation_X parameter (in
747  * arc-second).
748  * @param rotationYArcSecond Value of the Rotation_Y parameter (in
749  * arc-second).
750  * @param rotationZArcSecond Value of the Rotation_Z parameter (in
751  * arc-second).
752  * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
753  * parts-per-million).
754  * @param rateTranslationX Value of the rate of change of X-axis translation (in
755  * metre/year)
756  * @param rateTranslationY Value of the rate of change of Y-axis translation (in
757  * metre/year)
758  * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
759  * metre/year)
760  * @param rateRotationX Value of the rate of change of X-axis rotation (in
761  * arc-second/year)
762  * @param rateRotationY Value of the rate of change of Y-axis rotation (in
763  * arc-second/year)
764  * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
765  * arc-second/year)
766  * @param rateScaleDifference Value of the rate of change of scale difference
767  * (in PPM/year)
768  * @param referenceEpochYear Parameter reference epoch (in decimal year)
769  * @param accuracies Vector of positional accuracy (might be empty).
770  * @return new Transformation.
771  */
createTimeDependentPositionVector(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,double rotationXArcSecond,double rotationYArcSecond,double rotationZArcSecond,double scaleDifferencePPM,double rateTranslationX,double rateTranslationY,double rateTranslationZ,double rateRotationX,double rateRotationY,double rateRotationZ,double rateScaleDifference,double referenceEpochYear,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)772 TransformationNNPtr Transformation::createTimeDependentPositionVector(
773     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
774     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
775     double translationYMetre, double translationZMetre,
776     double rotationXArcSecond, double rotationYArcSecond,
777     double rotationZArcSecond, double scaleDifferencePPM,
778     double rateTranslationX, double rateTranslationY, double rateTranslationZ,
779     double rateRotationX, double rateRotationY, double rateRotationZ,
780     double rateScaleDifference, double referenceEpochYear,
781     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
782     bool isGeocentric;
783     bool isGeog2D;
784     bool isGeog3D;
785     getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
786                           isGeog3D);
787     return createFifteenParamsTransform(
788         properties,
789         createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
790             properties,
791             isGeocentric
792                 ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC
793                 : isGeog2D
794                       ? EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D
795                       : EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D)),
796         sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
797         translationZMetre, rotationXArcSecond, rotationYArcSecond,
798         rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
799         rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
800         rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
801 }
802 
803 // ---------------------------------------------------------------------------
804 
805 /** \brief Instantiate a transformation with Time Dependent Position coordinate
806  * frame rotation transformation method.
807  *
808  * This is similar to createTimeDependentPositionVector(), except that the sign
809  * of
810  * the rotation terms is inverted.
811  *
812  * This method is defined as [EPSG:1056]
813  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1056)
814  *
815  * @param properties See \ref general_properties of the Transformation.
816  * At minimum the name should be defined.
817  * @param sourceCRSIn Source CRS.
818  * @param targetCRSIn Target CRS.
819  * @param translationXMetre Value of the Translation_X parameter (in metre).
820  * @param translationYMetre Value of the Translation_Y parameter (in metre).
821  * @param translationZMetre Value of the Translation_Z parameter (in metre).
822  * @param rotationXArcSecond Value of the Rotation_X parameter (in
823  * arc-second).
824  * @param rotationYArcSecond Value of the Rotation_Y parameter (in
825  * arc-second).
826  * @param rotationZArcSecond Value of the Rotation_Z parameter (in
827  * arc-second).
828  * @param scaleDifferencePPM Value of the Scale_Difference parameter (in
829  * parts-per-million).
830  * @param rateTranslationX Value of the rate of change of X-axis translation (in
831  * metre/year)
832  * @param rateTranslationY Value of the rate of change of Y-axis translation (in
833  * metre/year)
834  * @param rateTranslationZ Value of the rate of change of Z-axis translation (in
835  * metre/year)
836  * @param rateRotationX Value of the rate of change of X-axis rotation (in
837  * arc-second/year)
838  * @param rateRotationY Value of the rate of change of Y-axis rotation (in
839  * arc-second/year)
840  * @param rateRotationZ Value of the rate of change of Z-axis rotation (in
841  * arc-second/year)
842  * @param rateScaleDifference Value of the rate of change of scale difference
843  * (in PPM/year)
844  * @param referenceEpochYear Parameter reference epoch (in decimal year)
845  * @param accuracies Vector of positional accuracy (might be empty).
846  * @return new Transformation.
847  * @throws InvalidOperation
848  */
createTimeDependentCoordinateFrameRotation(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,double rotationXArcSecond,double rotationYArcSecond,double rotationZArcSecond,double scaleDifferencePPM,double rateTranslationX,double rateTranslationY,double rateTranslationZ,double rateRotationX,double rateRotationY,double rateRotationZ,double rateScaleDifference,double referenceEpochYear,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)849 TransformationNNPtr Transformation::createTimeDependentCoordinateFrameRotation(
850     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
851     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
852     double translationYMetre, double translationZMetre,
853     double rotationXArcSecond, double rotationYArcSecond,
854     double rotationZArcSecond, double scaleDifferencePPM,
855     double rateTranslationX, double rateTranslationY, double rateTranslationZ,
856     double rateRotationX, double rateRotationY, double rateRotationZ,
857     double rateScaleDifference, double referenceEpochYear,
858     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
859 
860     bool isGeocentric;
861     bool isGeog2D;
862     bool isGeog3D;
863     getTransformationType(sourceCRSIn, targetCRSIn, isGeocentric, isGeog2D,
864                           isGeog3D);
865     return createFifteenParamsTransform(
866         properties,
867         createMethodMapNameEPSGCode(useOperationMethodEPSGCodeIfPresent(
868             properties,
869             isGeocentric
870                 ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC
871                 : isGeog2D
872                       ? EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D
873                       : EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D)),
874         sourceCRSIn, targetCRSIn, translationXMetre, translationYMetre,
875         translationZMetre, rotationXArcSecond, rotationYArcSecond,
876         rotationZArcSecond, scaleDifferencePPM, rateTranslationX,
877         rateTranslationY, rateTranslationZ, rateRotationX, rateRotationY,
878         rateRotationZ, rateScaleDifference, referenceEpochYear, accuracies);
879 }
880 
881 // ---------------------------------------------------------------------------
882 
883 //! @cond Doxygen_Suppress
_createMolodensky(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,int methodEPSGCode,double translationXMetre,double translationYMetre,double translationZMetre,double semiMajorAxisDifferenceMetre,double flattingDifference,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)884 static TransformationNNPtr _createMolodensky(
885     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
886     const crs::CRSNNPtr &targetCRSIn, int methodEPSGCode,
887     double translationXMetre, double translationYMetre,
888     double translationZMetre, double semiMajorAxisDifferenceMetre,
889     double flattingDifference,
890     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
891     return Transformation::create(
892         properties, sourceCRSIn, targetCRSIn, nullptr,
893         createMethodMapNameEPSGCode(methodEPSGCode),
894         VectorOfParameters{
895             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
896             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
897             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
898             createOpParamNameEPSGCode(
899                 EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE),
900             createOpParamNameEPSGCode(
901                 EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE),
902         },
903         createParams(
904             common::Length(translationXMetre),
905             common::Length(translationYMetre),
906             common::Length(translationZMetre),
907             common::Length(semiMajorAxisDifferenceMetre),
908             common::Measure(flattingDifference, common::UnitOfMeasure::NONE)),
909         accuracies);
910 }
911 //! @endcond
912 
913 // ---------------------------------------------------------------------------
914 
915 /** \brief Instantiate a transformation with Molodensky method.
916  *
917  * @see createAbridgedMolodensky() for a related method.
918  *
919  * This method is defined as [EPSG:9604]
920  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9604)
921  *
922  * @param properties See \ref general_properties of the Transformation.
923  * At minimum the name should be defined.
924  * @param sourceCRSIn Source CRS.
925  * @param targetCRSIn Target CRS.
926  * @param translationXMetre Value of the Translation_X parameter (in metre).
927  * @param translationYMetre Value of the Translation_Y parameter (in metre).
928  * @param translationZMetre Value of the Translation_Z parameter (in metre).
929  * @param semiMajorAxisDifferenceMetre The difference between the semi-major
930  * axis values of the ellipsoids used in the target and source CRS (in metre).
931  * @param flattingDifference The difference  between the flattening values of
932  * the ellipsoids used in the target and source CRS.
933  * @param accuracies Vector of positional accuracy (might be empty).
934  * @return new Transformation.
935  * @throws InvalidOperation
936  */
createMolodensky(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,double semiMajorAxisDifferenceMetre,double flattingDifference,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)937 TransformationNNPtr Transformation::createMolodensky(
938     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
939     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
940     double translationYMetre, double translationZMetre,
941     double semiMajorAxisDifferenceMetre, double flattingDifference,
942     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
943     return _createMolodensky(
944         properties, sourceCRSIn, targetCRSIn, EPSG_CODE_METHOD_MOLODENSKY,
945         translationXMetre, translationYMetre, translationZMetre,
946         semiMajorAxisDifferenceMetre, flattingDifference, accuracies);
947 }
948 
949 // ---------------------------------------------------------------------------
950 
951 /** \brief Instantiate a transformation with Abridged Molodensky method.
952  *
953  * @see createdMolodensky() for a related method.
954  *
955  * This method is defined as [EPSG:9605]
956  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9605)
957  *
958  * @param properties See \ref general_properties of the Transformation.
959  * At minimum the name should be defined.
960  * @param sourceCRSIn Source CRS.
961  * @param targetCRSIn Target CRS.
962  * @param translationXMetre Value of the Translation_X parameter (in metre).
963  * @param translationYMetre Value of the Translation_Y parameter (in metre).
964  * @param translationZMetre Value of the Translation_Z parameter (in metre).
965  * @param semiMajorAxisDifferenceMetre The difference between the semi-major
966  * axis values of the ellipsoids used in the target and source CRS (in metre).
967  * @param flattingDifference The difference  between the flattening values of
968  * the ellipsoids used in the target and source CRS.
969  * @param accuracies Vector of positional accuracy (might be empty).
970  * @return new Transformation.
971  * @throws InvalidOperation
972  */
createAbridgedMolodensky(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,double translationXMetre,double translationYMetre,double translationZMetre,double semiMajorAxisDifferenceMetre,double flattingDifference,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)973 TransformationNNPtr Transformation::createAbridgedMolodensky(
974     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
975     const crs::CRSNNPtr &targetCRSIn, double translationXMetre,
976     double translationYMetre, double translationZMetre,
977     double semiMajorAxisDifferenceMetre, double flattingDifference,
978     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
979     return _createMolodensky(properties, sourceCRSIn, targetCRSIn,
980                              EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY,
981                              translationXMetre, translationYMetre,
982                              translationZMetre, semiMajorAxisDifferenceMetre,
983                              flattingDifference, accuracies);
984 }
985 
986 // ---------------------------------------------------------------------------
987 
988 /** \brief Instantiate a transformation from TOWGS84 parameters.
989  *
990  * This is a helper of createPositionVector() with the source CRS being the
991  * GeographicCRS of sourceCRSIn, and the target CRS being EPSG:4326
992  *
993  * @param sourceCRSIn Source CRS.
994  * @param TOWGS84Parameters The vector of 3 double values (Translation_X,_Y,_Z)
995  * or 7 double values (Translation_X,_Y,_Z, Rotation_X,_Y,_Z, Scale_Difference)
996  * passed to createPositionVector()
997  * @return new Transformation.
998  * @throws InvalidOperation
999  */
createTOWGS84(const crs::CRSNNPtr & sourceCRSIn,const std::vector<double> & TOWGS84Parameters)1000 TransformationNNPtr Transformation::createTOWGS84(
1001     const crs::CRSNNPtr &sourceCRSIn,
1002     const std::vector<double> &TOWGS84Parameters) // throw InvalidOperation
1003 {
1004     if (TOWGS84Parameters.size() != 3 && TOWGS84Parameters.size() != 7) {
1005         throw InvalidOperation(
1006             "Invalid number of elements in TOWGS84Parameters");
1007     }
1008 
1009     crs::CRSPtr transformSourceCRS = sourceCRSIn->extractGeodeticCRS();
1010     if (!transformSourceCRS) {
1011         throw InvalidOperation(
1012             "Cannot find GeodeticCRS in sourceCRS of TOWGS84 transformation");
1013     }
1014 
1015     util::PropertyMap properties;
1016     properties.set(common::IdentifiedObject::NAME_KEY,
1017                    concat("Transformation from ", transformSourceCRS->nameStr(),
1018                           " to WGS84"));
1019 
1020     auto targetCRS =
1021         dynamic_cast<const crs::GeographicCRS *>(transformSourceCRS.get())
1022             ? util::nn_static_pointer_cast<crs::CRS>(
1023                   crs::GeographicCRS::EPSG_4326)
1024             : util::nn_static_pointer_cast<crs::CRS>(
1025                   crs::GeodeticCRS::EPSG_4978);
1026 
1027     if (TOWGS84Parameters.size() == 3) {
1028         return createGeocentricTranslations(
1029             properties, NN_NO_CHECK(transformSourceCRS), targetCRS,
1030             TOWGS84Parameters[0], TOWGS84Parameters[1], TOWGS84Parameters[2],
1031             {});
1032     }
1033 
1034     return createPositionVector(properties, NN_NO_CHECK(transformSourceCRS),
1035                                 targetCRS, TOWGS84Parameters[0],
1036                                 TOWGS84Parameters[1], TOWGS84Parameters[2],
1037                                 TOWGS84Parameters[3], TOWGS84Parameters[4],
1038                                 TOWGS84Parameters[5], TOWGS84Parameters[6], {});
1039 }
1040 
1041 // ---------------------------------------------------------------------------
1042 /** \brief Instantiate a transformation with NTv2 method.
1043  *
1044  * @param properties See \ref general_properties of the Transformation.
1045  * At minimum the name should be defined.
1046  * @param sourceCRSIn Source CRS.
1047  * @param targetCRSIn Target CRS.
1048  * @param filename NTv2 filename.
1049  * @param accuracies Vector of positional accuracy (might be empty).
1050  * @return new Transformation.
1051  */
createNTv2(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const std::string & filename,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1052 TransformationNNPtr Transformation::createNTv2(
1053     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1054     const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
1055     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1056 
1057     return create(properties, sourceCRSIn, targetCRSIn, nullptr,
1058                   createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV2),
1059                   VectorOfParameters{createOpParamNameEPSGCode(
1060                       EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)},
1061                   VectorOfValues{ParameterValue::createFilename(filename)},
1062                   accuracies);
1063 }
1064 
1065 // ---------------------------------------------------------------------------
1066 
1067 //! @cond Doxygen_Suppress
_createGravityRelatedHeightToGeographic3D(const util::PropertyMap & properties,bool inverse,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const crs::CRSPtr & interpolationCRSIn,const std::string & filename,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1068 static TransformationNNPtr _createGravityRelatedHeightToGeographic3D(
1069     const util::PropertyMap &properties, bool inverse,
1070     const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
1071     const crs::CRSPtr &interpolationCRSIn, const std::string &filename,
1072     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1073 
1074     return Transformation::create(
1075         properties, sourceCRSIn, targetCRSIn, interpolationCRSIn,
1076         util::PropertyMap().set(
1077             common::IdentifiedObject::NAME_KEY,
1078             inverse ? INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D
1079                     : PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D),
1080         VectorOfParameters{createOpParamNameEPSGCode(
1081             EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)},
1082         VectorOfValues{ParameterValue::createFilename(filename)}, accuracies);
1083 }
1084 //! @endcond
1085 
1086 // ---------------------------------------------------------------------------
1087 /** \brief Instantiate a transformation from GravityRelatedHeight to
1088  * Geographic3D
1089  *
1090  * @param properties See \ref general_properties of the Transformation.
1091  * At minimum the name should be defined.
1092  * @param sourceCRSIn Source CRS.
1093  * @param targetCRSIn Target CRS.
1094  * @param interpolationCRSIn Interpolation CRS. (might be null)
1095  * @param filename GRID filename.
1096  * @param accuracies Vector of positional accuracy (might be empty).
1097  * @return new Transformation.
1098  */
createGravityRelatedHeightToGeographic3D(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const crs::CRSPtr & interpolationCRSIn,const std::string & filename,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1099 TransformationNNPtr Transformation::createGravityRelatedHeightToGeographic3D(
1100     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1101     const crs::CRSNNPtr &targetCRSIn, const crs::CRSPtr &interpolationCRSIn,
1102     const std::string &filename,
1103     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1104 
1105     return _createGravityRelatedHeightToGeographic3D(
1106         properties, false, sourceCRSIn, targetCRSIn, interpolationCRSIn,
1107         filename, accuracies);
1108 }
1109 
1110 // ---------------------------------------------------------------------------
1111 
1112 /** \brief Instantiate a transformation with method VERTCON
1113  *
1114  * @param properties See \ref general_properties of the Transformation.
1115  * At minimum the name should be defined.
1116  * @param sourceCRSIn Source CRS.
1117  * @param targetCRSIn Target CRS.
1118  * @param filename GRID filename.
1119  * @param accuracies Vector of positional accuracy (might be empty).
1120  * @return new Transformation.
1121  */
createVERTCON(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const std::string & filename,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1122 TransformationNNPtr Transformation::createVERTCON(
1123     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1124     const crs::CRSNNPtr &targetCRSIn, const std::string &filename,
1125     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1126 
1127     return create(properties, sourceCRSIn, targetCRSIn, nullptr,
1128                   createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTCON),
1129                   VectorOfParameters{createOpParamNameEPSGCode(
1130                       EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)},
1131                   VectorOfValues{ParameterValue::createFilename(filename)},
1132                   accuracies);
1133 }
1134 
1135 // ---------------------------------------------------------------------------
1136 
1137 //! @cond Doxygen_Suppress
1138 static inline std::vector<metadata::PositionalAccuracyNNPtr>
buildAccuracyZero()1139 buildAccuracyZero() {
1140     return std::vector<metadata::PositionalAccuracyNNPtr>{
1141         metadata::PositionalAccuracy::create("0")};
1142 }
1143 
1144 // ---------------------------------------------------------------------------
1145 
1146 //! @endcond
1147 
1148 /** \brief Instantiate a transformation with method Longitude rotation
1149  *
1150  * This method is defined as [EPSG:9601]
1151  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9601)
1152  * *
1153  * @param properties See \ref general_properties of the Transformation.
1154  * At minimum the name should be defined.
1155  * @param sourceCRSIn Source CRS.
1156  * @param targetCRSIn Target CRS.
1157  * @param offset Longitude offset to add.
1158  * @return new Transformation.
1159  */
createLongitudeRotation(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const common::Angle & offset)1160 TransformationNNPtr Transformation::createLongitudeRotation(
1161     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1162     const crs::CRSNNPtr &targetCRSIn, const common::Angle &offset) {
1163 
1164     return create(
1165         properties, sourceCRSIn, targetCRSIn, nullptr,
1166         createMethodMapNameEPSGCode(EPSG_CODE_METHOD_LONGITUDE_ROTATION),
1167         VectorOfParameters{
1168             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
1169         VectorOfValues{ParameterValue::create(offset)}, buildAccuracyZero());
1170 }
1171 
1172 // ---------------------------------------------------------------------------
1173 
1174 //! @cond Doxygen_Suppress
isLongitudeRotation() const1175 bool Transformation::isLongitudeRotation() const {
1176     return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION;
1177 }
1178 
1179 //! @endcond
1180 
1181 // ---------------------------------------------------------------------------
1182 
1183 /** \brief Instantiate a transformation with method Geographic 2D offsets
1184  *
1185  * This method is defined as [EPSG:9619]
1186  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9619)
1187  * *
1188  * @param properties See \ref general_properties of the Transformation.
1189  * At minimum the name should be defined.
1190  * @param sourceCRSIn Source CRS.
1191  * @param targetCRSIn Target CRS.
1192  * @param offsetLat Latitude offset to add.
1193  * @param offsetLon Longitude offset to add.
1194  * @param accuracies Vector of positional accuracy (might be empty).
1195  * @return new Transformation.
1196  */
createGeographic2DOffsets(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const common::Angle & offsetLat,const common::Angle & offsetLon,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1197 TransformationNNPtr Transformation::createGeographic2DOffsets(
1198     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1199     const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
1200     const common::Angle &offsetLon,
1201     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1202     return create(
1203         properties, sourceCRSIn, targetCRSIn, nullptr,
1204         createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS),
1205         VectorOfParameters{
1206             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
1207             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET)},
1208         VectorOfValues{offsetLat, offsetLon}, accuracies);
1209 }
1210 
1211 // ---------------------------------------------------------------------------
1212 
1213 /** \brief Instantiate a transformation with method Geographic 3D offsets
1214  *
1215  * This method is defined as [EPSG:9660]
1216  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9660)
1217  * *
1218  * @param properties See \ref general_properties of the Transformation.
1219  * At minimum the name should be defined.
1220  * @param sourceCRSIn Source CRS.
1221  * @param targetCRSIn Target CRS.
1222  * @param offsetLat Latitude offset to add.
1223  * @param offsetLon Longitude offset to add.
1224  * @param offsetHeight Height offset to add.
1225  * @param accuracies Vector of positional accuracy (might be empty).
1226  * @return new Transformation.
1227  */
createGeographic3DOffsets(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const common::Angle & offsetLat,const common::Angle & offsetLon,const common::Length & offsetHeight,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1228 TransformationNNPtr Transformation::createGeographic3DOffsets(
1229     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1230     const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
1231     const common::Angle &offsetLon, const common::Length &offsetHeight,
1232     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1233     return create(
1234         properties, sourceCRSIn, targetCRSIn, nullptr,
1235         createMethodMapNameEPSGCode(EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS),
1236         VectorOfParameters{
1237             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
1238             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
1239             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
1240         VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies);
1241 }
1242 
1243 // ---------------------------------------------------------------------------
1244 
1245 /** \brief Instantiate a transformation with method Geographic 2D with
1246  * height
1247  * offsets
1248  *
1249  * This method is defined as [EPSG:9618]
1250  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9618)
1251  * *
1252  * @param properties See \ref general_properties of the Transformation.
1253  * At minimum the name should be defined.
1254  * @param sourceCRSIn Source CRS.
1255  * @param targetCRSIn Target CRS.
1256  * @param offsetLat Latitude offset to add.
1257  * @param offsetLon Longitude offset to add.
1258  * @param offsetHeight Geoid undulation to add.
1259  * @param accuracies Vector of positional accuracy (might be empty).
1260  * @return new Transformation.
1261  */
createGeographic2DWithHeightOffsets(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const common::Angle & offsetLat,const common::Angle & offsetLon,const common::Length & offsetHeight,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1262 TransformationNNPtr Transformation::createGeographic2DWithHeightOffsets(
1263     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1264     const crs::CRSNNPtr &targetCRSIn, const common::Angle &offsetLat,
1265     const common::Angle &offsetLon, const common::Length &offsetHeight,
1266     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1267     return create(
1268         properties, sourceCRSIn, targetCRSIn, nullptr,
1269         createMethodMapNameEPSGCode(
1270             EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS),
1271         VectorOfParameters{
1272             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LATITUDE_OFFSET),
1273             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET),
1274             createOpParamNameEPSGCode(EPSG_CODE_PARAMETER_GEOID_UNDULATION)},
1275         VectorOfValues{offsetLat, offsetLon, offsetHeight}, accuracies);
1276 }
1277 
1278 // ---------------------------------------------------------------------------
1279 
1280 /** \brief Instantiate a transformation with method Vertical Offset.
1281  *
1282  * This method is defined as [EPSG:9616]
1283  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::9616)
1284  * *
1285  * @param properties See \ref general_properties of the Transformation.
1286  * At minimum the name should be defined.
1287  * @param sourceCRSIn Source CRS.
1288  * @param targetCRSIn Target CRS.
1289  * @param offsetHeight Geoid undulation to add.
1290  * @param accuracies Vector of positional accuracy (might be empty).
1291  * @return new Transformation.
1292  */
createVerticalOffset(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const common::Length & offsetHeight,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1293 TransformationNNPtr Transformation::createVerticalOffset(
1294     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1295     const crs::CRSNNPtr &targetCRSIn, const common::Length &offsetHeight,
1296     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1297     return create(properties, sourceCRSIn, targetCRSIn, nullptr,
1298                   createMethodMapNameEPSGCode(EPSG_CODE_METHOD_VERTICAL_OFFSET),
1299                   VectorOfParameters{createOpParamNameEPSGCode(
1300                       EPSG_CODE_PARAMETER_VERTICAL_OFFSET)},
1301                   VectorOfValues{offsetHeight}, accuracies);
1302 }
1303 
1304 // ---------------------------------------------------------------------------
1305 
1306 /** \brief Instantiate a transformation based on the Change of Vertical Unit
1307  * method.
1308  *
1309  * This method is defined as [EPSG:1069]
1310  * (https://www.epsg-registry.org/export.htm?gml=urn:ogc:def:method:EPSG::1069)
1311  *
1312  * @param properties See \ref general_properties of the conversion. If the name
1313  * is not provided, it is automatically set.
1314  * @param sourceCRSIn Source CRS.
1315  * @param targetCRSIn Target CRS.
1316  * @param factor Conversion factor
1317  * @param accuracies Vector of positional accuracy (might be empty).
1318  * @return a new Transformation.
1319  */
createChangeVerticalUnit(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const common::Scale & factor,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)1320 TransformationNNPtr Transformation::createChangeVerticalUnit(
1321     const util::PropertyMap &properties, const crs::CRSNNPtr &sourceCRSIn,
1322     const crs::CRSNNPtr &targetCRSIn, const common::Scale &factor,
1323     const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
1324     return create(
1325         properties, sourceCRSIn, targetCRSIn, nullptr,
1326         createMethodMapNameEPSGCode(EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT),
1327         VectorOfParameters{
1328             createOpParamNameEPSGCode(
1329                 EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR),
1330         },
1331         VectorOfValues{
1332             factor,
1333         },
1334         accuracies);
1335 }
1336 
1337 // ---------------------------------------------------------------------------
1338 
1339 // to avoid -0...
negate(double val)1340 static double negate(double val) {
1341     if (val != 0) {
1342         return -val;
1343     }
1344     return 0.0;
1345 }
1346 
1347 // ---------------------------------------------------------------------------
1348 
1349 static CoordinateOperationPtr
createApproximateInverseIfPossible(const Transformation * op)1350 createApproximateInverseIfPossible(const Transformation *op) {
1351     bool sevenParamsTransform = false;
1352     bool fifteenParamsTransform = false;
1353     const auto &method = op->method();
1354     const auto &methodName = method->nameStr();
1355     const int methodEPSGCode = method->getEPSGCode();
1356     const auto paramCount = op->parameterValues().size();
1357     const bool isPositionVector =
1358         ci_find(methodName, "Position Vector") != std::string::npos;
1359     const bool isCoordinateFrame =
1360         ci_find(methodName, "Coordinate Frame") != std::string::npos;
1361 
1362     // See end of "2.4.3.3 Helmert 7-parameter transformations"
1363     // in EPSG 7-2 guidance
1364     // For practical purposes, the inverse of 7- or 15-parameters Helmert
1365     // can be obtained by using the forward method with all parameters
1366     // negated
1367     // (except reference epoch!)
1368     // So for WKT export use that. But for PROJ string, we use the +inv flag
1369     // so as to get "perfect" round-tripability.
1370     if ((paramCount == 7 && isCoordinateFrame &&
1371          !isTimeDependent(methodName)) ||
1372         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
1373         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
1374         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
1375         sevenParamsTransform = true;
1376     } else if (
1377         (paramCount == 15 && isCoordinateFrame &&
1378          isTimeDependent(methodName)) ||
1379         methodEPSGCode ==
1380             EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
1381         methodEPSGCode ==
1382             EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
1383         methodEPSGCode ==
1384             EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
1385         fifteenParamsTransform = true;
1386     } else if ((paramCount == 7 && isPositionVector &&
1387                 !isTimeDependent(methodName)) ||
1388                methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
1389                methodEPSGCode ==
1390                    EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
1391                methodEPSGCode ==
1392                    EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
1393         sevenParamsTransform = true;
1394     } else if (
1395         (paramCount == 15 && isPositionVector && isTimeDependent(methodName)) ||
1396         methodEPSGCode ==
1397             EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
1398         methodEPSGCode ==
1399             EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
1400         methodEPSGCode ==
1401             EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
1402         fifteenParamsTransform = true;
1403     }
1404     if (sevenParamsTransform || fifteenParamsTransform) {
1405         double neg_x = negate(op->parameterValueNumericAsSI(
1406             EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION));
1407         double neg_y = negate(op->parameterValueNumericAsSI(
1408             EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION));
1409         double neg_z = negate(op->parameterValueNumericAsSI(
1410             EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION));
1411         double neg_rx = negate(
1412             op->parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
1413                                       common::UnitOfMeasure::ARC_SECOND));
1414         double neg_ry = negate(
1415             op->parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
1416                                       common::UnitOfMeasure::ARC_SECOND));
1417         double neg_rz = negate(
1418             op->parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
1419                                       common::UnitOfMeasure::ARC_SECOND));
1420         double neg_scaleDiff = negate(op->parameterValueNumeric(
1421             EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
1422             common::UnitOfMeasure::PARTS_PER_MILLION));
1423         auto methodProperties = util::PropertyMap().set(
1424             common::IdentifiedObject::NAME_KEY, methodName);
1425         int method_epsg_code = method->getEPSGCode();
1426         if (method_epsg_code) {
1427             methodProperties
1428                 .set(metadata::Identifier::CODESPACE_KEY,
1429                      metadata::Identifier::EPSG)
1430                 .set(metadata::Identifier::CODE_KEY, method_epsg_code);
1431         }
1432         if (fifteenParamsTransform) {
1433             double neg_rate_x = negate(op->parameterValueNumeric(
1434                 EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
1435                 common::UnitOfMeasure::METRE_PER_YEAR));
1436             double neg_rate_y = negate(op->parameterValueNumeric(
1437                 EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
1438                 common::UnitOfMeasure::METRE_PER_YEAR));
1439             double neg_rate_z = negate(op->parameterValueNumeric(
1440                 EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
1441                 common::UnitOfMeasure::METRE_PER_YEAR));
1442             double neg_rate_rx = negate(op->parameterValueNumeric(
1443                 EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
1444                 common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
1445             double neg_rate_ry = negate(op->parameterValueNumeric(
1446                 EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
1447                 common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
1448             double neg_rate_rz = negate(op->parameterValueNumeric(
1449                 EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
1450                 common::UnitOfMeasure::ARC_SECOND_PER_YEAR));
1451             double neg_rate_scaleDiff = negate(op->parameterValueNumeric(
1452                 EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
1453                 common::UnitOfMeasure::PPM_PER_YEAR));
1454             double referenceEpochYear =
1455                 op->parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
1456                                           common::UnitOfMeasure::YEAR);
1457             return util::nn_static_pointer_cast<CoordinateOperation>(
1458                        createFifteenParamsTransform(
1459                            createPropertiesForInverse(op, false, true),
1460                            methodProperties, op->targetCRS(), op->sourceCRS(),
1461                            neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
1462                            neg_scaleDiff, neg_rate_x, neg_rate_y, neg_rate_z,
1463                            neg_rate_rx, neg_rate_ry, neg_rate_rz,
1464                            neg_rate_scaleDiff, referenceEpochYear,
1465                            op->coordinateOperationAccuracies()))
1466                 .as_nullable();
1467         } else {
1468             return util::nn_static_pointer_cast<CoordinateOperation>(
1469                        createSevenParamsTransform(
1470                            createPropertiesForInverse(op, false, true),
1471                            methodProperties, op->targetCRS(), op->sourceCRS(),
1472                            neg_x, neg_y, neg_z, neg_rx, neg_ry, neg_rz,
1473                            neg_scaleDiff, op->coordinateOperationAccuracies()))
1474                 .as_nullable();
1475         }
1476     }
1477 
1478     return nullptr;
1479 }
1480 //! @endcond
1481 
1482 // ---------------------------------------------------------------------------
1483 
1484 //! @cond Doxygen_Suppress
1485 TransformationNNPtr
registerInv(const Transformation * thisIn,TransformationNNPtr invTransform)1486 Transformation::Private::registerInv(const Transformation *thisIn,
1487                                      TransformationNNPtr invTransform) {
1488     invTransform->d->forwardOperation_ = thisIn->shallowClone().as_nullable();
1489     invTransform->setHasBallparkTransformation(
1490         thisIn->hasBallparkTransformation());
1491     return invTransform;
1492 }
1493 //! @endcond
1494 
1495 // ---------------------------------------------------------------------------
1496 
inverse() const1497 CoordinateOperationNNPtr Transformation::inverse() const {
1498     return inverseAsTransformation();
1499 }
1500 
1501 // ---------------------------------------------------------------------------
1502 
inverseAsTransformation() const1503 TransformationNNPtr Transformation::inverseAsTransformation() const {
1504 
1505     if (d->forwardOperation_) {
1506         return NN_NO_CHECK(d->forwardOperation_);
1507     }
1508     const auto &l_method = method();
1509     const auto &methodName = l_method->nameStr();
1510     const int methodEPSGCode = l_method->getEPSGCode();
1511     const auto &l_sourceCRS = sourceCRS();
1512     const auto &l_targetCRS = targetCRS();
1513 
1514     // For geocentric translation, the inverse is exactly the negation of
1515     // the parameters.
1516     if (ci_find(methodName, "Geocentric translations") != std::string::npos ||
1517         methodEPSGCode == EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
1518         methodEPSGCode ==
1519             EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
1520         methodEPSGCode ==
1521             EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
1522         double x =
1523             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
1524         double y =
1525             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
1526         double z =
1527             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
1528         auto properties = createPropertiesForInverse(this, false, false);
1529         return Private::registerInv(
1530             this, create(properties, l_targetCRS, l_sourceCRS, nullptr,
1531                          createMethodMapNameEPSGCode(
1532                              useOperationMethodEPSGCodeIfPresent(
1533                                  properties, methodEPSGCode)),
1534                          VectorOfParameters{
1535                              createOpParamNameEPSGCode(
1536                                  EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION),
1537                              createOpParamNameEPSGCode(
1538                                  EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION),
1539                              createOpParamNameEPSGCode(
1540                                  EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION),
1541                          },
1542                          createParams(common::Length(negate(x)),
1543                                       common::Length(negate(y)),
1544                                       common::Length(negate(z))),
1545                          coordinateOperationAccuracies()));
1546     }
1547 
1548     if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
1549         methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
1550         double x =
1551             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
1552         double y =
1553             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
1554         double z =
1555             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
1556         double da = parameterValueNumericAsSI(
1557             EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
1558         double df = parameterValueNumericAsSI(
1559             EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
1560 
1561         if (methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
1562             return Private::registerInv(
1563                 this,
1564                 createAbridgedMolodensky(
1565                     createPropertiesForInverse(this, false, false), l_targetCRS,
1566                     l_sourceCRS, negate(x), negate(y), negate(z), negate(da),
1567                     negate(df), coordinateOperationAccuracies()));
1568         } else {
1569             return Private::registerInv(
1570                 this,
1571                 createMolodensky(createPropertiesForInverse(this, false, false),
1572                                  l_targetCRS, l_sourceCRS, negate(x), negate(y),
1573                                  negate(z), negate(da), negate(df),
1574                                  coordinateOperationAccuracies()));
1575         }
1576     }
1577 
1578     if (isLongitudeRotation()) {
1579         auto offset =
1580             parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1581         const common::Angle newOffset(negate(offset.value()), offset.unit());
1582         return Private::registerInv(
1583             this, createLongitudeRotation(
1584                       createPropertiesForInverse(this, false, false),
1585                       l_targetCRS, l_sourceCRS, newOffset));
1586     }
1587 
1588     if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
1589         auto offsetLat =
1590             parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
1591         const common::Angle newOffsetLat(negate(offsetLat.value()),
1592                                          offsetLat.unit());
1593 
1594         auto offsetLong =
1595             parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1596         const common::Angle newOffsetLong(negate(offsetLong.value()),
1597                                           offsetLong.unit());
1598 
1599         return Private::registerInv(
1600             this, createGeographic2DOffsets(
1601                       createPropertiesForInverse(this, false, false),
1602                       l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
1603                       coordinateOperationAccuracies()));
1604     }
1605 
1606     if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
1607         auto offsetLat =
1608             parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
1609         const common::Angle newOffsetLat(negate(offsetLat.value()),
1610                                          offsetLat.unit());
1611 
1612         auto offsetLong =
1613             parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1614         const common::Angle newOffsetLong(negate(offsetLong.value()),
1615                                           offsetLong.unit());
1616 
1617         auto offsetHeight =
1618             parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
1619         const common::Length newOffsetHeight(negate(offsetHeight.value()),
1620                                              offsetHeight.unit());
1621 
1622         return Private::registerInv(
1623             this, createGeographic3DOffsets(
1624                       createPropertiesForInverse(this, false, false),
1625                       l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
1626                       newOffsetHeight, coordinateOperationAccuracies()));
1627     }
1628 
1629     if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
1630         auto offsetLat =
1631             parameterValueMeasure(EPSG_CODE_PARAMETER_LATITUDE_OFFSET);
1632         const common::Angle newOffsetLat(negate(offsetLat.value()),
1633                                          offsetLat.unit());
1634 
1635         auto offsetLong =
1636             parameterValueMeasure(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET);
1637         const common::Angle newOffsetLong(negate(offsetLong.value()),
1638                                           offsetLong.unit());
1639 
1640         auto offsetHeight =
1641             parameterValueMeasure(EPSG_CODE_PARAMETER_GEOID_UNDULATION);
1642         const common::Length newOffsetHeight(negate(offsetHeight.value()),
1643                                              offsetHeight.unit());
1644 
1645         return Private::registerInv(
1646             this, createGeographic2DWithHeightOffsets(
1647                       createPropertiesForInverse(this, false, false),
1648                       l_targetCRS, l_sourceCRS, newOffsetLat, newOffsetLong,
1649                       newOffsetHeight, coordinateOperationAccuracies()));
1650     }
1651 
1652     if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
1653 
1654         auto offsetHeight =
1655             parameterValueMeasure(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
1656         const common::Length newOffsetHeight(negate(offsetHeight.value()),
1657                                              offsetHeight.unit());
1658 
1659         return Private::registerInv(
1660             this,
1661             createVerticalOffset(createPropertiesForInverse(this, false, false),
1662                                  l_targetCRS, l_sourceCRS, newOffsetHeight,
1663                                  coordinateOperationAccuracies()));
1664     }
1665 
1666     if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) {
1667         const double convFactor = parameterValueNumericAsSI(
1668             EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR);
1669         return Private::registerInv(
1670             this, createChangeVerticalUnit(
1671                       createPropertiesForInverse(this, false, false),
1672                       l_targetCRS, l_sourceCRS, common::Scale(1.0 / convFactor),
1673                       coordinateOperationAccuracies()));
1674     }
1675 
1676 #ifdef notdef
1677     // We don't need that currently, but we might...
1678     if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) {
1679         return Private::registerInv(
1680             this,
1681             createHeightDepthReversal(
1682                 createPropertiesForInverse(this, false, false), l_targetCRS,
1683                 l_sourceCRS, coordinateOperationAccuracies()));
1684     }
1685 #endif
1686 
1687     return InverseTransformation::create(NN_NO_CHECK(
1688         util::nn_dynamic_pointer_cast<Transformation>(shared_from_this())));
1689 }
1690 
1691 // ---------------------------------------------------------------------------
1692 
1693 //! @cond Doxygen_Suppress
1694 
1695 // ---------------------------------------------------------------------------
1696 
InverseTransformation(const TransformationNNPtr & forward)1697 InverseTransformation::InverseTransformation(const TransformationNNPtr &forward)
1698     : Transformation(
1699           forward->targetCRS(), forward->sourceCRS(),
1700           forward->interpolationCRS(),
1701           OperationMethod::create(createPropertiesForInverse(forward->method()),
1702                                   forward->method()->parameters()),
1703           forward->parameterValues(), forward->coordinateOperationAccuracies()),
1704       InverseCoordinateOperation(forward, true) {
1705     setPropertiesFromForward();
1706 }
1707 
1708 // ---------------------------------------------------------------------------
1709 
1710 InverseTransformation::~InverseTransformation() = default;
1711 
1712 // ---------------------------------------------------------------------------
1713 
1714 TransformationNNPtr
create(const TransformationNNPtr & forward)1715 InverseTransformation::create(const TransformationNNPtr &forward) {
1716     auto conv = util::nn_make_shared<InverseTransformation>(forward);
1717     conv->assignSelf(conv);
1718     return conv;
1719 }
1720 
1721 // ---------------------------------------------------------------------------
1722 
inverseAsTransformation() const1723 TransformationNNPtr InverseTransformation::inverseAsTransformation() const {
1724     return NN_NO_CHECK(
1725         util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_));
1726 }
1727 
1728 // ---------------------------------------------------------------------------
1729 
_exportToWKT(io::WKTFormatter * formatter) const1730 void InverseTransformation::_exportToWKT(io::WKTFormatter *formatter) const {
1731 
1732     auto approxInverse = createApproximateInverseIfPossible(
1733         util::nn_dynamic_pointer_cast<Transformation>(forwardOperation_).get());
1734     if (approxInverse) {
1735         approxInverse->_exportToWKT(formatter);
1736     } else {
1737         Transformation::_exportToWKT(formatter);
1738     }
1739 }
1740 
1741 // ---------------------------------------------------------------------------
1742 
_shallowClone() const1743 CoordinateOperationNNPtr InverseTransformation::_shallowClone() const {
1744     auto op = InverseTransformation::nn_make_shared<InverseTransformation>(
1745         inverseAsTransformation()->shallowClone());
1746     op->assignSelf(op);
1747     op->setCRSs(this, false);
1748     return util::nn_static_pointer_cast<CoordinateOperation>(op);
1749 }
1750 
1751 //! @endcond
1752 
1753 // ---------------------------------------------------------------------------
1754 
1755 //! @cond Doxygen_Suppress
_exportToWKT(io::WKTFormatter * formatter) const1756 void Transformation::_exportToWKT(io::WKTFormatter *formatter) const {
1757     exportTransformationToWKT(formatter);
1758 }
1759 //! @endcond
1760 
1761 // ---------------------------------------------------------------------------
1762 
1763 //! @cond Doxygen_Suppress
_exportToJSON(io::JSONFormatter * formatter) const1764 void Transformation::_exportToJSON(
1765     io::JSONFormatter *formatter) const // throw(FormattingException)
1766 {
1767     auto writer = formatter->writer();
1768     auto objectContext(formatter->MakeObjectContext(
1769         formatter->abridgedTransformation() ? "AbridgedTransformation"
1770                                             : "Transformation",
1771         !identifiers().empty()));
1772 
1773     writer->AddObjKey("name");
1774     auto l_name = nameStr();
1775     if (l_name.empty()) {
1776         writer->Add("unnamed");
1777     } else {
1778         writer->Add(l_name);
1779     }
1780 
1781     if (!formatter->abridgedTransformation()) {
1782         writer->AddObjKey("source_crs");
1783         formatter->setAllowIDInImmediateChild();
1784         sourceCRS()->_exportToJSON(formatter);
1785 
1786         writer->AddObjKey("target_crs");
1787         formatter->setAllowIDInImmediateChild();
1788         targetCRS()->_exportToJSON(formatter);
1789 
1790         const auto &l_interpolationCRS = interpolationCRS();
1791         if (l_interpolationCRS) {
1792             writer->AddObjKey("interpolation_crs");
1793             formatter->setAllowIDInImmediateChild();
1794             l_interpolationCRS->_exportToJSON(formatter);
1795         }
1796     }
1797 
1798     writer->AddObjKey("method");
1799     formatter->setOmitTypeInImmediateChild();
1800     formatter->setAllowIDInImmediateChild();
1801     method()->_exportToJSON(formatter);
1802 
1803     writer->AddObjKey("parameters");
1804     {
1805         auto parametersContext(writer->MakeArrayContext(false));
1806         for (const auto &genOpParamvalue : parameterValues()) {
1807             formatter->setAllowIDInImmediateChild();
1808             formatter->setOmitTypeInImmediateChild();
1809             genOpParamvalue->_exportToJSON(formatter);
1810         }
1811     }
1812 
1813     if (!formatter->abridgedTransformation()) {
1814         if (!coordinateOperationAccuracies().empty()) {
1815             writer->AddObjKey("accuracy");
1816             writer->Add(coordinateOperationAccuracies()[0]->value());
1817         }
1818     }
1819 
1820     if (formatter->abridgedTransformation()) {
1821         if (formatter->outputId()) {
1822             formatID(formatter);
1823         }
1824     } else {
1825         ObjectUsage::baseExportToJSON(formatter);
1826     }
1827 }
1828 
1829 //! @endcond
1830 
1831 //! @cond Doxygen_Suppress
1832 static const std::string nullString;
1833 
_getNTv2Filename(const Transformation * op,bool allowInverse)1834 static const std::string &_getNTv2Filename(const Transformation *op,
1835                                            bool allowInverse) {
1836 
1837     const auto &l_method = op->method();
1838     if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 ||
1839         (allowInverse &&
1840          ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) {
1841         const auto &fileParameter = op->parameterValue(
1842             EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
1843             EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
1844         if (fileParameter &&
1845             fileParameter->type() == ParameterValue::Type::FILENAME) {
1846             return fileParameter->valueFile();
1847         }
1848     }
1849     return nullString;
1850 }
1851 //! @endcond
1852 
1853 // ---------------------------------------------------------------------------
1854 //! @cond Doxygen_Suppress
getNTv2Filename() const1855 const std::string &Transformation::getNTv2Filename() const {
1856 
1857     return _getNTv2Filename(this, false);
1858 }
1859 //! @endcond
1860 
1861 // ---------------------------------------------------------------------------
1862 
1863 //! @cond Doxygen_Suppress
_getNTv1Filename(const Transformation * op,bool allowInverse)1864 static const std::string &_getNTv1Filename(const Transformation *op,
1865                                            bool allowInverse) {
1866 
1867     const auto &l_method = op->method();
1868     const auto &methodName = l_method->nameStr();
1869     if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 ||
1870         (allowInverse &&
1871          ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) {
1872         const auto &fileParameter = op->parameterValue(
1873             EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
1874             EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
1875         if (fileParameter &&
1876             fileParameter->type() == ParameterValue::Type::FILENAME) {
1877             return fileParameter->valueFile();
1878         }
1879     }
1880     return nullString;
1881 }
1882 //! @endcond
1883 
1884 // ---------------------------------------------------------------------------
1885 
1886 //! @cond Doxygen_Suppress
_getCTABLE2Filename(const Transformation * op,bool allowInverse)1887 static const std::string &_getCTABLE2Filename(const Transformation *op,
1888                                               bool allowInverse) {
1889     const auto &l_method = op->method();
1890     const auto &methodName = l_method->nameStr();
1891     if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) ||
1892         (allowInverse &&
1893          ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) {
1894         const auto &fileParameter = op->parameterValue(
1895             EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
1896             EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
1897         if (fileParameter &&
1898             fileParameter->type() == ParameterValue::Type::FILENAME) {
1899             return fileParameter->valueFile();
1900         }
1901     }
1902     return nullString;
1903 }
1904 //! @endcond
1905 
1906 // ---------------------------------------------------------------------------
1907 
1908 //! @cond Doxygen_Suppress
1909 static const std::string &
_getHorizontalShiftGTIFFFilename(const Transformation * op,bool allowInverse)1910 _getHorizontalShiftGTIFFFilename(const Transformation *op, bool allowInverse) {
1911     const auto &l_method = op->method();
1912     const auto &methodName = l_method->nameStr();
1913     if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) ||
1914         (allowInverse &&
1915          ci_equal(methodName,
1916                   INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF))) {
1917         const auto &fileParameter = op->parameterValue(
1918             EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE,
1919             EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE);
1920         if (fileParameter &&
1921             fileParameter->type() == ParameterValue::Type::FILENAME) {
1922             return fileParameter->valueFile();
1923         }
1924     }
1925     return nullString;
1926 }
1927 //! @endcond
1928 
1929 // ---------------------------------------------------------------------------
1930 
1931 //! @cond Doxygen_Suppress
1932 static const std::string &
_getGeocentricTranslationFilename(const Transformation * op,bool allowInverse)1933 _getGeocentricTranslationFilename(const Transformation *op, bool allowInverse) {
1934 
1935     const auto &l_method = op->method();
1936     const auto &methodName = l_method->nameStr();
1937     if (l_method->getEPSGCode() ==
1938             EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN ||
1939         (allowInverse &&
1940          ci_equal(
1941              methodName,
1942              INVERSE_OF +
1943                  EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN))) {
1944         const auto &fileParameter =
1945             op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE,
1946                                EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE);
1947         if (fileParameter &&
1948             fileParameter->type() == ParameterValue::Type::FILENAME) {
1949             return fileParameter->valueFile();
1950         }
1951     }
1952     return nullString;
1953 }
1954 //! @endcond
1955 
1956 // ---------------------------------------------------------------------------
1957 
1958 //! @cond Doxygen_Suppress
1959 static const std::string &
_getHeightToGeographic3DFilename(const Transformation * op,bool allowInverse)1960 _getHeightToGeographic3DFilename(const Transformation *op, bool allowInverse) {
1961 
1962     const auto &methodName = op->method()->nameStr();
1963 
1964     if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) ||
1965         (allowInverse &&
1966          ci_equal(methodName,
1967                   INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) {
1968         const auto &fileParameter =
1969             op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
1970                                EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
1971         if (fileParameter &&
1972             fileParameter->type() == ParameterValue::Type::FILENAME) {
1973             return fileParameter->valueFile();
1974         }
1975     }
1976     return nullString;
1977 }
1978 //! @endcond
1979 
1980 // ---------------------------------------------------------------------------
1981 
1982 //! @cond Doxygen_Suppress
1983 static bool
isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr & method,bool allowInverse)1984 isGeographic3DToGravityRelatedHeight(const OperationMethodNNPtr &method,
1985                                      bool allowInverse) {
1986     const auto &methodName = method->nameStr();
1987     static const char *const methodCodes[] = {
1988         "1025", // Geographic3D to GravityRelatedHeight (EGM2008)
1989         "1030", // Geographic3D to GravityRelatedHeight (NZgeoid)
1990         "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire)
1991         "1047", // Geographic3D to GravityRelatedHeight (Gravsoft)
1992         "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2)
1993         "1050", // Geographic3D to GravityRelatedHeight (CI)
1994         "1059", // Geographic3D to GravityRelatedHeight (PNG)
1995         "1060", // Geographic3D to GravityRelatedHeight (CGG2013)
1996         "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire)
1997         "1073", // Geographic3D to GravityRelatedHeight (IGN2009)
1998         "1081", // Geographic3D to GravityRelatedHeight (BEV AT)
1999         "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2)
2000         "9661", // Geographic3D to GravityRelatedHeight (EGM)
2001         "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98)
2002         "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB)
2003         "9664", // Geographic3D to GravityRelatedHeight (IGN1997)
2004         "9665", // Geographic3D to GravityRelatedHeight (US .gtx)
2005         "9635", // Geog3D to Geog2D+GravityRelatedHeight (US .gtx)
2006     };
2007 
2008     if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) {
2009         return true;
2010     }
2011     if (allowInverse &&
2012         ci_find(methodName,
2013                 INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) {
2014         return true;
2015     }
2016 
2017     for (const auto &code : methodCodes) {
2018         for (const auto &idSrc : method->identifiers()) {
2019             const auto &srcAuthName = *(idSrc->codeSpace());
2020             const auto &srcCode = idSrc->code();
2021             if (ci_equal(srcAuthName, "EPSG") && srcCode == code) {
2022                 return true;
2023             }
2024             if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") &&
2025                 srcCode == code) {
2026                 return true;
2027             }
2028         }
2029     }
2030     return false;
2031 }
2032 //! @endcond
2033 
2034 // ---------------------------------------------------------------------------
2035 
2036 //! @cond Doxygen_Suppress
getHeightToGeographic3DFilename() const2037 const std::string &Transformation::getHeightToGeographic3DFilename() const {
2038 
2039     const std::string &ret = _getHeightToGeographic3DFilename(this, false);
2040     if (!ret.empty())
2041         return ret;
2042     if (isGeographic3DToGravityRelatedHeight(method(), false)) {
2043         const auto &fileParameter =
2044             parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
2045                            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
2046         if (fileParameter &&
2047             fileParameter->type() == ParameterValue::Type::FILENAME) {
2048             return fileParameter->valueFile();
2049         }
2050     }
2051     return nullString;
2052 }
2053 //! @endcond
2054 
2055 // ---------------------------------------------------------------------------
2056 
2057 //! @cond Doxygen_Suppress
2058 static util::PropertyMap
createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj)2059 createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) {
2060     util::PropertyMap map;
2061 
2062     const std::string &forwardName = obj->nameStr();
2063     if (!forwardName.empty()) {
2064         map.set(common::IdentifiedObject::NAME_KEY, forwardName);
2065     }
2066 
2067     {
2068         auto ar = util::ArrayOfBaseObject::create();
2069         for (const auto &idSrc : obj->identifiers()) {
2070             const auto &srcAuthName = *(idSrc->codeSpace());
2071             const auto &srcCode = idSrc->code();
2072             auto idsProp = util::PropertyMap().set(
2073                 metadata::Identifier::CODESPACE_KEY, srcAuthName);
2074             ar->add(metadata::Identifier::create(srcCode, idsProp));
2075         }
2076         if (!ar->empty()) {
2077             map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar);
2078         }
2079     }
2080 
2081     return map;
2082 }
2083 //! @endcond
2084 
2085 // ---------------------------------------------------------------------------
2086 
2087 //! @cond Doxygen_Suppress
2088 static util::PropertyMap
createSimilarPropertiesTransformation(TransformationNNPtr obj)2089 createSimilarPropertiesTransformation(TransformationNNPtr obj) {
2090     util::PropertyMap map;
2091 
2092     // The domain(s) are unchanged
2093     addDomains(map, obj.get());
2094 
2095     const std::string &forwardName = obj->nameStr();
2096     if (!forwardName.empty()) {
2097         map.set(common::IdentifiedObject::NAME_KEY, forwardName);
2098     }
2099 
2100     const std::string &remarks = obj->remarks();
2101     if (!remarks.empty()) {
2102         map.set(common::IdentifiedObject::REMARKS_KEY, remarks);
2103     }
2104 
2105     addModifiedIdentifier(map, obj.get(), false, true);
2106 
2107     return map;
2108 }
2109 //! @endcond
2110 
2111 // ---------------------------------------------------------------------------
2112 
2113 //! @cond Doxygen_Suppress
2114 static TransformationNNPtr
createNTv1(const util::PropertyMap & properties,const crs::CRSNNPtr & sourceCRSIn,const crs::CRSNNPtr & targetCRSIn,const std::string & filename,const std::vector<metadata::PositionalAccuracyNNPtr> & accuracies)2115 createNTv1(const util::PropertyMap &properties,
2116            const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn,
2117            const std::string &filename,
2118            const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) {
2119     return Transformation::create(
2120         properties, sourceCRSIn, targetCRSIn, nullptr,
2121         createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1),
2122         {OperationParameter::create(
2123             util::PropertyMap()
2124                 .set(common::IdentifiedObject::NAME_KEY,
2125                      EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)
2126                 .set(metadata::Identifier::CODESPACE_KEY,
2127                      metadata::Identifier::EPSG)
2128                 .set(metadata::Identifier::CODE_KEY,
2129                      EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE))},
2130         {ParameterValue::createFilename(filename)}, accuracies);
2131 }
2132 //! @endcond
2133 
2134 // ---------------------------------------------------------------------------
2135 
2136 /** \brief Return an equivalent transformation to the current one, but using
2137  * PROJ alternative grid names.
2138  */
substitutePROJAlternativeGridNames(io::DatabaseContextNNPtr databaseContext) const2139 TransformationNNPtr Transformation::substitutePROJAlternativeGridNames(
2140     io::DatabaseContextNNPtr databaseContext) const {
2141     auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
2142         shared_from_this().as_nullable()));
2143 
2144     const auto &l_method = method();
2145     const int methodEPSGCode = l_method->getEPSGCode();
2146 
2147     std::string projFilename;
2148     std::string projGridFormat;
2149     bool inverseDirection = false;
2150 
2151     const auto &NTv1Filename = _getNTv1Filename(this, false);
2152     const auto &NTv2Filename = _getNTv2Filename(this, false);
2153     std::string lasFilename;
2154     if (methodEPSGCode == EPSG_CODE_METHOD_NADCON) {
2155         const auto &latitudeFileParameter =
2156             parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE,
2157                            EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE);
2158         const auto &longitudeFileParameter =
2159             parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE,
2160                            EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE);
2161         if (latitudeFileParameter &&
2162             latitudeFileParameter->type() == ParameterValue::Type::FILENAME &&
2163             longitudeFileParameter &&
2164             longitudeFileParameter->type() == ParameterValue::Type::FILENAME) {
2165             lasFilename = latitudeFileParameter->valueFile();
2166         }
2167     }
2168     const auto &horizontalGridName =
2169         !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
2170                                                    ? NTv2Filename
2171                                                    : lasFilename;
2172 
2173     if (!horizontalGridName.empty() &&
2174         databaseContext->lookForGridAlternative(horizontalGridName,
2175                                                 projFilename, projGridFormat,
2176                                                 inverseDirection)) {
2177 
2178         if (horizontalGridName == projFilename) {
2179             if (inverseDirection) {
2180                 throw util::UnsupportedOperationException(
2181                     "Inverse direction for " + projFilename + " not supported");
2182             }
2183             return self;
2184         }
2185 
2186         const auto &l_sourceCRS = sourceCRS();
2187         const auto &l_targetCRS = targetCRS();
2188         const auto &l_accuracies = coordinateOperationAccuracies();
2189         if (projGridFormat == "GTiff") {
2190             auto parameters =
2191                 std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
2192                     EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
2193             auto methodProperties = util::PropertyMap().set(
2194                 common::IdentifiedObject::NAME_KEY,
2195                 PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF);
2196             auto values = std::vector<ParameterValueNNPtr>{
2197                 ParameterValue::createFilename(projFilename)};
2198             if (inverseDirection) {
2199                 return create(createPropertiesForInverse(
2200                                   self.as_nullable().get(), true, false),
2201                               l_targetCRS, l_sourceCRS, nullptr,
2202                               methodProperties, parameters, values,
2203                               l_accuracies)
2204                     ->inverseAsTransformation();
2205 
2206             } else {
2207                 return create(createSimilarPropertiesTransformation(self),
2208                               l_sourceCRS, l_targetCRS, nullptr,
2209                               methodProperties, parameters, values,
2210                               l_accuracies);
2211             }
2212         } else if (projGridFormat == "NTv1") {
2213             if (inverseDirection) {
2214                 return createNTv1(createPropertiesForInverse(
2215                                       self.as_nullable().get(), true, false),
2216                                   l_targetCRS, l_sourceCRS, projFilename,
2217                                   l_accuracies)
2218                     ->inverseAsTransformation();
2219             } else {
2220                 return createNTv1(createSimilarPropertiesTransformation(self),
2221                                   l_sourceCRS, l_targetCRS, projFilename,
2222                                   l_accuracies);
2223             }
2224         } else if (projGridFormat == "NTv2") {
2225             if (inverseDirection) {
2226                 return createNTv2(createPropertiesForInverse(
2227                                       self.as_nullable().get(), true, false),
2228                                   l_targetCRS, l_sourceCRS, projFilename,
2229                                   l_accuracies)
2230                     ->inverseAsTransformation();
2231             } else {
2232                 return createNTv2(createSimilarPropertiesTransformation(self),
2233                                   l_sourceCRS, l_targetCRS, projFilename,
2234                                   l_accuracies);
2235             }
2236         } else if (projGridFormat == "CTable2") {
2237             auto parameters =
2238                 std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
2239                     EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)};
2240             auto methodProperties =
2241                 util::PropertyMap().set(common::IdentifiedObject::NAME_KEY,
2242                                         PROJ_WKT2_NAME_METHOD_CTABLE2);
2243             auto values = std::vector<ParameterValueNNPtr>{
2244                 ParameterValue::createFilename(projFilename)};
2245             if (inverseDirection) {
2246                 return create(createPropertiesForInverse(
2247                                   self.as_nullable().get(), true, false),
2248                               l_targetCRS, l_sourceCRS, nullptr,
2249                               methodProperties, parameters, values,
2250                               l_accuracies)
2251                     ->inverseAsTransformation();
2252 
2253             } else {
2254                 return create(createSimilarPropertiesTransformation(self),
2255                               l_sourceCRS, l_targetCRS, nullptr,
2256                               methodProperties, parameters, values,
2257                               l_accuracies);
2258             }
2259         }
2260     }
2261 
2262     if (isGeographic3DToGravityRelatedHeight(method(), false)) {
2263         const auto &fileParameter =
2264             parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
2265                            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
2266         if (fileParameter &&
2267             fileParameter->type() == ParameterValue::Type::FILENAME) {
2268             auto filename = fileParameter->valueFile();
2269             if (databaseContext->lookForGridAlternative(
2270                     filename, projFilename, projGridFormat, inverseDirection)) {
2271 
2272                 if (inverseDirection) {
2273                     throw util::UnsupportedOperationException(
2274                         "Inverse direction for "
2275                         "Geographic3DToGravityRelatedHeight not supported");
2276                 }
2277 
2278                 if (filename == projFilename) {
2279                     return self;
2280                 }
2281 
2282                 auto parameters = std::vector<OperationParameterNNPtr>{
2283                     createOpParamNameEPSGCode(
2284                         EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)};
2285 #ifdef disabled_for_now
2286                 if (inverseDirection) {
2287                     return create(createPropertiesForInverse(
2288                                       self.as_nullable().get(), true, false),
2289                                   targetCRS(), sourceCRS(), nullptr,
2290                                   createSimilarPropertiesMethod(method()),
2291                                   parameters, {ParameterValue::createFilename(
2292                                                   projFilename)},
2293                                   coordinateOperationAccuracies())
2294                         ->inverseAsTransformation();
2295                 } else
2296 #endif
2297                 {
2298                     return create(
2299                         createSimilarPropertiesTransformation(self),
2300                         sourceCRS(), targetCRS(), nullptr,
2301                         createSimilarPropertiesMethod(method()), parameters,
2302                         {ParameterValue::createFilename(projFilename)},
2303                         coordinateOperationAccuracies());
2304                 }
2305             }
2306         }
2307     }
2308 
2309     const auto &geocentricTranslationFilename =
2310         _getGeocentricTranslationFilename(this, false);
2311     if (!geocentricTranslationFilename.empty()) {
2312         if (databaseContext->lookForGridAlternative(
2313                 geocentricTranslationFilename, projFilename, projGridFormat,
2314                 inverseDirection)) {
2315 
2316             if (inverseDirection) {
2317                 throw util::UnsupportedOperationException(
2318                     "Inverse direction for "
2319                     "GeocentricTranslation not supported");
2320             }
2321 
2322             if (geocentricTranslationFilename == projFilename) {
2323                 return self;
2324             }
2325 
2326             auto parameters =
2327                 std::vector<OperationParameterNNPtr>{createOpParamNameEPSGCode(
2328                     EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)};
2329             return create(createSimilarPropertiesTransformation(self),
2330                           sourceCRS(), targetCRS(), interpolationCRS(),
2331                           createSimilarPropertiesMethod(method()), parameters,
2332                           {ParameterValue::createFilename(projFilename)},
2333                           coordinateOperationAccuracies());
2334         }
2335     }
2336 
2337     if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON ||
2338         methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
2339         methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
2340         methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) {
2341         auto fileParameter =
2342             parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
2343                            EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
2344         if (fileParameter &&
2345             fileParameter->type() == ParameterValue::Type::FILENAME) {
2346 
2347             auto filename = fileParameter->valueFile();
2348             if (databaseContext->lookForGridAlternative(
2349                     filename, projFilename, projGridFormat, inverseDirection)) {
2350 
2351                 if (filename == projFilename) {
2352                     if (inverseDirection) {
2353                         throw util::UnsupportedOperationException(
2354                             "Inverse direction for " + projFilename +
2355                             " not supported");
2356                     }
2357                     return self;
2358                 }
2359 
2360                 auto parameters = std::vector<OperationParameterNNPtr>{
2361                     createOpParamNameEPSGCode(
2362                         EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE)};
2363                 if (inverseDirection) {
2364                     return create(createPropertiesForInverse(
2365                                       self.as_nullable().get(), true, false),
2366                                   targetCRS(), sourceCRS(), nullptr,
2367                                   createSimilarPropertiesMethod(method()),
2368                                   parameters, {ParameterValue::createFilename(
2369                                                   projFilename)},
2370                                   coordinateOperationAccuracies())
2371                         ->inverseAsTransformation();
2372                 } else {
2373                     return create(
2374                         createSimilarPropertiesTransformation(self),
2375                         sourceCRS(), targetCRS(), nullptr,
2376                         createSimilarPropertiesMethod(method()), parameters,
2377                         {ParameterValue::createFilename(projFilename)},
2378                         coordinateOperationAccuracies());
2379                 }
2380             }
2381         }
2382     }
2383 
2384     return self;
2385 }
2386 
2387 // ---------------------------------------------------------------------------
2388 
2389 //! @cond Doxygen_Suppress
2390 
ThrowExceptionNotGeodeticGeographic(const char * trfrm_name)2391 static void ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) {
2392     throw io::FormattingException(concat("Can apply ", std::string(trfrm_name),
2393                                          " only to GeodeticCRS / "
2394                                          "GeographicCRS"));
2395 }
2396 
2397 // ---------------------------------------------------------------------------
2398 
2399 // If crs is a geographic CRS, or a compound CRS of a geographic CRS,
2400 // or a compoundCRS of a bound CRS of a geographic CRS, return that
2401 // geographic CRS
2402 static crs::GeographicCRSPtr
extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr & crs)2403 extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) {
2404     auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs);
2405     if (!geogCRS) {
2406         auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs);
2407         if (compoundCRS) {
2408             const auto &components = compoundCRS->componentReferenceSystems();
2409             if (!components.empty()) {
2410                 geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
2411                     components[0]);
2412                 if (!geogCRS) {
2413                     auto boundCRS =
2414                         util::nn_dynamic_pointer_cast<crs::BoundCRS>(
2415                             components[0]);
2416                     if (boundCRS) {
2417                         geogCRS =
2418                             util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
2419                                 boundCRS->baseCRS());
2420                     }
2421                 }
2422             }
2423         } else {
2424             auto boundCRS = util::nn_dynamic_pointer_cast<crs::BoundCRS>(crs);
2425             if (boundCRS) {
2426                 geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(
2427                     boundCRS->baseCRS());
2428             }
2429         }
2430     }
2431     return geogCRS;
2432 }
2433 
2434 // ---------------------------------------------------------------------------
2435 
setupPROJGeodeticSourceCRS(io::PROJStringFormatter * formatter,const crs::CRSNNPtr & crs,bool addPushV3,const char * trfrm_name)2436 static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter,
2437                                        const crs::CRSNNPtr &crs, bool addPushV3,
2438                                        const char *trfrm_name) {
2439     auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
2440     if (sourceCRSGeog) {
2441         formatter->startInversion();
2442         sourceCRSGeog->_exportToPROJString(formatter);
2443         formatter->stopInversion();
2444         if (util::isOfExactType<crs::DerivedGeographicCRS>(
2445                 *(sourceCRSGeog.get()))) {
2446             // The export of a DerivedGeographicCRS in non-CRS mode adds
2447             // unit conversion and axis swapping. We must compensate for that
2448             formatter->startInversion();
2449             sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2450             formatter->stopInversion();
2451         }
2452 
2453         if (addPushV3) {
2454             formatter->addStep("push");
2455             formatter->addParam("v_3");
2456         }
2457 
2458         formatter->addStep("cart");
2459         sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
2460     } else {
2461         auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
2462         if (!sourceCRSGeod) {
2463             ThrowExceptionNotGeodeticGeographic(trfrm_name);
2464         }
2465         formatter->startInversion();
2466         sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
2467         formatter->stopInversion();
2468     }
2469 }
2470 // ---------------------------------------------------------------------------
2471 
setupPROJGeodeticTargetCRS(io::PROJStringFormatter * formatter,const crs::CRSNNPtr & crs,bool addPopV3,const char * trfrm_name)2472 static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter,
2473                                        const crs::CRSNNPtr &crs, bool addPopV3,
2474                                        const char *trfrm_name) {
2475     auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs);
2476     if (targetCRSGeog) {
2477         formatter->addStep("cart");
2478         formatter->setCurrentStepInverted(true);
2479         targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
2480 
2481         if (addPopV3) {
2482             formatter->addStep("pop");
2483             formatter->addParam("v_3");
2484         }
2485         if (util::isOfExactType<crs::DerivedGeographicCRS>(
2486                 *(targetCRSGeog.get()))) {
2487             // The export of a DerivedGeographicCRS in non-CRS mode adds
2488             // unit conversion and axis swapping. We must compensate for that
2489             targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2490         }
2491         targetCRSGeog->_exportToPROJString(formatter);
2492     } else {
2493         auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get());
2494         if (!targetCRSGeod) {
2495             ThrowExceptionNotGeodeticGeographic(trfrm_name);
2496         }
2497         targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter);
2498     }
2499 }
2500 
2501 //! @endcond
2502 
2503 // ---------------------------------------------------------------------------
2504 
_exportToPROJString(io::PROJStringFormatter * formatter) const2505 void Transformation::_exportToPROJString(
2506     io::PROJStringFormatter *formatter) const // throw(FormattingException)
2507 {
2508     if (formatter->convention() ==
2509         io::PROJStringFormatter::Convention::PROJ_4) {
2510         throw io::FormattingException(
2511             "Transformation cannot be exported as a PROJ.4 string");
2512     }
2513 
2514     formatter->setCoordinateOperationOptimizations(true);
2515 
2516     bool positionVectorConvention = true;
2517     bool sevenParamsTransform = false;
2518     bool threeParamsTransform = false;
2519     bool fifteenParamsTransform = false;
2520     const auto &l_method = method();
2521     const int methodEPSGCode = l_method->getEPSGCode();
2522     const auto &methodName = l_method->nameStr();
2523     const auto paramCount = parameterValues().size();
2524     const bool l_isTimeDependent = isTimeDependent(methodName);
2525     const bool isPositionVector =
2526         ci_find(methodName, "Position Vector") != std::string::npos ||
2527         ci_find(methodName, "PV") != std::string::npos;
2528     const bool isCoordinateFrame =
2529         ci_find(methodName, "Coordinate Frame") != std::string::npos ||
2530         ci_find(methodName, "CF") != std::string::npos;
2531     if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) ||
2532         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC ||
2533         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D ||
2534         methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D) {
2535         positionVectorConvention = false;
2536         sevenParamsTransform = true;
2537     } else if (
2538         (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) ||
2539         methodEPSGCode ==
2540             EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC ||
2541         methodEPSGCode ==
2542             EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D ||
2543         methodEPSGCode ==
2544             EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) {
2545         positionVectorConvention = false;
2546         fifteenParamsTransform = true;
2547     } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) ||
2548                methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC ||
2549                methodEPSGCode ==
2550                    EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D ||
2551                methodEPSGCode ==
2552                    EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) {
2553         sevenParamsTransform = true;
2554     } else if (
2555         (paramCount == 15 && isPositionVector && l_isTimeDependent) ||
2556         methodEPSGCode ==
2557             EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC ||
2558         methodEPSGCode ==
2559             EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D ||
2560         methodEPSGCode ==
2561             EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) {
2562         fifteenParamsTransform = true;
2563     } else if ((paramCount == 3 &&
2564                 ci_find(methodName, "Geocentric translations") !=
2565                     std::string::npos) ||
2566                methodEPSGCode ==
2567                    EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC ||
2568                methodEPSGCode ==
2569                    EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D ||
2570                methodEPSGCode ==
2571                    EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) {
2572         threeParamsTransform = true;
2573     }
2574     if (threeParamsTransform || sevenParamsTransform ||
2575         fifteenParamsTransform) {
2576         double x =
2577             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
2578         double y =
2579             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
2580         double z =
2581             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
2582 
2583         auto sourceCRSGeog =
2584             dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
2585         auto targetCRSGeog =
2586             dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
2587         const bool addPushPopV3 =
2588             !CoordinateOperation::getPrivate()->use3DHelmert_ &&
2589             ((sourceCRSGeog &&
2590               sourceCRSGeog->coordinateSystem()->axisList().size() == 2) ||
2591              (targetCRSGeog &&
2592               targetCRSGeog->coordinateSystem()->axisList().size() == 2));
2593 
2594         setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3,
2595                                    "Helmert");
2596 
2597         formatter->addStep("helmert");
2598         formatter->addParam("x", x);
2599         formatter->addParam("y", y);
2600         formatter->addParam("z", z);
2601         if (sevenParamsTransform || fifteenParamsTransform) {
2602             double rx =
2603                 parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
2604                                       common::UnitOfMeasure::ARC_SECOND);
2605             double ry =
2606                 parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
2607                                       common::UnitOfMeasure::ARC_SECOND);
2608             double rz =
2609                 parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
2610                                       common::UnitOfMeasure::ARC_SECOND);
2611             double scaleDiff =
2612                 parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
2613                                       common::UnitOfMeasure::PARTS_PER_MILLION);
2614             formatter->addParam("rx", rx);
2615             formatter->addParam("ry", ry);
2616             formatter->addParam("rz", rz);
2617             formatter->addParam("s", scaleDiff);
2618             if (fifteenParamsTransform) {
2619                 double rate_x = parameterValueNumeric(
2620                     EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION,
2621                     common::UnitOfMeasure::METRE_PER_YEAR);
2622                 double rate_y = parameterValueNumeric(
2623                     EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION,
2624                     common::UnitOfMeasure::METRE_PER_YEAR);
2625                 double rate_z = parameterValueNumeric(
2626                     EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION,
2627                     common::UnitOfMeasure::METRE_PER_YEAR);
2628                 double rate_rx = parameterValueNumeric(
2629                     EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION,
2630                     common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
2631                 double rate_ry = parameterValueNumeric(
2632                     EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION,
2633                     common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
2634                 double rate_rz = parameterValueNumeric(
2635                     EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION,
2636                     common::UnitOfMeasure::ARC_SECOND_PER_YEAR);
2637                 double rate_scaleDiff = parameterValueNumeric(
2638                     EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE,
2639                     common::UnitOfMeasure::PPM_PER_YEAR);
2640                 double referenceEpochYear =
2641                     parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH,
2642                                           common::UnitOfMeasure::YEAR);
2643                 formatter->addParam("dx", rate_x);
2644                 formatter->addParam("dy", rate_y);
2645                 formatter->addParam("dz", rate_z);
2646                 formatter->addParam("drx", rate_rx);
2647                 formatter->addParam("dry", rate_ry);
2648                 formatter->addParam("drz", rate_rz);
2649                 formatter->addParam("ds", rate_scaleDiff);
2650                 formatter->addParam("t_epoch", referenceEpochYear);
2651             }
2652             if (positionVectorConvention) {
2653                 formatter->addParam("convention", "position_vector");
2654             } else {
2655                 formatter->addParam("convention", "coordinate_frame");
2656             }
2657         }
2658 
2659         setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3,
2660                                    "Helmert");
2661 
2662         return;
2663     }
2664 
2665     if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC ||
2666         methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
2667         methodEPSGCode ==
2668             EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D ||
2669         methodEPSGCode ==
2670             EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
2671         methodEPSGCode ==
2672             EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D ||
2673         methodEPSGCode ==
2674             EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) {
2675 
2676         positionVectorConvention =
2677             isPositionVector ||
2678             methodEPSGCode ==
2679                 EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC ||
2680             methodEPSGCode ==
2681                 EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D ||
2682             methodEPSGCode ==
2683                 EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D;
2684 
2685         double x =
2686             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
2687         double y =
2688             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
2689         double z =
2690             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
2691         double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION,
2692                                           common::UnitOfMeasure::ARC_SECOND);
2693         double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION,
2694                                           common::UnitOfMeasure::ARC_SECOND);
2695         double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION,
2696                                           common::UnitOfMeasure::ARC_SECOND);
2697         double scaleDiff =
2698             parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE,
2699                                   common::UnitOfMeasure::PARTS_PER_MILLION);
2700 
2701         double px = parameterValueNumericAsSI(
2702             EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT);
2703         double py = parameterValueNumericAsSI(
2704             EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT);
2705         double pz = parameterValueNumericAsSI(
2706             EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT);
2707 
2708         bool addPushPopV3 =
2709             (methodEPSGCode ==
2710                  EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D ||
2711              methodEPSGCode ==
2712                  EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D);
2713 
2714         setupPROJGeodeticSourceCRS(formatter, sourceCRS(), addPushPopV3,
2715                                    "Molodensky-Badekas");
2716 
2717         formatter->addStep("molobadekas");
2718         formatter->addParam("x", x);
2719         formatter->addParam("y", y);
2720         formatter->addParam("z", z);
2721         formatter->addParam("rx", rx);
2722         formatter->addParam("ry", ry);
2723         formatter->addParam("rz", rz);
2724         formatter->addParam("s", scaleDiff);
2725         formatter->addParam("px", px);
2726         formatter->addParam("py", py);
2727         formatter->addParam("pz", pz);
2728         if (positionVectorConvention) {
2729             formatter->addParam("convention", "position_vector");
2730         } else {
2731             formatter->addParam("convention", "coordinate_frame");
2732         }
2733 
2734         setupPROJGeodeticTargetCRS(formatter, targetCRS(), addPushPopV3,
2735                                    "Molodensky-Badekas");
2736 
2737         return;
2738     }
2739 
2740     if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY ||
2741         methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
2742         double x =
2743             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION);
2744         double y =
2745             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION);
2746         double z =
2747             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION);
2748         double da = parameterValueNumericAsSI(
2749             EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE);
2750         double df = parameterValueNumericAsSI(
2751             EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE);
2752 
2753         auto sourceCRSGeog =
2754             dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
2755         if (!sourceCRSGeog) {
2756             throw io::FormattingException(
2757                 "Can apply Molodensky only to GeographicCRS");
2758         }
2759 
2760         auto targetCRSGeog =
2761             dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
2762         if (!targetCRSGeog) {
2763             throw io::FormattingException(
2764                 "Can apply Molodensky only to GeographicCRS");
2765         }
2766 
2767         formatter->startInversion();
2768         sourceCRSGeog->_exportToPROJString(formatter);
2769         formatter->stopInversion();
2770 
2771         formatter->addStep("molodensky");
2772         sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
2773         formatter->addParam("dx", x);
2774         formatter->addParam("dy", y);
2775         formatter->addParam("dz", z);
2776         formatter->addParam("da", da);
2777         formatter->addParam("df", df);
2778 
2779         if (ci_find(methodName, "Abridged") != std::string::npos ||
2780             methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) {
2781             formatter->addParam("abridged");
2782         }
2783 
2784         targetCRSGeog->_exportToPROJString(formatter);
2785 
2786         return;
2787     }
2788 
2789     if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) {
2790         double offsetLat =
2791             parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
2792                                   common::UnitOfMeasure::ARC_SECOND);
2793         double offsetLong =
2794             parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
2795                                   common::UnitOfMeasure::ARC_SECOND);
2796 
2797         auto sourceCRSGeog =
2798             dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
2799         if (!sourceCRSGeog) {
2800             throw io::FormattingException(
2801                 "Can apply Geographic 2D offsets only to GeographicCRS");
2802         }
2803 
2804         auto targetCRSGeog =
2805             dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
2806         if (!targetCRSGeog) {
2807             throw io::FormattingException(
2808                 "Can apply Geographic 2D offsets only to GeographicCRS");
2809         }
2810 
2811         formatter->startInversion();
2812         sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2813         formatter->stopInversion();
2814 
2815         if (offsetLat != 0.0 || offsetLong != 0.0) {
2816             formatter->addStep("geogoffset");
2817             formatter->addParam("dlat", offsetLat);
2818             formatter->addParam("dlon", offsetLong);
2819         }
2820 
2821         targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2822 
2823         return;
2824     }
2825 
2826     if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) {
2827         double offsetLat =
2828             parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
2829                                   common::UnitOfMeasure::ARC_SECOND);
2830         double offsetLong =
2831             parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
2832                                   common::UnitOfMeasure::ARC_SECOND);
2833         double offsetHeight =
2834             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
2835 
2836         auto sourceCRSGeog =
2837             dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
2838         if (!sourceCRSGeog) {
2839             throw io::FormattingException(
2840                 "Can apply Geographic 3D offsets only to GeographicCRS");
2841         }
2842 
2843         auto targetCRSGeog =
2844             dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
2845         if (!targetCRSGeog) {
2846             throw io::FormattingException(
2847                 "Can apply Geographic 3D offsets only to GeographicCRS");
2848         }
2849 
2850         formatter->startInversion();
2851         sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2852         formatter->stopInversion();
2853 
2854         if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
2855             formatter->addStep("geogoffset");
2856             formatter->addParam("dlat", offsetLat);
2857             formatter->addParam("dlon", offsetLong);
2858             formatter->addParam("dh", offsetHeight);
2859         }
2860 
2861         targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2862 
2863         return;
2864     }
2865 
2866     if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) {
2867         double offsetLat =
2868             parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET,
2869                                   common::UnitOfMeasure::ARC_SECOND);
2870         double offsetLong =
2871             parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
2872                                   common::UnitOfMeasure::ARC_SECOND);
2873         double offsetHeight =
2874             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_UNDULATION);
2875 
2876         auto sourceCRSGeog =
2877             dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
2878         if (!sourceCRSGeog) {
2879             auto sourceCRSCompound =
2880                 dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get());
2881             if (sourceCRSCompound) {
2882                 sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get();
2883             }
2884             if (!sourceCRSGeog) {
2885                 throw io::FormattingException("Can apply Geographic 2D with "
2886                                               "height offsets only to "
2887                                               "GeographicCRS / CompoundCRS");
2888             }
2889         }
2890 
2891         auto targetCRSGeog =
2892             dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
2893         if (!targetCRSGeog) {
2894             auto targetCRSCompound =
2895                 dynamic_cast<const crs::CompoundCRS *>(targetCRS().get());
2896             if (targetCRSCompound) {
2897                 targetCRSGeog = targetCRSCompound->extractGeographicCRS().get();
2898             }
2899             if (!targetCRSGeog) {
2900                 throw io::FormattingException("Can apply Geographic 2D with "
2901                                               "height offsets only to "
2902                                               "GeographicCRS / CompoundCRS");
2903             }
2904         }
2905 
2906         formatter->startInversion();
2907         sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2908         formatter->stopInversion();
2909 
2910         if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) {
2911             formatter->addStep("geogoffset");
2912             formatter->addParam("dlat", offsetLat);
2913             formatter->addParam("dlon", offsetLong);
2914             formatter->addParam("dh", offsetHeight);
2915         }
2916 
2917         targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2918 
2919         return;
2920     }
2921 
2922     if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) {
2923 
2924         auto sourceCRSVert =
2925             dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get());
2926         if (!sourceCRSVert) {
2927             throw io::FormattingException(
2928                 "Can apply Vertical offset only to VerticalCRS");
2929         }
2930 
2931         auto targetCRSVert =
2932             dynamic_cast<const crs::VerticalCRS *>(targetCRS().get());
2933         if (!targetCRSVert) {
2934             throw io::FormattingException(
2935                 "Can apply Vertical offset only to VerticalCRS");
2936         }
2937 
2938         auto offsetHeight =
2939             parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET);
2940 
2941         formatter->startInversion();
2942         sourceCRSVert->addLinearUnitConvert(formatter);
2943         formatter->stopInversion();
2944 
2945         formatter->addStep("geogoffset");
2946         formatter->addParam("dh", offsetHeight);
2947 
2948         targetCRSVert->addLinearUnitConvert(formatter);
2949 
2950         return;
2951     }
2952 
2953     // Substitute grid names with PROJ friendly names.
2954     if (formatter->databaseContext()) {
2955         auto alternate = substitutePROJAlternativeGridNames(
2956             NN_NO_CHECK(formatter->databaseContext()));
2957         auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>(
2958             shared_from_this().as_nullable()));
2959 
2960         if (alternate != self) {
2961             alternate->_exportToPROJString(formatter);
2962             return;
2963         }
2964     }
2965 
2966     const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF);
2967 
2968     const auto &NTv1Filename = _getNTv1Filename(this, true);
2969     const auto &NTv2Filename = _getNTv2Filename(this, true);
2970     const auto &CTABLE2Filename = _getCTABLE2Filename(this, true);
2971     const auto &HorizontalShiftGTIFFFilename =
2972         _getHorizontalShiftGTIFFFilename(this, true);
2973     const auto &hGridShiftFilename =
2974         !HorizontalShiftGTIFFFilename.empty()
2975             ? HorizontalShiftGTIFFFilename
2976             : !NTv1Filename.empty() ? NTv1Filename : !NTv2Filename.empty()
2977                                                          ? NTv2Filename
2978                                                          : CTABLE2Filename;
2979     if (!hGridShiftFilename.empty()) {
2980         auto sourceCRSGeog =
2981             extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
2982         if (!sourceCRSGeog) {
2983             throw io::FormattingException(
2984                 concat("Can apply ", methodName, " only to GeographicCRS"));
2985         }
2986 
2987         auto targetCRSGeog =
2988             extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
2989         if (!targetCRSGeog) {
2990             throw io::FormattingException(
2991                 concat("Can apply ", methodName, " only to GeographicCRS"));
2992         }
2993 
2994         formatter->startInversion();
2995         sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
2996         formatter->stopInversion();
2997 
2998         if (isMethodInverseOf) {
2999             formatter->startInversion();
3000         }
3001         formatter->addStep("hgridshift");
3002         formatter->addParam("grids", hGridShiftFilename);
3003         if (isMethodInverseOf) {
3004             formatter->stopInversion();
3005         }
3006 
3007         targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3008 
3009         return;
3010     }
3011 
3012     const auto &geocentricTranslationFilename =
3013         _getGeocentricTranslationFilename(this, true);
3014     if (!geocentricTranslationFilename.empty()) {
3015         auto sourceCRSGeog =
3016             dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
3017         if (!sourceCRSGeog) {
3018             throw io::FormattingException(
3019                 concat("Can apply ", methodName, " only to GeographicCRS"));
3020         }
3021 
3022         auto targetCRSGeog =
3023             dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
3024         if (!targetCRSGeog) {
3025             throw io::FormattingException(
3026                 concat("Can apply ", methodName, " only to GeographicCRS"));
3027         }
3028 
3029         const auto &interpCRS = interpolationCRS();
3030         if (!interpCRS) {
3031             throw io::FormattingException(
3032                 "InterpolationCRS required "
3033                 "for"
3034                 " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN);
3035         }
3036         const bool interpIsSrc = interpCRS->_isEquivalentTo(
3037             sourceCRS().get(), util::IComparable::Criterion::EQUIVALENT);
3038         const bool interpIsTarget = interpCRS->_isEquivalentTo(
3039             targetCRS().get(), util::IComparable::Criterion::EQUIVALENT);
3040         if (!interpIsSrc && !interpIsTarget) {
3041             throw io::FormattingException(
3042                 "For"
3043                 " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATION_BY_GRID_INTERPOLATION_IGN
3044                 ", interpolation CRS should be the source or target CRS");
3045         }
3046 
3047         formatter->startInversion();
3048         sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3049         formatter->stopInversion();
3050 
3051         if (isMethodInverseOf) {
3052             formatter->startInversion();
3053         }
3054 
3055         formatter->addStep("push");
3056         formatter->addParam("v_3");
3057 
3058         formatter->addStep("cart");
3059         sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
3060 
3061         formatter->addStep("xyzgridshift");
3062         formatter->addParam("grids", geocentricTranslationFilename);
3063         formatter->addParam("grid_ref",
3064                             interpIsTarget ? "output_crs" : "input_crs");
3065         (interpIsTarget ? targetCRSGeog : sourceCRSGeog)
3066             ->ellipsoid()
3067             ->_exportToPROJString(formatter);
3068 
3069         formatter->startInversion();
3070         formatter->addStep("cart");
3071         targetCRSGeog->ellipsoid()->_exportToPROJString(formatter);
3072         formatter->stopInversion();
3073 
3074         formatter->addStep("pop");
3075         formatter->addParam("v_3");
3076 
3077         if (isMethodInverseOf) {
3078             formatter->stopInversion();
3079         }
3080 
3081         targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3082 
3083         return;
3084     }
3085 
3086     const auto &heightFilename = _getHeightToGeographic3DFilename(this, true);
3087     if (!heightFilename.empty()) {
3088         auto targetCRSGeog =
3089             extractGeographicCRSIfGeographicCRSOrEquivalent(targetCRS());
3090         if (!targetCRSGeog) {
3091             throw io::FormattingException(
3092                 concat("Can apply ", methodName, " only to GeographicCRS"));
3093         }
3094 
3095         if (!formatter->omitHorizontalConversionInVertTransformation()) {
3096             formatter->startInversion();
3097             formatter->pushOmitZUnitConversion();
3098             targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3099             formatter->popOmitZUnitConversion();
3100             formatter->stopInversion();
3101         }
3102 
3103         if (isMethodInverseOf) {
3104             formatter->startInversion();
3105         }
3106         formatter->addStep("vgridshift");
3107         formatter->addParam("grids", heightFilename);
3108         formatter->addParam("multiplier", 1.0);
3109         if (isMethodInverseOf) {
3110             formatter->stopInversion();
3111         }
3112 
3113         if (!formatter->omitHorizontalConversionInVertTransformation()) {
3114             formatter->pushOmitZUnitConversion();
3115             targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3116             formatter->popOmitZUnitConversion();
3117         }
3118 
3119         return;
3120     }
3121 
3122     if (isGeographic3DToGravityRelatedHeight(method(), true)) {
3123         auto fileParameter =
3124             parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME,
3125                            EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME);
3126         if (fileParameter &&
3127             fileParameter->type() == ParameterValue::Type::FILENAME) {
3128             auto filename = fileParameter->valueFile();
3129 
3130             auto sourceCRSGeog =
3131                 extractGeographicCRSIfGeographicCRSOrEquivalent(sourceCRS());
3132             if (!sourceCRSGeog) {
3133                 throw io::FormattingException(
3134                     concat("Can apply ", methodName, " only to GeographicCRS"));
3135             }
3136 
3137             if (!formatter->omitHorizontalConversionInVertTransformation()) {
3138                 formatter->startInversion();
3139                 formatter->pushOmitZUnitConversion();
3140                 sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3141                 formatter->popOmitZUnitConversion();
3142                 formatter->stopInversion();
3143             }
3144 
3145             bool doInversion = isMethodInverseOf;
3146             // The EPSG Geog3DToHeight is the reverse convention of PROJ !
3147             doInversion = !doInversion;
3148             if (doInversion) {
3149                 formatter->startInversion();
3150             }
3151             formatter->addStep("vgridshift");
3152             formatter->addParam("grids", filename);
3153             formatter->addParam("multiplier", 1.0);
3154             if (doInversion) {
3155                 formatter->stopInversion();
3156             }
3157 
3158             if (!formatter->omitHorizontalConversionInVertTransformation()) {
3159                 formatter->pushOmitZUnitConversion();
3160                 sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3161                 formatter->popOmitZUnitConversion();
3162             }
3163 
3164             return;
3165         }
3166     }
3167 
3168     if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) {
3169         auto fileParameter =
3170             parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
3171                            EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
3172         if (fileParameter &&
3173             fileParameter->type() == ParameterValue::Type::FILENAME) {
3174             formatter->addStep("vgridshift");
3175             formatter->addParam("grids", fileParameter->valueFile());
3176             if (fileParameter->valueFile().find(".tif") != std::string::npos) {
3177                 formatter->addParam("multiplier", 1.0);
3178             } else {
3179                 // The vertcon grids go from NGVD 29 to NAVD 88, with units
3180                 // in millimeter (see
3181                 // https://github.com/OSGeo/proj.4/issues/1071), for gtx files
3182                 formatter->addParam("multiplier", 0.001);
3183             }
3184             return;
3185         }
3186     }
3187 
3188     if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD ||
3189         methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT ||
3190         methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX) {
3191         auto fileParameter =
3192             parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE,
3193                            EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE);
3194         if (fileParameter &&
3195             fileParameter->type() == ParameterValue::Type::FILENAME) {
3196             formatter->addStep("vgridshift");
3197             formatter->addParam("grids", fileParameter->valueFile());
3198             formatter->addParam("multiplier", 1.0);
3199             return;
3200         }
3201     }
3202 
3203     if (isLongitudeRotation()) {
3204         double offsetDeg =
3205             parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET,
3206                                   common::UnitOfMeasure::DEGREE);
3207 
3208         auto sourceCRSGeog =
3209             dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get());
3210         if (!sourceCRSGeog) {
3211             throw io::FormattingException(
3212                 concat("Can apply ", methodName, " only to GeographicCRS"));
3213         }
3214 
3215         auto targetCRSGeog =
3216             dynamic_cast<const crs::GeographicCRS *>(targetCRS().get());
3217         if (!targetCRSGeog) {
3218             throw io::FormattingException(
3219                 concat("Can apply ", methodName + " only to GeographicCRS"));
3220         }
3221 
3222         if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo(
3223                 targetCRSGeog->ellipsoid().get(),
3224                 util::IComparable::Criterion::EQUIVALENT)) {
3225             // This is arguable if we should check this...
3226             throw io::FormattingException("Can apply Longitude rotation "
3227                                           "only to SRS with same "
3228                                           "ellipsoid");
3229         }
3230 
3231         formatter->startInversion();
3232         sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3233         formatter->stopInversion();
3234 
3235         bool done = false;
3236         if (offsetDeg != 0.0) {
3237             // Optimization: as we are doing nominally a +step=inv,
3238             // if the negation of the offset value is a well-known name,
3239             // then use forward case with this name.
3240             auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName(
3241                 common::Angle(-offsetDeg));
3242             if (!projPMName.empty()) {
3243                 done = true;
3244                 formatter->addStep("longlat");
3245                 sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
3246                 formatter->addParam("pm", projPMName);
3247             }
3248         }
3249         if (!done) {
3250             // To actually add the offset, we must use the reverse longlat
3251             // operation.
3252             formatter->startInversion();
3253             formatter->addStep("longlat");
3254             sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter);
3255             datum::PrimeMeridian::create(util::PropertyMap(),
3256                                          common::Angle(offsetDeg))
3257                 ->_exportToPROJString(formatter);
3258             formatter->stopInversion();
3259         }
3260 
3261         targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter);
3262 
3263         return;
3264     }
3265 
3266     if (exportToPROJStringGeneric(formatter)) {
3267         return;
3268     }
3269 
3270     throw io::FormattingException("Unimplemented");
3271 }
3272 
3273 } // namespace crs
3274 NS_PROJ_END
3275