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 ¶meter = 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> ¶meters,
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