/src/proj/src/iso19111/operation/singleoperation.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: PROJ |
4 | | * Purpose: ISO19111:2019 implementation |
5 | | * Author: Even Rouault <even dot rouault at spatialys dot com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> |
9 | | * |
10 | | * Permission is hereby granted, free of charge, to any person obtaining a |
11 | | * copy of this software and associated documentation files (the "Software"), |
12 | | * to deal in the Software without restriction, including without limitation |
13 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
14 | | * and/or sell copies of the Software, and to permit persons to whom the |
15 | | * Software is furnished to do so, subject to the following conditions: |
16 | | * |
17 | | * The above copyright notice and this permission notice shall be included |
18 | | * in all copies or substantial portions of the Software. |
19 | | * |
20 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
21 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
22 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
23 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
24 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
25 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
26 | | * DEALINGS IN THE SOFTWARE. |
27 | | ****************************************************************************/ |
28 | | |
29 | | #ifndef FROM_PROJ_CPP |
30 | | #define FROM_PROJ_CPP |
31 | | #endif |
32 | | |
33 | | #include "proj/common.hpp" |
34 | | #include "proj/coordinateoperation.hpp" |
35 | | #include "proj/crs.hpp" |
36 | | #include "proj/io.hpp" |
37 | | #include "proj/metadata.hpp" |
38 | | #include "proj/util.hpp" |
39 | | |
40 | | #include "proj/internal/crs_internal.hpp" |
41 | | #include "proj/internal/internal.hpp" |
42 | | #include "proj/internal/io_internal.hpp" |
43 | | |
44 | | #include "coordinateoperation_internal.hpp" |
45 | | #include "coordinateoperation_private.hpp" |
46 | | #include "operationmethod_private.hpp" |
47 | | #include "oputils.hpp" |
48 | | #include "parammappings.hpp" |
49 | | #include "vectorofvaluesparams.hpp" |
50 | | |
51 | | // PROJ include order is sensitive |
52 | | // clang-format off |
53 | | #include "proj.h" |
54 | | #include "proj_internal.h" // M_PI |
55 | | // clang-format on |
56 | | #include "proj_constants.h" |
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 | | |
79 | | InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( |
80 | | const std::string &message) |
81 | 0 | : InvalidOperation(message) {} |
82 | | |
83 | | InvalidOperationEmptyIntersection::InvalidOperationEmptyIntersection( |
84 | 0 | const InvalidOperationEmptyIntersection &) = default; |
85 | | |
86 | 0 | InvalidOperationEmptyIntersection::~InvalidOperationEmptyIntersection() = |
87 | | default; |
88 | | |
89 | | //! @endcond |
90 | | |
91 | | // --------------------------------------------------------------------------- |
92 | | |
93 | | //! @cond Doxygen_Suppress |
94 | | |
95 | | // --------------------------------------------------------------------------- |
96 | | |
97 | | GridDescription::GridDescription() |
98 | 0 | : shortName{}, fullName{}, packageName{}, url{}, directDownload(false), |
99 | 0 | openLicense(false), available(false) {} |
100 | | |
101 | 0 | GridDescription::~GridDescription() = default; |
102 | | |
103 | 0 | GridDescription::GridDescription(const GridDescription &) = default; |
104 | | |
105 | | GridDescription::GridDescription(GridDescription &&other) noexcept |
106 | 0 | : shortName(std::move(other.shortName)), |
107 | 0 | fullName(std::move(other.fullName)), |
108 | 0 | packageName(std::move(other.packageName)), url(std::move(other.url)), |
109 | 0 | directDownload(other.directDownload), openLicense(other.openLicense), |
110 | 0 | available(other.available) {} |
111 | | |
112 | | //! @endcond |
113 | | |
114 | | // --------------------------------------------------------------------------- |
115 | | |
116 | 0 | CoordinateOperation::CoordinateOperation() : d(std::make_unique<Private>()) {} |
117 | | |
118 | | // --------------------------------------------------------------------------- |
119 | | |
120 | | CoordinateOperation::CoordinateOperation(const CoordinateOperation &other) |
121 | 0 | : ObjectUsage(other), d(std::make_unique<Private>(*other.d)) {} |
122 | | |
123 | | // --------------------------------------------------------------------------- |
124 | | |
125 | | //! @cond Doxygen_Suppress |
126 | 0 | CoordinateOperation::~CoordinateOperation() = default; |
127 | | //! @endcond |
128 | | |
129 | | // --------------------------------------------------------------------------- |
130 | | |
131 | | /** \brief Return the version of the coordinate transformation (i.e. |
132 | | * instantiation |
133 | | * due to the stochastic nature of the parameters). |
134 | | * |
135 | | * Mandatory when describing a coordinate transformation or point motion |
136 | | * operation, and should not be supplied for a coordinate conversion. |
137 | | * |
138 | | * @return version or empty. |
139 | | */ |
140 | | const util::optional<std::string> & |
141 | 0 | CoordinateOperation::operationVersion() const { |
142 | 0 | return d->operationVersion_; |
143 | 0 | } |
144 | | |
145 | | // --------------------------------------------------------------------------- |
146 | | |
147 | | /** \brief Return estimate(s) of the impact of this coordinate operation on |
148 | | * point accuracy. |
149 | | * |
150 | | * Gives position error estimates for target coordinates of this coordinate |
151 | | * operation, assuming no errors in source coordinates. |
152 | | * |
153 | | * @return estimate(s) or empty vector. |
154 | | */ |
155 | | const std::vector<metadata::PositionalAccuracyNNPtr> & |
156 | 0 | CoordinateOperation::coordinateOperationAccuracies() const { |
157 | 0 | return d->coordinateOperationAccuracies_; |
158 | 0 | } |
159 | | |
160 | | // --------------------------------------------------------------------------- |
161 | | |
162 | | /** \brief Return the source CRS of this coordinate operation. |
163 | | * |
164 | | * This should not be null, expect for of a derivingConversion of a DerivedCRS |
165 | | * when the owning DerivedCRS has been destroyed. |
166 | | * |
167 | | * @return source CRS, or null. |
168 | | */ |
169 | 0 | const crs::CRSPtr CoordinateOperation::sourceCRS() const { |
170 | 0 | return d->sourceCRSWeak_.lock(); |
171 | 0 | } |
172 | | |
173 | | // --------------------------------------------------------------------------- |
174 | | |
175 | | /** \brief Return the target CRS of this coordinate operation. |
176 | | * |
177 | | * This should not be null, expect for of a derivingConversion of a DerivedCRS |
178 | | * when the owning DerivedCRS has been destroyed. |
179 | | * |
180 | | * @return target CRS, or null. |
181 | | */ |
182 | 0 | const crs::CRSPtr CoordinateOperation::targetCRS() const { |
183 | 0 | return d->targetCRSWeak_.lock(); |
184 | 0 | } |
185 | | |
186 | | // --------------------------------------------------------------------------- |
187 | | |
188 | | /** \brief Return the interpolation CRS of this coordinate operation. |
189 | | * |
190 | | * @return interpolation CRS, or null. |
191 | | */ |
192 | 0 | const crs::CRSPtr &CoordinateOperation::interpolationCRS() const { |
193 | 0 | return d->interpolationCRS_; |
194 | 0 | } |
195 | | |
196 | | // --------------------------------------------------------------------------- |
197 | | |
198 | | /** \brief Return the source epoch of coordinates. |
199 | | * |
200 | | * @return source epoch of coordinates, or empty. |
201 | | */ |
202 | | const util::optional<common::DataEpoch> & |
203 | 0 | CoordinateOperation::sourceCoordinateEpoch() const { |
204 | 0 | return *(d->sourceCoordinateEpoch_); |
205 | 0 | } |
206 | | |
207 | | // --------------------------------------------------------------------------- |
208 | | |
209 | | /** \brief Return the target epoch of coordinates. |
210 | | * |
211 | | * @return target epoch of coordinates, or empty. |
212 | | */ |
213 | | const util::optional<common::DataEpoch> & |
214 | 0 | CoordinateOperation::targetCoordinateEpoch() const { |
215 | 0 | return *(d->targetCoordinateEpoch_); |
216 | 0 | } |
217 | | |
218 | | // --------------------------------------------------------------------------- |
219 | | |
220 | | void CoordinateOperation::setWeakSourceTargetCRS( |
221 | 0 | std::weak_ptr<crs::CRS> sourceCRSIn, std::weak_ptr<crs::CRS> targetCRSIn) { |
222 | 0 | d->sourceCRSWeak_ = std::move(sourceCRSIn); |
223 | 0 | d->targetCRSWeak_ = std::move(targetCRSIn); |
224 | 0 | } |
225 | | |
226 | | // --------------------------------------------------------------------------- |
227 | | |
228 | | void CoordinateOperation::setCRSs(const crs::CRSNNPtr &sourceCRSIn, |
229 | | const crs::CRSNNPtr &targetCRSIn, |
230 | 0 | const crs::CRSPtr &interpolationCRSIn) { |
231 | 0 | d->strongRef_ = |
232 | 0 | std::make_unique<Private::CRSStrongRef>(sourceCRSIn, targetCRSIn); |
233 | 0 | d->sourceCRSWeak_ = sourceCRSIn.as_nullable(); |
234 | 0 | d->targetCRSWeak_ = targetCRSIn.as_nullable(); |
235 | 0 | d->interpolationCRS_ = interpolationCRSIn; |
236 | 0 | } |
237 | | |
238 | | // --------------------------------------------------------------------------- |
239 | | |
240 | | void CoordinateOperation::setCRSsUpdateInverse( |
241 | | const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, |
242 | 0 | const crs::CRSPtr &interpolationCRSIn) { |
243 | 0 | setCRSs(sourceCRSIn, targetCRSIn, interpolationCRSIn); |
244 | |
|
245 | 0 | auto invCO = dynamic_cast<InverseCoordinateOperation *>(this); |
246 | 0 | if (invCO) { |
247 | 0 | invCO->forwardOperation()->setCRSs(targetCRSIn, sourceCRSIn, |
248 | 0 | interpolationCRSIn); |
249 | 0 | } |
250 | |
|
251 | 0 | auto transf = dynamic_cast<Transformation *>(this); |
252 | 0 | if (transf) { |
253 | 0 | transf->inverseAsTransformation()->setCRSs(targetCRSIn, sourceCRSIn, |
254 | 0 | interpolationCRSIn); |
255 | 0 | } |
256 | |
|
257 | 0 | auto concat = dynamic_cast<ConcatenatedOperation *>(this); |
258 | 0 | if (concat) { |
259 | 0 | auto first = concat->operations().front().get(); |
260 | 0 | auto &firstTarget(first->targetCRS()); |
261 | 0 | if (firstTarget) { |
262 | 0 | first->setCRSsUpdateInverse(sourceCRSIn, NN_NO_CHECK(firstTarget), |
263 | 0 | first->interpolationCRS()); |
264 | 0 | } |
265 | 0 | auto last = concat->operations().back().get(); |
266 | 0 | auto &lastSource(last->sourceCRS()); |
267 | 0 | if (lastSource) { |
268 | 0 | last->setCRSsUpdateInverse(NN_NO_CHECK(lastSource), targetCRSIn, |
269 | 0 | last->interpolationCRS()); |
270 | 0 | } |
271 | 0 | } |
272 | 0 | } |
273 | | |
274 | | // --------------------------------------------------------------------------- |
275 | | |
276 | | void CoordinateOperation::setInterpolationCRS( |
277 | 0 | const crs::CRSPtr &interpolationCRSIn) { |
278 | 0 | d->interpolationCRS_ = interpolationCRSIn; |
279 | 0 | } |
280 | | |
281 | | // --------------------------------------------------------------------------- |
282 | | |
283 | | void CoordinateOperation::setCRSs(const CoordinateOperation *in, |
284 | 0 | bool inverseSourceTarget) { |
285 | 0 | auto l_sourceCRS = in->sourceCRS(); |
286 | 0 | auto l_targetCRS = in->targetCRS(); |
287 | 0 | if (l_sourceCRS && l_targetCRS) { |
288 | 0 | auto nn_sourceCRS = NN_NO_CHECK(l_sourceCRS); |
289 | 0 | auto nn_targetCRS = NN_NO_CHECK(l_targetCRS); |
290 | 0 | if (inverseSourceTarget) { |
291 | 0 | setCRSs(nn_targetCRS, nn_sourceCRS, in->interpolationCRS()); |
292 | 0 | } else { |
293 | 0 | setCRSs(nn_sourceCRS, nn_targetCRS, in->interpolationCRS()); |
294 | 0 | } |
295 | 0 | } |
296 | 0 | } |
297 | | |
298 | | // --------------------------------------------------------------------------- |
299 | | |
300 | | void CoordinateOperation::setSourceCoordinateEpoch( |
301 | 0 | const util::optional<common::DataEpoch> &epoch) { |
302 | 0 | d->sourceCoordinateEpoch_ = |
303 | 0 | std::make_shared<util::optional<common::DataEpoch>>(epoch); |
304 | |
|
305 | 0 | if (epoch) { |
306 | 0 | auto invOp = dynamic_cast<InverseCoordinateOperation *>(this); |
307 | 0 | if (invOp) |
308 | 0 | invOp->forwardOperation()->setTargetCoordinateEpoch(epoch); |
309 | 0 | } |
310 | 0 | } |
311 | | |
312 | | // --------------------------------------------------------------------------- |
313 | | |
314 | | void CoordinateOperation::setTargetCoordinateEpoch( |
315 | 0 | const util::optional<common::DataEpoch> &epoch) { |
316 | 0 | d->targetCoordinateEpoch_ = |
317 | 0 | std::make_shared<util::optional<common::DataEpoch>>(epoch); |
318 | |
|
319 | 0 | if (epoch) { |
320 | 0 | auto invOp = dynamic_cast<InverseCoordinateOperation *>(this); |
321 | 0 | if (invOp) |
322 | 0 | invOp->forwardOperation()->setSourceCoordinateEpoch(epoch); |
323 | 0 | } |
324 | 0 | } |
325 | | |
326 | | // --------------------------------------------------------------------------- |
327 | | |
328 | | void CoordinateOperation::setAccuracies( |
329 | 0 | const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { |
330 | 0 | d->coordinateOperationAccuracies_ = accuracies; |
331 | 0 | } |
332 | | |
333 | | // --------------------------------------------------------------------------- |
334 | | |
335 | | /** \brief Return whether a coordinate operation can be instantiated as |
336 | | * a PROJ pipeline, checking in particular that referenced grids are |
337 | | * available. |
338 | | */ |
339 | | bool CoordinateOperation::isPROJInstantiable( |
340 | | const io::DatabaseContextPtr &databaseContext, |
341 | 0 | bool considerKnownGridsAsAvailable) const { |
342 | 0 | try { |
343 | 0 | exportToPROJString(io::PROJStringFormatter::create().get()); |
344 | 0 | } catch (const std::exception &) { |
345 | 0 | return false; |
346 | 0 | } |
347 | 0 | for (const auto &gridDesc : |
348 | 0 | gridsNeeded(databaseContext, considerKnownGridsAsAvailable)) { |
349 | | // Grid name starting with @ are considered as optional. |
350 | 0 | if (!gridDesc.available && |
351 | 0 | (gridDesc.shortName.empty() || gridDesc.shortName[0] != '@')) { |
352 | 0 | return false; |
353 | 0 | } |
354 | 0 | } |
355 | 0 | return true; |
356 | 0 | } |
357 | | |
358 | | // --------------------------------------------------------------------------- |
359 | | |
360 | | /** \brief Return whether a coordinate operation has a "ballpark" |
361 | | * transformation, |
362 | | * that is a very approximate one, due to lack of more accurate transformations. |
363 | | * |
364 | | * Typically a null geographic offset between two horizontal datum, or a |
365 | | * null vertical offset (or limited to unit changes) between two vertical |
366 | | * datum. Errors of several tens to one hundred meters might be expected, |
367 | | * compared to more accurate transformations. |
368 | | */ |
369 | 0 | bool CoordinateOperation::hasBallparkTransformation() const { |
370 | 0 | return d->hasBallparkTransformation_; |
371 | 0 | } |
372 | | |
373 | | // --------------------------------------------------------------------------- |
374 | | |
375 | 0 | void CoordinateOperation::setHasBallparkTransformation(bool b) { |
376 | 0 | d->hasBallparkTransformation_ = b; |
377 | 0 | } |
378 | | |
379 | | // --------------------------------------------------------------------------- |
380 | | |
381 | | /** \brief Return whether a coordinate operation requires coordinate tuples |
382 | | * to have a valid input time for the coordinate transformation to succeed. |
383 | | * (this applies for the forward direction) |
384 | | * |
385 | | * Note: in the case of a time-dependent Helmert transformation, this function |
386 | | * will return true, but when executing proj_trans(), execution will still |
387 | | * succeed if the time information is missing, due to the transformation central |
388 | | * epoch being used as a fallback. |
389 | | * |
390 | | * @since 9.5 |
391 | | */ |
392 | 0 | bool CoordinateOperation::requiresPerCoordinateInputTime() const { |
393 | 0 | return d->requiresPerCoordinateInputTime_ && |
394 | 0 | !d->sourceCoordinateEpoch_->has_value(); |
395 | 0 | } |
396 | | |
397 | | // --------------------------------------------------------------------------- |
398 | | |
399 | 0 | void CoordinateOperation::setRequiresPerCoordinateInputTime(bool b) { |
400 | 0 | d->requiresPerCoordinateInputTime_ = b; |
401 | 0 | } |
402 | | |
403 | | // --------------------------------------------------------------------------- |
404 | | |
405 | | void CoordinateOperation::setProperties( |
406 | | const util::PropertyMap &properties) // throw(InvalidValueTypeException) |
407 | 0 | { |
408 | 0 | ObjectUsage::setProperties(properties); |
409 | 0 | properties.getStringValue(OPERATION_VERSION_KEY, d->operationVersion_); |
410 | 0 | } |
411 | | |
412 | | // --------------------------------------------------------------------------- |
413 | | |
414 | | /** \brief Return a variation of the current coordinate operation whose axis |
415 | | * order is the one expected for visualization purposes. |
416 | | */ |
417 | | CoordinateOperationNNPtr |
418 | 0 | CoordinateOperation::normalizeForVisualization() const { |
419 | 0 | auto l_sourceCRS = sourceCRS(); |
420 | 0 | auto l_targetCRS = targetCRS(); |
421 | 0 | if (!l_sourceCRS || !l_targetCRS) { |
422 | 0 | throw util::UnsupportedOperationException( |
423 | 0 | "Cannot retrieve source or target CRS"); |
424 | 0 | } |
425 | 0 | const bool swapSource = |
426 | 0 | l_sourceCRS->mustAxisOrderBeSwitchedForVisualization(); |
427 | 0 | const bool swapTarget = |
428 | 0 | l_targetCRS->mustAxisOrderBeSwitchedForVisualization(); |
429 | 0 | auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>( |
430 | 0 | shared_from_this().as_nullable())); |
431 | 0 | if (!swapSource && !swapTarget) { |
432 | 0 | return l_this; |
433 | 0 | } |
434 | 0 | std::vector<CoordinateOperationNNPtr> subOps; |
435 | 0 | if (swapSource) { |
436 | 0 | auto op = Conversion::createAxisOrderReversal(false); |
437 | 0 | op->setCRSs(l_sourceCRS->normalizeForVisualization(), |
438 | 0 | NN_NO_CHECK(l_sourceCRS), nullptr); |
439 | 0 | subOps.emplace_back(op); |
440 | 0 | } |
441 | 0 | subOps.emplace_back(l_this); |
442 | 0 | if (swapTarget) { |
443 | 0 | auto op = Conversion::createAxisOrderReversal(false); |
444 | 0 | op->setCRSs(NN_NO_CHECK(l_targetCRS), |
445 | 0 | l_targetCRS->normalizeForVisualization(), nullptr); |
446 | 0 | subOps.emplace_back(op); |
447 | 0 | } |
448 | |
|
449 | 0 | util::PropertyMap properties; |
450 | | // The domain(s) are unchanged |
451 | 0 | addDomains(properties, this); |
452 | 0 | addModifiedIdentifier(properties, this, /* inverse = */ false, |
453 | 0 | /* derivedFrom = */ true); |
454 | |
|
455 | 0 | std::string name = nameStr(); |
456 | 0 | if (!name.empty()) { |
457 | 0 | name += NORMALIZED_AXIS_ORDER_SUFFIX_STR; |
458 | 0 | properties.set(common::IdentifiedObject::NAME_KEY, name); |
459 | 0 | } |
460 | |
|
461 | 0 | const std::string &l_remarks = remarks(); |
462 | 0 | if (!l_remarks.empty()) { |
463 | 0 | properties.set(common::IdentifiedObject::REMARKS_KEY, l_remarks); |
464 | 0 | } |
465 | |
|
466 | 0 | std::vector<CoordinateOperationNNPtr> flattenOps; |
467 | 0 | for (const auto &subOp : subOps) { |
468 | 0 | auto subOpConcat = |
469 | 0 | dynamic_cast<const ConcatenatedOperation *>(subOp.get()); |
470 | 0 | if (subOpConcat) { |
471 | 0 | auto subSubOps = subOpConcat->operations(); |
472 | 0 | for (const auto &subSubOp : subSubOps) { |
473 | 0 | flattenOps.emplace_back(subSubOp); |
474 | 0 | } |
475 | 0 | } else { |
476 | 0 | flattenOps.emplace_back(subOp); |
477 | 0 | } |
478 | 0 | } |
479 | |
|
480 | 0 | return ConcatenatedOperation::create(properties, flattenOps, |
481 | 0 | coordinateOperationAccuracies()); |
482 | 0 | } |
483 | | |
484 | | // --------------------------------------------------------------------------- |
485 | | |
486 | | /** \brief Return a coordinate transformer for this operation. |
487 | | * |
488 | | * The returned coordinate transformer is tied to the provided context, |
489 | | * and should only be called by the thread "owning" the passed context. |
490 | | * It should not be used after the context has been destroyed. |
491 | | * |
492 | | * @param ctx Execution context to which the transformer will be tied to. |
493 | | * If null, the default context will be used (only safe for |
494 | | * single-threaded applications). |
495 | | * @return a new CoordinateTransformer instance. |
496 | | * @since 9.3 |
497 | | * @throw UnsupportedOperationException if the transformer cannot be |
498 | | * instantiated. |
499 | | */ |
500 | | CoordinateTransformerNNPtr |
501 | 0 | CoordinateOperation::coordinateTransformer(PJ_CONTEXT *ctx) const { |
502 | 0 | auto l_this = NN_NO_CHECK(std::dynamic_pointer_cast<CoordinateOperation>( |
503 | 0 | shared_from_this().as_nullable())); |
504 | 0 | return CoordinateTransformer::create(l_this, ctx); |
505 | 0 | } |
506 | | |
507 | | // --------------------------------------------------------------------------- |
508 | | |
509 | | //! @cond Doxygen_Suppress |
510 | 0 | CoordinateOperationNNPtr CoordinateOperation::shallowClone() const { |
511 | 0 | return _shallowClone(); |
512 | 0 | } |
513 | | //! @endcond |
514 | | |
515 | | // --------------------------------------------------------------------------- |
516 | | |
517 | | //! @cond Doxygen_Suppress |
518 | | struct CoordinateTransformer::Private { |
519 | | PJ *pj_; |
520 | | }; |
521 | | //! @endcond |
522 | | |
523 | | // --------------------------------------------------------------------------- |
524 | | |
525 | | CoordinateTransformer::CoordinateTransformer() |
526 | 0 | : d(std::make_unique<Private>()) {} |
527 | | |
528 | | // --------------------------------------------------------------------------- |
529 | | |
530 | | //! @cond Doxygen_Suppress |
531 | 0 | CoordinateTransformer::~CoordinateTransformer() { |
532 | 0 | if (d->pj_) { |
533 | 0 | proj_assign_context(d->pj_, pj_get_default_ctx()); |
534 | 0 | proj_destroy(d->pj_); |
535 | 0 | } |
536 | 0 | } |
537 | | //! @endcond |
538 | | |
539 | | // --------------------------------------------------------------------------- |
540 | | |
541 | | CoordinateTransformerNNPtr |
542 | | CoordinateTransformer::create(const CoordinateOperationNNPtr &op, |
543 | 0 | PJ_CONTEXT *ctx) { |
544 | 0 | auto transformer = NN_NO_CHECK( |
545 | 0 | CoordinateTransformer::make_unique<CoordinateTransformer>()); |
546 | | // pj_obj_create does not sanitize the context |
547 | 0 | if (ctx == nullptr) |
548 | 0 | ctx = pj_get_default_ctx(); |
549 | 0 | transformer->d->pj_ = pj_obj_create(ctx, op); |
550 | 0 | if (transformer->d->pj_ == nullptr) |
551 | 0 | throw util::UnsupportedOperationException( |
552 | 0 | "Cannot instantiate transformer"); |
553 | 0 | return transformer; |
554 | 0 | } |
555 | | |
556 | | // --------------------------------------------------------------------------- |
557 | | |
558 | | /** Transforms a coordinate tuple. |
559 | | * |
560 | | * PJ_COORD is a union of many structures. In the context of this method, |
561 | | * it is prudent to only use the v[] array, with the understanding that |
562 | | * the expected input values should be passed in the order and the unit of |
563 | | * the successive axis of the input CRS. Similarly the values returned in the |
564 | | * v[] array of the output PJ_COORD are in the order and the unit of the |
565 | | * successive axis of the output CRS. |
566 | | * For coordinate operations involving a time-dependent operation, |
567 | | * coord.v[3] is the decimal year of the coordinate epoch of the input (or |
568 | | * HUGE_VAL to indicate none) |
569 | | * |
570 | | * If an error occurs, HUGE_VAL is returned in the .v[0] member of the output |
571 | | * coordinate tuple. |
572 | | * |
573 | | * Example how to transform coordinates from EPSG:4326 (WGS 84 |
574 | | * latitude/longitude) to EPSG:32631 (WGS 84 / UTM zone 31N). |
575 | | \code{.cpp} |
576 | | auto authFactory = |
577 | | AuthorityFactory::create(DatabaseContext::create(), std::string()); |
578 | | auto coord_op_ctxt = CoordinateOperationContext::create( |
579 | | authFactory, nullptr, 0.0); |
580 | | auto authFactoryEPSG = |
581 | | AuthorityFactory::create(DatabaseContext::create(), "EPSG"); |
582 | | auto list = CoordinateOperationFactory::create()->createOperations( |
583 | | authFactoryEPSG->createCoordinateReferenceSystem("4326"), |
584 | | authFactoryEPSG->createCoordinateReferenceSystem("32631"), |
585 | | coord_op_ctxt); |
586 | | ASSERT_TRUE(!list.empty()); |
587 | | PJ_CONTEXT* ctx = proj_context_create(); |
588 | | auto transformer = list[0]->coordinateTransformer(ctx); |
589 | | PJ_COORD c; |
590 | | c.v[0] = 49; // latitude in degree |
591 | | c.v[1] = 2; // longitude in degree |
592 | | c.v[2] = 0; |
593 | | c.v[3] = HUGE_VAL; |
594 | | c = transformer->transform(c); |
595 | | EXPECT_NEAR(c.v[0], 426857.98771728, 1e-8); // easting in metre |
596 | | EXPECT_NEAR(c.v[1], 5427937.52346492, 1e-8); // northing in metre |
597 | | proj_context_destroy(ctx); |
598 | | \endcode |
599 | | */ |
600 | 0 | PJ_COORD CoordinateTransformer::transform(PJ_COORD coord) { |
601 | 0 | return proj_trans(d->pj_, PJ_FWD, coord); |
602 | 0 | } |
603 | | |
604 | | // --------------------------------------------------------------------------- |
605 | | |
606 | 0 | OperationMethod::OperationMethod() : d(std::make_unique<Private>()) {} |
607 | | |
608 | | // --------------------------------------------------------------------------- |
609 | | |
610 | | OperationMethod::OperationMethod(const OperationMethod &other) |
611 | 0 | : IdentifiedObject(other), d(std::make_unique<Private>(*other.d)) {} |
612 | | |
613 | | // --------------------------------------------------------------------------- |
614 | | |
615 | | //! @cond Doxygen_Suppress |
616 | 0 | OperationMethod::~OperationMethod() = default; |
617 | | //! @endcond |
618 | | |
619 | | // --------------------------------------------------------------------------- |
620 | | |
621 | | /** \brief Return the formula(s) or procedure used by this coordinate operation |
622 | | * method. |
623 | | * |
624 | | * This may be a reference to a publication (in which case use |
625 | | * formulaCitation()). |
626 | | * |
627 | | * Note that the operation method may not be analytic, in which case this |
628 | | * attribute references or contains the procedure, not an analytic formula. |
629 | | * |
630 | | * @return the formula, or empty. |
631 | | */ |
632 | 0 | const util::optional<std::string> &OperationMethod::formula() PROJ_PURE_DEFN { |
633 | 0 | return d->formula_; |
634 | 0 | } |
635 | | |
636 | | // --------------------------------------------------------------------------- |
637 | | |
638 | | /** \brief Return a reference to a publication giving the formula(s) or |
639 | | * procedure |
640 | | * used by the coordinate operation method. |
641 | | * |
642 | | * @return the formula citation, or empty. |
643 | | */ |
644 | | const util::optional<metadata::Citation> & |
645 | 0 | OperationMethod::formulaCitation() PROJ_PURE_DEFN { |
646 | 0 | return d->formulaCitation_; |
647 | 0 | } |
648 | | |
649 | | // --------------------------------------------------------------------------- |
650 | | |
651 | | /** \brief Return the parameters of this operation method. |
652 | | * |
653 | | * @return the parameters. |
654 | | */ |
655 | | const std::vector<GeneralOperationParameterNNPtr> & |
656 | 0 | OperationMethod::parameters() PROJ_PURE_DEFN { |
657 | 0 | return d->parameters_; |
658 | 0 | } |
659 | | |
660 | | // --------------------------------------------------------------------------- |
661 | | |
662 | | /** \brief Instantiate a operation method from a vector of |
663 | | * GeneralOperationParameter. |
664 | | * |
665 | | * @param properties See \ref general_properties. At minimum the name should be |
666 | | * defined. |
667 | | * @param parameters Vector of GeneralOperationParameterNNPtr. |
668 | | * @return a new OperationMethod. |
669 | | */ |
670 | | OperationMethodNNPtr OperationMethod::create( |
671 | | const util::PropertyMap &properties, |
672 | 0 | const std::vector<GeneralOperationParameterNNPtr> ¶meters) { |
673 | 0 | OperationMethodNNPtr method( |
674 | 0 | OperationMethod::nn_make_shared<OperationMethod>()); |
675 | 0 | method->assignSelf(method); |
676 | 0 | method->setProperties(properties); |
677 | 0 | method->d->parameters_ = parameters; |
678 | 0 | properties.getStringValue("proj_method", method->d->projMethodOverride_); |
679 | 0 | return method; |
680 | 0 | } |
681 | | |
682 | | // --------------------------------------------------------------------------- |
683 | | |
684 | | /** \brief Instantiate a operation method from a vector of OperationParameter. |
685 | | * |
686 | | * @param properties See \ref general_properties. At minimum the name should be |
687 | | * defined. |
688 | | * @param parameters Vector of OperationParameterNNPtr. |
689 | | * @return a new OperationMethod. |
690 | | */ |
691 | | OperationMethodNNPtr OperationMethod::create( |
692 | | const util::PropertyMap &properties, |
693 | 0 | const std::vector<OperationParameterNNPtr> ¶meters) { |
694 | 0 | std::vector<GeneralOperationParameterNNPtr> parametersGeneral; |
695 | 0 | parametersGeneral.reserve(parameters.size()); |
696 | 0 | for (const auto &p : parameters) { |
697 | 0 | parametersGeneral.push_back(p); |
698 | 0 | } |
699 | 0 | return create(properties, parametersGeneral); |
700 | 0 | } |
701 | | |
702 | | // --------------------------------------------------------------------------- |
703 | | |
704 | | /** \brief Return the EPSG code, either directly, or through the name |
705 | | * @return code, or 0 if not found |
706 | | */ |
707 | 0 | int OperationMethod::getEPSGCode() PROJ_PURE_DEFN { |
708 | 0 | int epsg_code = IdentifiedObject::getEPSGCode(); |
709 | 0 | if (epsg_code == 0) { |
710 | 0 | auto l_name = nameStr(); |
711 | 0 | if (ends_with(l_name, " (3D)")) { |
712 | 0 | l_name.resize(l_name.size() - strlen(" (3D)")); |
713 | 0 | } |
714 | 0 | size_t nMethodNameCodes = 0; |
715 | 0 | const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes); |
716 | 0 | for (size_t i = 0; i < nMethodNameCodes; ++i) { |
717 | 0 | const auto &tuple = methodNameCodes[i]; |
718 | 0 | if (metadata::Identifier::isEquivalentName(l_name.c_str(), |
719 | 0 | tuple.name)) { |
720 | 0 | return tuple.epsg_code; |
721 | 0 | } |
722 | 0 | } |
723 | 0 | } |
724 | 0 | return epsg_code; |
725 | 0 | } |
726 | | |
727 | | // --------------------------------------------------------------------------- |
728 | | |
729 | | //! @cond Doxygen_Suppress |
730 | 0 | void OperationMethod::_exportToWKT(io::WKTFormatter *formatter) const { |
731 | 0 | const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; |
732 | 0 | formatter->startNode(isWKT2 ? io::WKTConstants::METHOD |
733 | 0 | : io::WKTConstants::PROJECTION, |
734 | 0 | !identifiers().empty()); |
735 | 0 | std::string l_name(nameStr()); |
736 | 0 | if (!isWKT2) { |
737 | 0 | const MethodMapping *mapping = getMapping(this); |
738 | 0 | if (mapping == nullptr) { |
739 | 0 | l_name = replaceAll(l_name, " ", "_"); |
740 | 0 | } else { |
741 | 0 | if (l_name == |
742 | 0 | PROJ_WKT2_NAME_METHOD_GEOSTATIONARY_SATELLITE_SWEEP_X) { |
743 | 0 | l_name = "Geostationary_Satellite"; |
744 | 0 | } else { |
745 | 0 | if (mapping->wkt1_name == nullptr) { |
746 | 0 | throw io::FormattingException( |
747 | 0 | std::string("Unsupported conversion method: ") + |
748 | 0 | mapping->wkt2_name); |
749 | 0 | } |
750 | 0 | l_name = mapping->wkt1_name; |
751 | 0 | } |
752 | 0 | } |
753 | 0 | } |
754 | 0 | formatter->addQuotedString(l_name); |
755 | 0 | if (formatter->outputId()) { |
756 | 0 | formatID(formatter); |
757 | 0 | } |
758 | 0 | formatter->endNode(); |
759 | 0 | } |
760 | | //! @endcond |
761 | | |
762 | | // --------------------------------------------------------------------------- |
763 | | |
764 | | //! @cond Doxygen_Suppress |
765 | | void OperationMethod::_exportToJSON( |
766 | | io::JSONFormatter *formatter) const // throw(FormattingException) |
767 | 0 | { |
768 | 0 | auto writer = formatter->writer(); |
769 | 0 | auto objectContext(formatter->MakeObjectContext("OperationMethod", |
770 | 0 | !identifiers().empty())); |
771 | |
|
772 | 0 | writer->AddObjKey("name"); |
773 | 0 | writer->Add(nameStr()); |
774 | |
|
775 | 0 | if (formatter->outputId()) { |
776 | 0 | formatID(formatter); |
777 | 0 | } |
778 | 0 | } |
779 | | //! @endcond |
780 | | |
781 | | // --------------------------------------------------------------------------- |
782 | | |
783 | | //! @cond Doxygen_Suppress |
784 | | bool OperationMethod::_isEquivalentTo( |
785 | | const util::IComparable *other, util::IComparable::Criterion criterion, |
786 | 0 | const io::DatabaseContextPtr &dbContext) const { |
787 | 0 | auto otherOM = dynamic_cast<const OperationMethod *>(other); |
788 | 0 | if (otherOM == nullptr || |
789 | 0 | !IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { |
790 | 0 | return false; |
791 | 0 | } |
792 | | // TODO test formula and formulaCitation |
793 | 0 | const auto ¶ms = parameters(); |
794 | 0 | const auto &otherParams = otherOM->parameters(); |
795 | 0 | const auto paramsSize = params.size(); |
796 | 0 | if (paramsSize != otherParams.size()) { |
797 | 0 | return false; |
798 | 0 | } |
799 | 0 | if (criterion == util::IComparable::Criterion::STRICT) { |
800 | 0 | for (size_t i = 0; i < paramsSize; i++) { |
801 | 0 | if (!params[i]->_isEquivalentTo(otherParams[i].get(), criterion, |
802 | 0 | dbContext)) { |
803 | 0 | return false; |
804 | 0 | } |
805 | 0 | } |
806 | 0 | } else { |
807 | 0 | std::vector<bool> candidateIndices(paramsSize, true); |
808 | 0 | for (size_t i = 0; i < paramsSize; i++) { |
809 | 0 | bool found = false; |
810 | 0 | for (size_t j = 0; j < paramsSize; j++) { |
811 | 0 | if (candidateIndices[j] && |
812 | 0 | params[i]->_isEquivalentTo(otherParams[j].get(), criterion, |
813 | 0 | dbContext)) { |
814 | 0 | candidateIndices[j] = false; |
815 | 0 | found = true; |
816 | 0 | break; |
817 | 0 | } |
818 | 0 | } |
819 | 0 | if (!found) { |
820 | 0 | return false; |
821 | 0 | } |
822 | 0 | } |
823 | 0 | } |
824 | 0 | return true; |
825 | 0 | } |
826 | | //! @endcond |
827 | | |
828 | | // --------------------------------------------------------------------------- |
829 | | |
830 | | //! @cond Doxygen_Suppress |
831 | | struct GeneralParameterValue::Private {}; |
832 | | //! @endcond |
833 | | |
834 | | // --------------------------------------------------------------------------- |
835 | | |
836 | | //! @cond Doxygen_Suppress |
837 | 0 | GeneralParameterValue::GeneralParameterValue() : d(nullptr) {} |
838 | | |
839 | | // --------------------------------------------------------------------------- |
840 | | |
841 | | GeneralParameterValue::GeneralParameterValue(const GeneralParameterValue &) |
842 | 0 | : d(nullptr) {} |
843 | | //! @endcond |
844 | | |
845 | | // --------------------------------------------------------------------------- |
846 | | |
847 | | //! @cond Doxygen_Suppress |
848 | 0 | GeneralParameterValue::~GeneralParameterValue() = default; |
849 | | //! @endcond |
850 | | |
851 | | // --------------------------------------------------------------------------- |
852 | | |
853 | | //! @cond Doxygen_Suppress |
854 | | struct OperationParameterValue::Private { |
855 | | OperationParameterNNPtr parameter; |
856 | | ParameterValueNNPtr parameterValue; |
857 | | |
858 | | Private(const OperationParameterNNPtr ¶meterIn, |
859 | | const ParameterValueNNPtr &valueIn) |
860 | 0 | : parameter(parameterIn), parameterValue(valueIn) {} |
861 | | }; |
862 | | //! @endcond |
863 | | |
864 | | // --------------------------------------------------------------------------- |
865 | | |
866 | | OperationParameterValue::OperationParameterValue( |
867 | | const OperationParameterValue &other) |
868 | 0 | : GeneralParameterValue(other), d(std::make_unique<Private>(*other.d)) {} |
869 | | |
870 | | // --------------------------------------------------------------------------- |
871 | | |
872 | | OperationParameterValue::OperationParameterValue( |
873 | | const OperationParameterNNPtr ¶meterIn, |
874 | | const ParameterValueNNPtr &valueIn) |
875 | 0 | : GeneralParameterValue(), |
876 | 0 | d(std::make_unique<Private>(parameterIn, valueIn)) {} |
877 | | |
878 | | // --------------------------------------------------------------------------- |
879 | | |
880 | | /** \brief Instantiate a OperationParameterValue. |
881 | | * |
882 | | * @param parameterIn Parameter (definition). |
883 | | * @param valueIn Parameter value. |
884 | | * @return a new OperationParameterValue. |
885 | | */ |
886 | | OperationParameterValueNNPtr |
887 | | OperationParameterValue::create(const OperationParameterNNPtr ¶meterIn, |
888 | 0 | const ParameterValueNNPtr &valueIn) { |
889 | 0 | return OperationParameterValue::nn_make_shared<OperationParameterValue>( |
890 | 0 | parameterIn, valueIn); |
891 | 0 | } |
892 | | |
893 | | // --------------------------------------------------------------------------- |
894 | | |
895 | | //! @cond Doxygen_Suppress |
896 | 0 | OperationParameterValue::~OperationParameterValue() = default; |
897 | | //! @endcond |
898 | | |
899 | | // --------------------------------------------------------------------------- |
900 | | |
901 | | /** \brief Return the parameter (definition) |
902 | | * |
903 | | * @return the parameter (definition). |
904 | | */ |
905 | | const OperationParameterNNPtr & |
906 | 0 | OperationParameterValue::parameter() PROJ_PURE_DEFN { |
907 | 0 | return d->parameter; |
908 | 0 | } |
909 | | |
910 | | // --------------------------------------------------------------------------- |
911 | | |
912 | | /** \brief Return the parameter value. |
913 | | * |
914 | | * @return the parameter value. |
915 | | */ |
916 | | const ParameterValueNNPtr & |
917 | 0 | OperationParameterValue::parameterValue() PROJ_PURE_DEFN { |
918 | 0 | return d->parameterValue; |
919 | 0 | } |
920 | | |
921 | | // --------------------------------------------------------------------------- |
922 | | |
923 | | //! @cond Doxygen_Suppress |
924 | | void OperationParameterValue::_exportToWKT( |
925 | | // cppcheck-suppress passedByValue |
926 | 0 | io::WKTFormatter *formatter) const { |
927 | 0 | _exportToWKT(formatter, nullptr); |
928 | 0 | } |
929 | | |
930 | | void OperationParameterValue::_exportToWKT(io::WKTFormatter *formatter, |
931 | 0 | const MethodMapping *mapping) const { |
932 | 0 | const ParamMapping *paramMapping = |
933 | 0 | mapping ? getMapping(mapping, d->parameter) : nullptr; |
934 | 0 | if (paramMapping && paramMapping->wkt1_name == nullptr) { |
935 | 0 | return; |
936 | 0 | } |
937 | 0 | const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; |
938 | 0 | if (isWKT2 && parameterValue()->type() == ParameterValue::Type::FILENAME) { |
939 | 0 | formatter->startNode(io::WKTConstants::PARAMETERFILE, |
940 | 0 | !parameter()->identifiers().empty()); |
941 | 0 | } else { |
942 | 0 | formatter->startNode(io::WKTConstants::PARAMETER, |
943 | 0 | !parameter()->identifiers().empty()); |
944 | 0 | } |
945 | 0 | if (paramMapping) { |
946 | 0 | formatter->addQuotedString(paramMapping->wkt1_name); |
947 | 0 | } else { |
948 | 0 | formatter->addQuotedString(parameter()->nameStr()); |
949 | 0 | } |
950 | 0 | parameterValue()->_exportToWKT(formatter); |
951 | 0 | if (formatter->outputId()) { |
952 | 0 | parameter()->formatID(formatter); |
953 | 0 | } |
954 | 0 | formatter->endNode(); |
955 | 0 | } |
956 | | //! @endcond |
957 | | |
958 | | // --------------------------------------------------------------------------- |
959 | | |
960 | | //! @cond Doxygen_Suppress |
961 | | void OperationParameterValue::_exportToJSON( |
962 | 0 | io::JSONFormatter *formatter) const { |
963 | 0 | auto writer = formatter->writer(); |
964 | 0 | auto objectContext(formatter->MakeObjectContext( |
965 | 0 | "ParameterValue", !parameter()->identifiers().empty())); |
966 | |
|
967 | 0 | writer->AddObjKey("name"); |
968 | 0 | writer->Add(parameter()->nameStr()); |
969 | |
|
970 | 0 | const auto &l_value(parameterValue()); |
971 | 0 | const auto value_type = l_value->type(); |
972 | 0 | if (value_type == ParameterValue::Type::MEASURE) { |
973 | 0 | writer->AddObjKey("value"); |
974 | 0 | writer->Add(l_value->value().value(), 15); |
975 | 0 | writer->AddObjKey("unit"); |
976 | 0 | const auto &l_unit(l_value->value().unit()); |
977 | 0 | if (l_unit == common::UnitOfMeasure::METRE || |
978 | 0 | l_unit == common::UnitOfMeasure::DEGREE || |
979 | 0 | l_unit == common::UnitOfMeasure::SCALE_UNITY) { |
980 | 0 | writer->Add(l_unit.name()); |
981 | 0 | } else { |
982 | 0 | l_unit._exportToJSON(formatter); |
983 | 0 | } |
984 | 0 | } else if (value_type == ParameterValue::Type::FILENAME) { |
985 | 0 | writer->AddObjKey("value"); |
986 | 0 | writer->Add(l_value->valueFile()); |
987 | 0 | } else if (value_type == ParameterValue::Type::INTEGER) { |
988 | 0 | writer->AddObjKey("value"); |
989 | 0 | writer->Add(l_value->integerValue()); |
990 | 0 | } |
991 | |
|
992 | 0 | if (formatter->outputId()) { |
993 | 0 | parameter()->formatID(formatter); |
994 | 0 | } |
995 | 0 | } |
996 | | //! @endcond |
997 | | |
998 | | // --------------------------------------------------------------------------- |
999 | | |
1000 | | //! @cond Doxygen_Suppress |
1001 | | |
1002 | | /** Utility method used on WKT2 import to convert from abridged transformation |
1003 | | * to "normal" transformation parameters. |
1004 | | */ |
1005 | | bool OperationParameterValue::convertFromAbridged( |
1006 | | const std::string ¶mName, double &val, |
1007 | 0 | const common::UnitOfMeasure *&unit, int ¶mEPSGCode) { |
1008 | 0 | if (metadata::Identifier::isEquivalentName( |
1009 | 0 | paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_TRANSLATION) || |
1010 | 0 | paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION) { |
1011 | 0 | unit = &common::UnitOfMeasure::METRE; |
1012 | 0 | paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION; |
1013 | 0 | return true; |
1014 | 0 | } else if (metadata::Identifier::isEquivalentName( |
1015 | 0 | paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_TRANSLATION) || |
1016 | 0 | paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION) { |
1017 | 0 | unit = &common::UnitOfMeasure::METRE; |
1018 | 0 | paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION; |
1019 | 0 | return true; |
1020 | 0 | } else if (metadata::Identifier::isEquivalentName( |
1021 | 0 | paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_TRANSLATION) || |
1022 | 0 | paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION) { |
1023 | 0 | unit = &common::UnitOfMeasure::METRE; |
1024 | 0 | paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION; |
1025 | 0 | return true; |
1026 | 0 | } else if (metadata::Identifier::isEquivalentName( |
1027 | 0 | paramName.c_str(), EPSG_NAME_PARAMETER_X_AXIS_ROTATION) || |
1028 | 0 | paramEPSGCode == EPSG_CODE_PARAMETER_X_AXIS_ROTATION) { |
1029 | 0 | unit = &common::UnitOfMeasure::ARC_SECOND; |
1030 | 0 | paramEPSGCode = EPSG_CODE_PARAMETER_X_AXIS_ROTATION; |
1031 | 0 | return true; |
1032 | 0 | } else if (metadata::Identifier::isEquivalentName( |
1033 | 0 | paramName.c_str(), EPSG_NAME_PARAMETER_Y_AXIS_ROTATION) || |
1034 | 0 | paramEPSGCode == EPSG_CODE_PARAMETER_Y_AXIS_ROTATION) { |
1035 | 0 | unit = &common::UnitOfMeasure::ARC_SECOND; |
1036 | 0 | paramEPSGCode = EPSG_CODE_PARAMETER_Y_AXIS_ROTATION; |
1037 | 0 | return true; |
1038 | |
|
1039 | 0 | } else if (metadata::Identifier::isEquivalentName( |
1040 | 0 | paramName.c_str(), EPSG_NAME_PARAMETER_Z_AXIS_ROTATION) || |
1041 | 0 | paramEPSGCode == EPSG_CODE_PARAMETER_Z_AXIS_ROTATION) { |
1042 | 0 | unit = &common::UnitOfMeasure::ARC_SECOND; |
1043 | 0 | paramEPSGCode = EPSG_CODE_PARAMETER_Z_AXIS_ROTATION; |
1044 | 0 | return true; |
1045 | |
|
1046 | 0 | } else if (metadata::Identifier::isEquivalentName( |
1047 | 0 | paramName.c_str(), EPSG_NAME_PARAMETER_SCALE_DIFFERENCE) || |
1048 | 0 | paramEPSGCode == EPSG_CODE_PARAMETER_SCALE_DIFFERENCE) { |
1049 | 0 | val = (val - 1.0) * 1e6; |
1050 | 0 | unit = &common::UnitOfMeasure::PARTS_PER_MILLION; |
1051 | 0 | paramEPSGCode = EPSG_CODE_PARAMETER_SCALE_DIFFERENCE; |
1052 | 0 | return true; |
1053 | 0 | } |
1054 | 0 | return false; |
1055 | 0 | } |
1056 | | //! @endcond |
1057 | | |
1058 | | // --------------------------------------------------------------------------- |
1059 | | |
1060 | | //! @cond Doxygen_Suppress |
1061 | | bool OperationParameterValue::_isEquivalentTo( |
1062 | | const util::IComparable *other, util::IComparable::Criterion criterion, |
1063 | 0 | const io::DatabaseContextPtr &dbContext) const { |
1064 | 0 | auto otherOPV = dynamic_cast<const OperationParameterValue *>(other); |
1065 | 0 | if (otherOPV == nullptr) { |
1066 | 0 | return false; |
1067 | 0 | } |
1068 | 0 | if (!d->parameter->_isEquivalentTo(otherOPV->d->parameter.get(), criterion, |
1069 | 0 | dbContext)) { |
1070 | 0 | return false; |
1071 | 0 | } |
1072 | 0 | if (criterion == util::IComparable::Criterion::STRICT) { |
1073 | 0 | return d->parameterValue->_isEquivalentTo( |
1074 | 0 | otherOPV->d->parameterValue.get(), criterion); |
1075 | 0 | } |
1076 | 0 | if (d->parameterValue->_isEquivalentTo(otherOPV->d->parameterValue.get(), |
1077 | 0 | criterion, dbContext)) { |
1078 | 0 | return true; |
1079 | 0 | } |
1080 | 0 | if (d->parameter->getEPSGCode() == |
1081 | 0 | EPSG_CODE_PARAMETER_AZIMUTH_PROJECTION_CENTRE || |
1082 | 0 | d->parameter->getEPSGCode() == |
1083 | 0 | EPSG_CODE_PARAMETER_ANGLE_RECTIFIED_TO_SKEW_GRID) { |
1084 | 0 | if (parameterValue()->type() == ParameterValue::Type::MEASURE && |
1085 | 0 | otherOPV->parameterValue()->type() == |
1086 | 0 | ParameterValue::Type::MEASURE) { |
1087 | 0 | const double a = std::fmod(parameterValue()->value().convertToUnit( |
1088 | 0 | common::UnitOfMeasure::DEGREE) + |
1089 | 0 | 360.0, |
1090 | 0 | 360.0); |
1091 | 0 | const double b = |
1092 | 0 | std::fmod(otherOPV->parameterValue()->value().convertToUnit( |
1093 | 0 | common::UnitOfMeasure::DEGREE) + |
1094 | 0 | 360.0, |
1095 | 0 | 360.0); |
1096 | 0 | return std::fabs(a - b) <= 1e-10 * std::fabs(a); |
1097 | 0 | } |
1098 | 0 | } |
1099 | 0 | return false; |
1100 | 0 | } |
1101 | | //! @endcond |
1102 | | |
1103 | | // --------------------------------------------------------------------------- |
1104 | | |
1105 | | //! @cond Doxygen_Suppress |
1106 | | struct GeneralOperationParameter::Private {}; |
1107 | | //! @endcond |
1108 | | |
1109 | | // --------------------------------------------------------------------------- |
1110 | | |
1111 | 0 | GeneralOperationParameter::GeneralOperationParameter() : d(nullptr) {} |
1112 | | |
1113 | | // --------------------------------------------------------------------------- |
1114 | | |
1115 | | GeneralOperationParameter::GeneralOperationParameter( |
1116 | | const GeneralOperationParameter &other) |
1117 | 0 | : IdentifiedObject(other), d(nullptr) {} |
1118 | | |
1119 | | // --------------------------------------------------------------------------- |
1120 | | |
1121 | | //! @cond Doxygen_Suppress |
1122 | 0 | GeneralOperationParameter::~GeneralOperationParameter() = default; |
1123 | | //! @endcond |
1124 | | |
1125 | | // --------------------------------------------------------------------------- |
1126 | | |
1127 | | //! @cond Doxygen_Suppress |
1128 | | struct OperationParameter::Private {}; |
1129 | | //! @endcond |
1130 | | |
1131 | | // --------------------------------------------------------------------------- |
1132 | | |
1133 | 0 | OperationParameter::OperationParameter() : d(nullptr) {} |
1134 | | |
1135 | | // --------------------------------------------------------------------------- |
1136 | | |
1137 | | OperationParameter::OperationParameter(const OperationParameter &other) |
1138 | 0 | : GeneralOperationParameter(other), d(nullptr) {} |
1139 | | |
1140 | | // --------------------------------------------------------------------------- |
1141 | | |
1142 | | //! @cond Doxygen_Suppress |
1143 | 0 | OperationParameter::~OperationParameter() = default; |
1144 | | //! @endcond |
1145 | | |
1146 | | // --------------------------------------------------------------------------- |
1147 | | |
1148 | | /** \brief Instantiate a OperationParameter. |
1149 | | * |
1150 | | * @param properties See \ref general_properties. At minimum the name should be |
1151 | | * defined. |
1152 | | * @return a new OperationParameter. |
1153 | | */ |
1154 | | OperationParameterNNPtr |
1155 | 0 | OperationParameter::create(const util::PropertyMap &properties) { |
1156 | 0 | OperationParameterNNPtr op( |
1157 | 0 | OperationParameter::nn_make_shared<OperationParameter>()); |
1158 | 0 | op->assignSelf(op); |
1159 | 0 | op->setProperties(properties); |
1160 | 0 | return op; |
1161 | 0 | } |
1162 | | |
1163 | | // --------------------------------------------------------------------------- |
1164 | | |
1165 | | //! @cond Doxygen_Suppress |
1166 | | bool OperationParameter::_isEquivalentTo( |
1167 | | const util::IComparable *other, util::IComparable::Criterion criterion, |
1168 | 0 | const io::DatabaseContextPtr &dbContext) const { |
1169 | 0 | auto otherOP = dynamic_cast<const OperationParameter *>(other); |
1170 | 0 | if (otherOP == nullptr) { |
1171 | 0 | return false; |
1172 | 0 | } |
1173 | 0 | if (criterion == util::IComparable::Criterion::STRICT) { |
1174 | 0 | return IdentifiedObject::_isEquivalentTo(other, criterion, dbContext); |
1175 | 0 | } |
1176 | 0 | if (IdentifiedObject::_isEquivalentTo(other, criterion, dbContext)) { |
1177 | 0 | return true; |
1178 | 0 | } |
1179 | 0 | auto l_epsgCode = getEPSGCode(); |
1180 | 0 | return l_epsgCode != 0 && l_epsgCode == otherOP->getEPSGCode(); |
1181 | 0 | } |
1182 | | //! @endcond |
1183 | | |
1184 | | // --------------------------------------------------------------------------- |
1185 | | |
1186 | 0 | void OperationParameter::_exportToWKT(io::WKTFormatter *) const {} |
1187 | | |
1188 | | // --------------------------------------------------------------------------- |
1189 | | |
1190 | | /** \brief Return the name of a parameter designed by its EPSG code |
1191 | | * @return name, or nullptr if not found |
1192 | | */ |
1193 | 0 | const char *OperationParameter::getNameForEPSGCode(int epsg_code) noexcept { |
1194 | 0 | size_t nParamNameCodes = 0; |
1195 | 0 | const auto paramNameCodes = getParamNameCodes(nParamNameCodes); |
1196 | 0 | for (size_t i = 0; i < nParamNameCodes; ++i) { |
1197 | 0 | const auto &tuple = paramNameCodes[i]; |
1198 | 0 | if (tuple.epsg_code == epsg_code) { |
1199 | 0 | return tuple.name; |
1200 | 0 | } |
1201 | 0 | } |
1202 | 0 | return nullptr; |
1203 | 0 | } |
1204 | | |
1205 | | // --------------------------------------------------------------------------- |
1206 | | |
1207 | | /** \brief Return the EPSG code, either directly, or through the name |
1208 | | * @return code, or 0 if not found |
1209 | | */ |
1210 | 0 | int OperationParameter::getEPSGCode() PROJ_PURE_DEFN { |
1211 | 0 | int epsg_code = IdentifiedObject::getEPSGCode(); |
1212 | 0 | if (epsg_code == 0) { |
1213 | 0 | const auto &l_name = nameStr(); |
1214 | 0 | size_t nParamNameCodes = 0; |
1215 | 0 | const auto paramNameCodes = getParamNameCodes(nParamNameCodes); |
1216 | 0 | for (size_t i = 0; i < nParamNameCodes; ++i) { |
1217 | 0 | const auto &tuple = paramNameCodes[i]; |
1218 | 0 | if (metadata::Identifier::isEquivalentName(l_name.c_str(), |
1219 | 0 | tuple.name)) { |
1220 | 0 | return tuple.epsg_code; |
1221 | 0 | } |
1222 | 0 | } |
1223 | 0 | if (metadata::Identifier::isEquivalentName(l_name.c_str(), |
1224 | 0 | "Latitude of origin")) { |
1225 | 0 | return EPSG_CODE_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN; |
1226 | 0 | } |
1227 | 0 | if (metadata::Identifier::isEquivalentName(l_name.c_str(), |
1228 | 0 | "Scale factor")) { |
1229 | 0 | return EPSG_CODE_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN; |
1230 | 0 | } |
1231 | 0 | } |
1232 | 0 | return epsg_code; |
1233 | 0 | } |
1234 | | |
1235 | | // --------------------------------------------------------------------------- |
1236 | | |
1237 | | //! @cond Doxygen_Suppress |
1238 | | struct SingleOperation::Private { |
1239 | | std::vector<GeneralParameterValueNNPtr> parameterValues_{}; |
1240 | | OperationMethodNNPtr method_; |
1241 | | |
1242 | | explicit Private(const OperationMethodNNPtr &methodIn) |
1243 | 0 | : method_(methodIn) {} |
1244 | | }; |
1245 | | //! @endcond |
1246 | | |
1247 | | // --------------------------------------------------------------------------- |
1248 | | |
1249 | | SingleOperation::SingleOperation(const OperationMethodNNPtr &methodIn) |
1250 | 0 | : d(std::make_unique<Private>(methodIn)) { |
1251 | |
|
1252 | 0 | const int methodEPSGCode = d->method_->getEPSGCode(); |
1253 | 0 | const auto &methodName = d->method_->nameStr(); |
1254 | 0 | setRequiresPerCoordinateInputTime( |
1255 | 0 | isTimeDependent(methodName) || |
1256 | 0 | methodEPSGCode == |
1257 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || |
1258 | 0 | methodEPSGCode == |
1259 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || |
1260 | 0 | methodEPSGCode == |
1261 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D || |
1262 | 0 | methodEPSGCode == |
1263 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || |
1264 | 0 | methodEPSGCode == |
1265 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || |
1266 | 0 | methodEPSGCode == |
1267 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D); |
1268 | 0 | } |
1269 | | |
1270 | | // --------------------------------------------------------------------------- |
1271 | | |
1272 | | SingleOperation::SingleOperation(const SingleOperation &other) |
1273 | | : |
1274 | | #if !defined(COMPILER_WARNS_ABOUT_ABSTRACT_VBASE_INIT) |
1275 | | CoordinateOperation(other), |
1276 | | #endif |
1277 | 0 | d(std::make_unique<Private>(*other.d)) { |
1278 | 0 | } |
1279 | | |
1280 | | // --------------------------------------------------------------------------- |
1281 | | |
1282 | | //! @cond Doxygen_Suppress |
1283 | 0 | SingleOperation::~SingleOperation() = default; |
1284 | | //! @endcond |
1285 | | |
1286 | | // --------------------------------------------------------------------------- |
1287 | | |
1288 | | /** \brief Return the parameter values. |
1289 | | * |
1290 | | * @return the parameter values. |
1291 | | */ |
1292 | | const std::vector<GeneralParameterValueNNPtr> & |
1293 | 0 | SingleOperation::parameterValues() PROJ_PURE_DEFN { |
1294 | 0 | return d->parameterValues_; |
1295 | 0 | } |
1296 | | |
1297 | | // --------------------------------------------------------------------------- |
1298 | | |
1299 | | /** \brief Return the operation method associated to the operation. |
1300 | | * |
1301 | | * @return the operation method. |
1302 | | */ |
1303 | 0 | const OperationMethodNNPtr &SingleOperation::method() PROJ_PURE_DEFN { |
1304 | 0 | return d->method_; |
1305 | 0 | } |
1306 | | |
1307 | | // --------------------------------------------------------------------------- |
1308 | | |
1309 | | void SingleOperation::setParameterValues( |
1310 | 0 | const std::vector<GeneralParameterValueNNPtr> &values) { |
1311 | 0 | d->parameterValues_ = values; |
1312 | 0 | } |
1313 | | |
1314 | | // --------------------------------------------------------------------------- |
1315 | | |
1316 | | //! @cond Doxygen_Suppress |
1317 | | static const ParameterValuePtr nullParameterValue; |
1318 | | //! @endcond |
1319 | | |
1320 | | /** \brief Return the parameter value corresponding to a parameter name or |
1321 | | * EPSG code |
1322 | | * |
1323 | | * @param paramName the parameter name (or empty, in which case epsg_code |
1324 | | * should be non zero) |
1325 | | * @param epsg_code the parameter EPSG code (possibly zero) |
1326 | | * @return the value, or nullptr if not found. |
1327 | | */ |
1328 | | const ParameterValuePtr & |
1329 | | SingleOperation::parameterValue(const std::string ¶mName, |
1330 | 0 | int epsg_code) const noexcept { |
1331 | 0 | if (epsg_code) { |
1332 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
1333 | 0 | auto opParamvalue = dynamic_cast<const OperationParameterValue *>( |
1334 | 0 | genOpParamvalue.get()); |
1335 | 0 | if (opParamvalue) { |
1336 | 0 | const auto ¶meter = opParamvalue->parameter(); |
1337 | 0 | if (parameter->getEPSGCode() == epsg_code) { |
1338 | 0 | return opParamvalue->parameterValue(); |
1339 | 0 | } |
1340 | 0 | } |
1341 | 0 | } |
1342 | 0 | } |
1343 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
1344 | 0 | auto opParamvalue = dynamic_cast<const OperationParameterValue *>( |
1345 | 0 | genOpParamvalue.get()); |
1346 | 0 | if (opParamvalue) { |
1347 | 0 | const auto ¶meter = opParamvalue->parameter(); |
1348 | 0 | if (metadata::Identifier::isEquivalentName( |
1349 | 0 | paramName.c_str(), parameter->nameStr().c_str())) { |
1350 | 0 | return opParamvalue->parameterValue(); |
1351 | 0 | } |
1352 | 0 | } |
1353 | 0 | } |
1354 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
1355 | 0 | auto opParamvalue = dynamic_cast<const OperationParameterValue *>( |
1356 | 0 | genOpParamvalue.get()); |
1357 | 0 | if (opParamvalue) { |
1358 | 0 | const auto ¶meter = opParamvalue->parameter(); |
1359 | 0 | if (areEquivalentParameters(paramName, parameter->nameStr())) { |
1360 | 0 | return opParamvalue->parameterValue(); |
1361 | 0 | } |
1362 | 0 | } |
1363 | 0 | } |
1364 | 0 | return nullParameterValue; |
1365 | 0 | } |
1366 | | |
1367 | | // --------------------------------------------------------------------------- |
1368 | | |
1369 | | /** \brief Return the parameter value corresponding to a EPSG code |
1370 | | * |
1371 | | * @param epsg_code the parameter EPSG code |
1372 | | * @return the value, or nullptr if not found. |
1373 | | */ |
1374 | | const ParameterValuePtr & |
1375 | 0 | SingleOperation::parameterValue(int epsg_code) const noexcept { |
1376 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
1377 | 0 | auto opParamvalue = dynamic_cast<const OperationParameterValue *>( |
1378 | 0 | genOpParamvalue.get()); |
1379 | 0 | if (opParamvalue) { |
1380 | 0 | const auto ¶meter = opParamvalue->parameter(); |
1381 | 0 | if (parameter->getEPSGCode() == epsg_code) { |
1382 | 0 | return opParamvalue->parameterValue(); |
1383 | 0 | } |
1384 | 0 | } |
1385 | 0 | } |
1386 | 0 | return nullParameterValue; |
1387 | 0 | } |
1388 | | |
1389 | | // --------------------------------------------------------------------------- |
1390 | | |
1391 | | /** \brief Return the parameter value, as a measure, corresponding to a |
1392 | | * parameter name or EPSG code |
1393 | | * |
1394 | | * @param paramName the parameter name (or empty, in which case epsg_code |
1395 | | * should be non zero) |
1396 | | * @param epsg_code the parameter EPSG code (possibly zero) |
1397 | | * @return the measure, or the empty Measure() object if not found. |
1398 | | */ |
1399 | | const common::Measure & |
1400 | | SingleOperation::parameterValueMeasure(const std::string ¶mName, |
1401 | 0 | int epsg_code) const noexcept { |
1402 | 0 | const auto &val = parameterValue(paramName, epsg_code); |
1403 | 0 | if (val && val->type() == ParameterValue::Type::MEASURE) { |
1404 | 0 | return val->value(); |
1405 | 0 | } |
1406 | 0 | return nullMeasure; |
1407 | 0 | } |
1408 | | |
1409 | | /** \brief Return the parameter value, as a measure, corresponding to a |
1410 | | * EPSG code |
1411 | | * |
1412 | | * @param epsg_code the parameter EPSG code |
1413 | | * @return the measure, or the empty Measure() object if not found. |
1414 | | */ |
1415 | | const common::Measure & |
1416 | 0 | SingleOperation::parameterValueMeasure(int epsg_code) const noexcept { |
1417 | 0 | const auto &val = parameterValue(epsg_code); |
1418 | 0 | if (val && val->type() == ParameterValue::Type::MEASURE) { |
1419 | 0 | return val->value(); |
1420 | 0 | } |
1421 | 0 | return nullMeasure; |
1422 | 0 | } |
1423 | | |
1424 | | //! @cond Doxygen_Suppress |
1425 | | |
1426 | | double |
1427 | 0 | SingleOperation::parameterValueNumericAsSI(int epsg_code) const noexcept { |
1428 | 0 | const auto &val = parameterValue(epsg_code); |
1429 | 0 | if (val && val->type() == ParameterValue::Type::MEASURE) { |
1430 | 0 | return val->value().getSIValue(); |
1431 | 0 | } |
1432 | 0 | return 0.0; |
1433 | 0 | } |
1434 | | |
1435 | | double SingleOperation::parameterValueNumeric( |
1436 | 0 | int epsg_code, const common::UnitOfMeasure &targetUnit) const noexcept { |
1437 | 0 | const auto &val = parameterValue(epsg_code); |
1438 | 0 | if (val && val->type() == ParameterValue::Type::MEASURE) { |
1439 | 0 | return val->value().convertToUnit(targetUnit); |
1440 | 0 | } |
1441 | 0 | return 0.0; |
1442 | 0 | } |
1443 | | |
1444 | | double SingleOperation::parameterValueNumeric( |
1445 | | const char *param_name, |
1446 | 0 | const common::UnitOfMeasure &targetUnit) const noexcept { |
1447 | 0 | const auto &val = parameterValue(param_name, 0); |
1448 | 0 | if (val && val->type() == ParameterValue::Type::MEASURE) { |
1449 | 0 | return val->value().convertToUnit(targetUnit); |
1450 | 0 | } |
1451 | 0 | return 0.0; |
1452 | 0 | } |
1453 | | |
1454 | | //! @endcond |
1455 | | // --------------------------------------------------------------------------- |
1456 | | |
1457 | | /** \brief Instantiate a PROJ-based single operation. |
1458 | | * |
1459 | | * \note The operation might internally be a pipeline chaining several |
1460 | | * operations. |
1461 | | * The use of the SingleOperation modeling here is mostly to be able to get |
1462 | | * the PROJ string as a parameter. |
1463 | | * |
1464 | | * @param properties Properties |
1465 | | * @param PROJString the PROJ string. |
1466 | | * @param sourceCRS source CRS (might be null). |
1467 | | * @param targetCRS target CRS (might be null). |
1468 | | * @param accuracies Vector of positional accuracy (might be empty). |
1469 | | * @return the new instance |
1470 | | */ |
1471 | | SingleOperationNNPtr SingleOperation::createPROJBased( |
1472 | | const util::PropertyMap &properties, const std::string &PROJString, |
1473 | | const crs::CRSPtr &sourceCRS, const crs::CRSPtr &targetCRS, |
1474 | 0 | const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { |
1475 | 0 | return util::nn_static_pointer_cast<SingleOperation>( |
1476 | 0 | PROJBasedOperation::create(properties, PROJString, sourceCRS, targetCRS, |
1477 | 0 | accuracies)); |
1478 | 0 | } |
1479 | | |
1480 | | // --------------------------------------------------------------------------- |
1481 | | |
1482 | | //! @cond Doxygen_Suppress |
1483 | | bool SingleOperation::_isEquivalentTo( |
1484 | | const util::IComparable *other, util::IComparable::Criterion criterion, |
1485 | 0 | const io::DatabaseContextPtr &dbContext) const { |
1486 | 0 | return _isEquivalentTo(other, criterion, dbContext, false); |
1487 | 0 | } |
1488 | | |
1489 | | bool SingleOperation::_isEquivalentTo(const util::IComparable *other, |
1490 | | util::IComparable::Criterion criterion, |
1491 | | const io::DatabaseContextPtr &dbContext, |
1492 | 0 | bool inOtherDirection) const { |
1493 | |
|
1494 | 0 | auto otherSO = dynamic_cast<const SingleOperation *>(other); |
1495 | 0 | if (otherSO == nullptr || |
1496 | 0 | (criterion == util::IComparable::Criterion::STRICT && |
1497 | 0 | !ObjectUsage::_isEquivalentTo(other, criterion, dbContext))) { |
1498 | 0 | return false; |
1499 | 0 | } |
1500 | | |
1501 | 0 | const int methodEPSGCode = d->method_->getEPSGCode(); |
1502 | 0 | const int otherMethodEPSGCode = otherSO->d->method_->getEPSGCode(); |
1503 | |
|
1504 | 0 | bool equivalentMethods = |
1505 | 0 | (criterion == util::IComparable::Criterion::EQUIVALENT && |
1506 | 0 | methodEPSGCode != 0 && methodEPSGCode == otherMethodEPSGCode) || |
1507 | 0 | d->method_->_isEquivalentTo(otherSO->d->method_.get(), criterion, |
1508 | 0 | dbContext); |
1509 | 0 | if (!equivalentMethods && |
1510 | 0 | criterion == util::IComparable::Criterion::EQUIVALENT) { |
1511 | 0 | if ((methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA && |
1512 | 0 | otherMethodEPSGCode == |
1513 | 0 | EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) || |
1514 | 0 | (otherMethodEPSGCode == |
1515 | 0 | EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA && |
1516 | 0 | methodEPSGCode == |
1517 | 0 | EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA_SPHERICAL) || |
1518 | 0 | (methodEPSGCode == |
1519 | 0 | EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA && |
1520 | 0 | otherMethodEPSGCode == |
1521 | 0 | EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) || |
1522 | 0 | (otherMethodEPSGCode == |
1523 | 0 | EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA && |
1524 | 0 | methodEPSGCode == |
1525 | 0 | EPSG_CODE_METHOD_LAMBERT_CYLINDRICAL_EQUAL_AREA_SPHERICAL) || |
1526 | 0 | (methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && |
1527 | 0 | otherMethodEPSGCode == |
1528 | 0 | EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) || |
1529 | 0 | (otherMethodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL && |
1530 | 0 | methodEPSGCode == |
1531 | 0 | EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL)) { |
1532 | 0 | auto geodCRS = |
1533 | 0 | dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get()); |
1534 | 0 | auto otherGeodCRS = dynamic_cast<const crs::GeodeticCRS *>( |
1535 | 0 | otherSO->sourceCRS().get()); |
1536 | 0 | if (geodCRS && otherGeodCRS && geodCRS->ellipsoid()->isSphere() && |
1537 | 0 | otherGeodCRS->ellipsoid()->isSphere()) { |
1538 | 0 | equivalentMethods = true; |
1539 | 0 | } |
1540 | 0 | } |
1541 | 0 | } |
1542 | |
|
1543 | 0 | if (!equivalentMethods) { |
1544 | 0 | if (criterion == util::IComparable::Criterion::EQUIVALENT) { |
1545 | |
|
1546 | 0 | const auto isTOWGS84Transf = [](int code) { |
1547 | 0 | return code == |
1548 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || |
1549 | 0 | code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || |
1550 | 0 | code == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || |
1551 | 0 | code == |
1552 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || |
1553 | 0 | code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || |
1554 | 0 | code == |
1555 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || |
1556 | 0 | code == |
1557 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D || |
1558 | 0 | code == EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D || |
1559 | 0 | code == |
1560 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D || |
1561 | 0 | code == |
1562 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND; |
1563 | 0 | }; |
1564 | | |
1565 | | // Translation vs (PV or CF) |
1566 | | // or different PV vs CF convention |
1567 | 0 | if (isTOWGS84Transf(methodEPSGCode) && |
1568 | 0 | isTOWGS84Transf(otherMethodEPSGCode)) { |
1569 | 0 | auto transf = static_cast<const Transformation *>(this); |
1570 | 0 | auto otherTransf = static_cast<const Transformation *>(otherSO); |
1571 | 0 | auto params = transf->getTOWGS84Parameters(true); |
1572 | 0 | auto otherParams = otherTransf->getTOWGS84Parameters(true); |
1573 | 0 | assert(params.size() == 7); |
1574 | 0 | assert(otherParams.size() == 7); |
1575 | 0 | for (size_t i = 0; i < 7; i++) { |
1576 | 0 | if (std::fabs(params[i] - otherParams[i]) > |
1577 | 0 | 1e-10 * std::fabs(params[i])) { |
1578 | 0 | return false; |
1579 | 0 | } |
1580 | 0 | } |
1581 | 0 | return true; |
1582 | 0 | } |
1583 | | |
1584 | | // _1SP methods can sometimes be equivalent to _2SP ones |
1585 | | // Check it by using convertToOtherMethod() |
1586 | 0 | if (methodEPSGCode == |
1587 | 0 | EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP && |
1588 | 0 | otherMethodEPSGCode == |
1589 | 0 | EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { |
1590 | | // Convert from 2SP to 1SP as the other direction has more |
1591 | | // degree of liberties. |
1592 | 0 | return otherSO->_isEquivalentTo(this, criterion, dbContext); |
1593 | 0 | } else if ((methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_A && |
1594 | 0 | otherMethodEPSGCode == |
1595 | 0 | EPSG_CODE_METHOD_MERCATOR_VARIANT_B) || |
1596 | 0 | (methodEPSGCode == EPSG_CODE_METHOD_MERCATOR_VARIANT_B && |
1597 | 0 | otherMethodEPSGCode == |
1598 | 0 | EPSG_CODE_METHOD_MERCATOR_VARIANT_A) || |
1599 | 0 | (methodEPSGCode == |
1600 | 0 | EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP && |
1601 | 0 | otherMethodEPSGCode == |
1602 | 0 | EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP)) { |
1603 | 0 | auto conv = dynamic_cast<const Conversion *>(this); |
1604 | 0 | if (conv) { |
1605 | 0 | auto eqConv = |
1606 | 0 | conv->convertToOtherMethod(otherMethodEPSGCode); |
1607 | 0 | if (eqConv) { |
1608 | 0 | return eqConv->_isEquivalentTo(other, criterion, |
1609 | 0 | dbContext); |
1610 | 0 | } |
1611 | 0 | } |
1612 | 0 | } |
1613 | 0 | } |
1614 | | |
1615 | 0 | return false; |
1616 | 0 | } |
1617 | | |
1618 | 0 | const auto &values = d->parameterValues_; |
1619 | 0 | const auto &otherValues = otherSO->d->parameterValues_; |
1620 | 0 | const auto valuesSize = values.size(); |
1621 | 0 | const auto otherValuesSize = otherValues.size(); |
1622 | 0 | if (criterion == util::IComparable::Criterion::STRICT) { |
1623 | 0 | if (valuesSize != otherValuesSize) { |
1624 | 0 | return false; |
1625 | 0 | } |
1626 | 0 | for (size_t i = 0; i < valuesSize; i++) { |
1627 | 0 | if (!values[i]->_isEquivalentTo(otherValues[i].get(), criterion, |
1628 | 0 | dbContext)) { |
1629 | 0 | return false; |
1630 | 0 | } |
1631 | 0 | } |
1632 | 0 | return true; |
1633 | 0 | } |
1634 | | |
1635 | 0 | std::vector<bool> candidateIndices(otherValuesSize, true); |
1636 | 0 | bool equivalent = true; |
1637 | 0 | bool foundMissingArgs = valuesSize != otherValuesSize; |
1638 | |
|
1639 | 0 | for (size_t i = 0; equivalent && i < valuesSize; i++) { |
1640 | 0 | auto opParamvalue = |
1641 | 0 | dynamic_cast<const OperationParameterValue *>(values[i].get()); |
1642 | 0 | if (!opParamvalue) |
1643 | 0 | return false; |
1644 | | |
1645 | 0 | equivalent = false; |
1646 | 0 | bool sameNameDifferentValue = false; |
1647 | 0 | for (size_t j = 0; j < otherValuesSize; j++) { |
1648 | 0 | if (candidateIndices[j] && |
1649 | 0 | values[i]->_isEquivalentTo(otherValues[j].get(), criterion, |
1650 | 0 | dbContext)) { |
1651 | 0 | candidateIndices[j] = false; |
1652 | 0 | equivalent = true; |
1653 | 0 | break; |
1654 | 0 | } else if (candidateIndices[j]) { |
1655 | 0 | auto otherOpParamvalue = |
1656 | 0 | dynamic_cast<const OperationParameterValue *>( |
1657 | 0 | otherValues[j].get()); |
1658 | 0 | if (!otherOpParamvalue) |
1659 | 0 | return false; |
1660 | 0 | sameNameDifferentValue = |
1661 | 0 | opParamvalue->parameter()->_isEquivalentTo( |
1662 | 0 | otherOpParamvalue->parameter().get(), criterion, |
1663 | 0 | dbContext); |
1664 | 0 | if (sameNameDifferentValue) { |
1665 | 0 | candidateIndices[j] = false; |
1666 | 0 | break; |
1667 | 0 | } |
1668 | 0 | } |
1669 | 0 | } |
1670 | | |
1671 | 0 | if (!equivalent && |
1672 | 0 | methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { |
1673 | | // For LCC_2SP, the standard parallels can be switched and |
1674 | | // this will result in the same result. |
1675 | 0 | const int paramEPSGCode = opParamvalue->parameter()->getEPSGCode(); |
1676 | 0 | if (paramEPSGCode == |
1677 | 0 | EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL || |
1678 | 0 | paramEPSGCode == |
1679 | 0 | EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) { |
1680 | 0 | auto value_1st = parameterValue( |
1681 | 0 | EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL); |
1682 | 0 | auto value_2nd = parameterValue( |
1683 | 0 | EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL); |
1684 | 0 | if (value_1st && value_2nd) { |
1685 | 0 | equivalent = |
1686 | 0 | value_1st->_isEquivalentTo( |
1687 | 0 | otherSO |
1688 | 0 | ->parameterValue( |
1689 | 0 | EPSG_CODE_PARAMETER_LATITUDE_2ND_STD_PARALLEL) |
1690 | 0 | .get(), |
1691 | 0 | criterion, dbContext) && |
1692 | 0 | value_2nd->_isEquivalentTo( |
1693 | 0 | otherSO |
1694 | 0 | ->parameterValue( |
1695 | 0 | EPSG_CODE_PARAMETER_LATITUDE_1ST_STD_PARALLEL) |
1696 | 0 | .get(), |
1697 | 0 | criterion, dbContext); |
1698 | 0 | } |
1699 | 0 | } |
1700 | 0 | } |
1701 | |
|
1702 | 0 | if (equivalent) { |
1703 | 0 | continue; |
1704 | 0 | } |
1705 | | |
1706 | 0 | if (sameNameDifferentValue) { |
1707 | 0 | break; |
1708 | 0 | } |
1709 | | |
1710 | | // If there are parameters in this method not found in the other one, |
1711 | | // check that they are set to a default neutral value, that is 1 |
1712 | | // for scale, and 0 otherwise. |
1713 | 0 | foundMissingArgs = true; |
1714 | 0 | const auto &value = opParamvalue->parameterValue(); |
1715 | 0 | if (value->type() != ParameterValue::Type::MEASURE) { |
1716 | 0 | break; |
1717 | 0 | } |
1718 | 0 | if (value->value().unit().type() == |
1719 | 0 | common::UnitOfMeasure::Type::SCALE) { |
1720 | 0 | equivalent = value->value().getSIValue() == 1.0; |
1721 | 0 | } else { |
1722 | 0 | equivalent = value->value().getSIValue() == 0.0; |
1723 | 0 | } |
1724 | 0 | } |
1725 | | |
1726 | | // In the case the arguments don't perfectly match, try the reverse |
1727 | | // check. |
1728 | 0 | if (equivalent && foundMissingArgs && !inOtherDirection) { |
1729 | 0 | return otherSO->_isEquivalentTo(this, criterion, dbContext, true); |
1730 | 0 | } |
1731 | | |
1732 | | // Equivalent formulations of 2SP can have different parameters |
1733 | | // Then convert to 1SP and compare. |
1734 | 0 | if (!equivalent && |
1735 | 0 | methodEPSGCode == EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP) { |
1736 | 0 | auto conv = dynamic_cast<const Conversion *>(this); |
1737 | 0 | auto otherConv = dynamic_cast<const Conversion *>(other); |
1738 | 0 | if (conv && otherConv) { |
1739 | 0 | auto thisAs1SP = conv->convertToOtherMethod( |
1740 | 0 | EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); |
1741 | 0 | auto otherAs1SP = otherConv->convertToOtherMethod( |
1742 | 0 | EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP); |
1743 | 0 | if (thisAs1SP && otherAs1SP) { |
1744 | 0 | equivalent = thisAs1SP->_isEquivalentTo(otherAs1SP.get(), |
1745 | 0 | criterion, dbContext); |
1746 | 0 | } |
1747 | 0 | } |
1748 | 0 | } |
1749 | 0 | return equivalent; |
1750 | 0 | } |
1751 | | //! @endcond |
1752 | | |
1753 | | // --------------------------------------------------------------------------- |
1754 | | |
1755 | | std::set<GridDescription> |
1756 | | SingleOperation::gridsNeeded(const io::DatabaseContextPtr &databaseContext, |
1757 | 0 | bool considerKnownGridsAsAvailable) const { |
1758 | 0 | std::set<GridDescription> res; |
1759 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
1760 | 0 | auto opParamvalue = dynamic_cast<const OperationParameterValue *>( |
1761 | 0 | genOpParamvalue.get()); |
1762 | 0 | if (opParamvalue) { |
1763 | 0 | const auto &value = opParamvalue->parameterValue(); |
1764 | 0 | if (value->type() == ParameterValue::Type::FILENAME) { |
1765 | 0 | const auto gridNames = split(value->valueFile(), ","); |
1766 | 0 | for (const auto &gridName : gridNames) { |
1767 | 0 | GridDescription desc; |
1768 | 0 | desc.shortName = gridName; |
1769 | 0 | if (databaseContext) { |
1770 | 0 | databaseContext->lookForGridInfo( |
1771 | 0 | desc.shortName, considerKnownGridsAsAvailable, |
1772 | 0 | desc.fullName, desc.packageName, desc.url, |
1773 | 0 | desc.directDownload, desc.openLicense, |
1774 | 0 | desc.available); |
1775 | 0 | } |
1776 | 0 | res.insert(std::move(desc)); |
1777 | 0 | } |
1778 | 0 | } |
1779 | 0 | } |
1780 | 0 | } |
1781 | 0 | return res; |
1782 | 0 | } |
1783 | | |
1784 | | // --------------------------------------------------------------------------- |
1785 | | |
1786 | | /** \brief Validate the parameters used by a coordinate operation. |
1787 | | * |
1788 | | * Return whether the method is known or not, or a list of missing or extra |
1789 | | * parameters for the operations recognized by this implementation. |
1790 | | */ |
1791 | 0 | std::list<std::string> SingleOperation::validateParameters() const { |
1792 | 0 | std::list<std::string> res; |
1793 | |
|
1794 | 0 | const auto &l_method = method(); |
1795 | 0 | const auto &methodName = l_method->nameStr(); |
1796 | 0 | const auto methodEPSGCode = l_method->getEPSGCode(); |
1797 | |
|
1798 | 0 | const auto findMapping = [methodEPSGCode, &methodName]( |
1799 | 0 | const MethodMapping *mappings, |
1800 | 0 | size_t mappingCount) -> const MethodMapping * { |
1801 | 0 | if (methodEPSGCode != 0) { |
1802 | 0 | for (size_t i = 0; i < mappingCount; ++i) { |
1803 | 0 | const auto &mapping = mappings[i]; |
1804 | 0 | if (methodEPSGCode == mapping.epsg_code) { |
1805 | 0 | return &mapping; |
1806 | 0 | } |
1807 | 0 | } |
1808 | 0 | } |
1809 | 0 | for (size_t i = 0; i < mappingCount; ++i) { |
1810 | 0 | const auto &mapping = mappings[i]; |
1811 | 0 | if (metadata::Identifier::isEquivalentName(mapping.wkt2_name, |
1812 | 0 | methodName.c_str())) { |
1813 | 0 | return &mapping; |
1814 | 0 | } |
1815 | 0 | } |
1816 | 0 | return nullptr; |
1817 | 0 | }; |
1818 | |
|
1819 | 0 | size_t nProjectionMethodMappings = 0; |
1820 | 0 | const auto projectionMethodMappings = |
1821 | 0 | getProjectionMethodMappings(nProjectionMethodMappings); |
1822 | 0 | const MethodMapping *methodMapping = |
1823 | 0 | findMapping(projectionMethodMappings, nProjectionMethodMappings); |
1824 | 0 | if (methodMapping == nullptr) { |
1825 | 0 | size_t nOtherMethodMappings = 0; |
1826 | 0 | const auto otherMethodMappings = |
1827 | 0 | getOtherMethodMappings(nOtherMethodMappings); |
1828 | 0 | methodMapping = findMapping(otherMethodMappings, nOtherMethodMappings); |
1829 | 0 | } |
1830 | 0 | if (!methodMapping) { |
1831 | 0 | res.emplace_back("Unknown method " + methodName); |
1832 | 0 | return res; |
1833 | 0 | } |
1834 | 0 | if (methodMapping->wkt2_name != methodName) { |
1835 | 0 | if (metadata::Identifier::isEquivalentName(methodMapping->wkt2_name, |
1836 | 0 | methodName.c_str())) { |
1837 | 0 | std::string msg("Method name "); |
1838 | 0 | msg += methodName; |
1839 | 0 | msg += " is equivalent to official "; |
1840 | 0 | msg += methodMapping->wkt2_name; |
1841 | 0 | msg += " but not strictly equal"; |
1842 | 0 | res.emplace_back(msg); |
1843 | 0 | } else { |
1844 | 0 | std::string msg("Method name "); |
1845 | 0 | msg += methodName; |
1846 | 0 | msg += ", matched to "; |
1847 | 0 | msg += methodMapping->wkt2_name; |
1848 | 0 | msg += ", through its EPSG code has not an equivalent name"; |
1849 | 0 | res.emplace_back(msg); |
1850 | 0 | } |
1851 | 0 | } |
1852 | 0 | if (methodEPSGCode != 0 && methodEPSGCode != methodMapping->epsg_code) { |
1853 | 0 | std::string msg("Method of EPSG code "); |
1854 | 0 | msg += toString(methodEPSGCode); |
1855 | 0 | msg += " does not match official code ("; |
1856 | 0 | msg += toString(methodMapping->epsg_code); |
1857 | 0 | msg += ')'; |
1858 | 0 | res.emplace_back(msg); |
1859 | 0 | } |
1860 | | |
1861 | | // Check if expected parameters are found |
1862 | 0 | for (int i = 0; |
1863 | 0 | methodMapping->params && methodMapping->params[i] != nullptr; ++i) { |
1864 | 0 | const auto *paramMapping = methodMapping->params[i]; |
1865 | |
|
1866 | 0 | const OperationParameterValue *opv = nullptr; |
1867 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
1868 | 0 | auto opParamvalue = dynamic_cast<const OperationParameterValue *>( |
1869 | 0 | genOpParamvalue.get()); |
1870 | 0 | if (opParamvalue) { |
1871 | 0 | const auto ¶meter = opParamvalue->parameter(); |
1872 | 0 | if ((paramMapping->epsg_code != 0 && |
1873 | 0 | parameter->getEPSGCode() == paramMapping->epsg_code) || |
1874 | 0 | ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) { |
1875 | 0 | opv = opParamvalue; |
1876 | 0 | break; |
1877 | 0 | } |
1878 | 0 | } |
1879 | 0 | } |
1880 | |
|
1881 | 0 | if (!opv) { |
1882 | 0 | if ((methodEPSGCode == EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL || |
1883 | 0 | methodEPSGCode == |
1884 | 0 | EPSG_CODE_METHOD_EQUIDISTANT_CYLINDRICAL_SPHERICAL) && |
1885 | 0 | paramMapping == ¶mLatitudeNatOrigin) { |
1886 | | // extension of EPSG used by GDAL/PROJ, so we should not |
1887 | | // warn on its absence. |
1888 | 0 | continue; |
1889 | 0 | } |
1890 | 0 | std::string msg("Cannot find expected parameter "); |
1891 | 0 | msg += paramMapping->wkt2_name; |
1892 | 0 | res.emplace_back(msg); |
1893 | 0 | continue; |
1894 | 0 | } |
1895 | 0 | const auto ¶meter = opv->parameter(); |
1896 | 0 | if (paramMapping->wkt2_name != parameter->nameStr()) { |
1897 | 0 | if (ci_equal(parameter->nameStr(), paramMapping->wkt2_name)) { |
1898 | 0 | std::string msg("Parameter name "); |
1899 | 0 | msg += parameter->nameStr(); |
1900 | 0 | msg += " is equivalent to official "; |
1901 | 0 | msg += paramMapping->wkt2_name; |
1902 | 0 | msg += " but not strictly equal"; |
1903 | 0 | res.emplace_back(msg); |
1904 | 0 | } else { |
1905 | 0 | std::string msg("Parameter name "); |
1906 | 0 | msg += parameter->nameStr(); |
1907 | 0 | msg += ", matched to "; |
1908 | 0 | msg += paramMapping->wkt2_name; |
1909 | 0 | msg += ", through its EPSG code has not an equivalent name"; |
1910 | 0 | res.emplace_back(msg); |
1911 | 0 | } |
1912 | 0 | } |
1913 | 0 | const auto paramEPSGCode = parameter->getEPSGCode(); |
1914 | 0 | if (paramEPSGCode != 0 && paramEPSGCode != paramMapping->epsg_code) { |
1915 | 0 | std::string msg("Parameter of EPSG code "); |
1916 | 0 | msg += toString(paramEPSGCode); |
1917 | 0 | msg += " does not match official code ("; |
1918 | 0 | msg += toString(paramMapping->epsg_code); |
1919 | 0 | msg += ')'; |
1920 | 0 | res.emplace_back(msg); |
1921 | 0 | } |
1922 | 0 | } |
1923 | | |
1924 | | // Check if there are extra parameters |
1925 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
1926 | 0 | auto opParamvalue = dynamic_cast<const OperationParameterValue *>( |
1927 | 0 | genOpParamvalue.get()); |
1928 | 0 | if (opParamvalue) { |
1929 | 0 | const auto ¶meter = opParamvalue->parameter(); |
1930 | 0 | if (!getMapping(methodMapping, parameter)) { |
1931 | 0 | std::string msg("Parameter "); |
1932 | 0 | msg += parameter->nameStr(); |
1933 | 0 | msg += " found but not expected for this method"; |
1934 | 0 | res.emplace_back(msg); |
1935 | 0 | } |
1936 | 0 | } |
1937 | 0 | } |
1938 | |
|
1939 | 0 | return res; |
1940 | 0 | } |
1941 | | |
1942 | | // --------------------------------------------------------------------------- |
1943 | | |
1944 | | //! @cond Doxygen_Suppress |
1945 | 0 | bool SingleOperation::isLongitudeRotation() const { |
1946 | 0 | return method()->getEPSGCode() == EPSG_CODE_METHOD_LONGITUDE_ROTATION; |
1947 | 0 | } |
1948 | | |
1949 | | //! @endcond |
1950 | | |
1951 | | // --------------------------------------------------------------------------- |
1952 | | |
1953 | | //! @cond Doxygen_Suppress |
1954 | | static const std::string nullString; |
1955 | | |
1956 | | static const std::string &_getNTv1Filename(const SingleOperation *op, |
1957 | 0 | bool allowInverse) { |
1958 | |
|
1959 | 0 | const auto &l_method = op->method(); |
1960 | 0 | const auto &methodName = l_method->nameStr(); |
1961 | 0 | if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV1 || |
1962 | 0 | (allowInverse && |
1963 | 0 | ci_equal(methodName, INVERSE_OF + EPSG_NAME_METHOD_NTV1))) { |
1964 | 0 | const auto &fileParameter = op->parameterValue( |
1965 | 0 | EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, |
1966 | 0 | EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); |
1967 | 0 | if (fileParameter && |
1968 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
1969 | 0 | return fileParameter->valueFile(); |
1970 | 0 | } |
1971 | 0 | } |
1972 | 0 | return nullString; |
1973 | 0 | } |
1974 | | |
1975 | | // |
1976 | | static const std::string &_getNTv2Filename(const SingleOperation *op, |
1977 | 0 | bool allowInverse) { |
1978 | |
|
1979 | 0 | const auto &l_method = op->method(); |
1980 | 0 | if (l_method->getEPSGCode() == EPSG_CODE_METHOD_NTV2 || |
1981 | 0 | (allowInverse && |
1982 | 0 | ci_equal(l_method->nameStr(), INVERSE_OF + EPSG_NAME_METHOD_NTV2))) { |
1983 | 0 | const auto &fileParameter = op->parameterValue( |
1984 | 0 | EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, |
1985 | 0 | EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); |
1986 | 0 | if (fileParameter && |
1987 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
1988 | 0 | return fileParameter->valueFile(); |
1989 | 0 | } |
1990 | 0 | } |
1991 | 0 | return nullString; |
1992 | 0 | } |
1993 | | |
1994 | | //! @endcond |
1995 | | |
1996 | | // --------------------------------------------------------------------------- |
1997 | | //! @cond Doxygen_Suppress |
1998 | 0 | const std::string &Transformation::getPROJ4NadgridsCompatibleFilename() const { |
1999 | |
|
2000 | 0 | const std::string &filename = _getNTv2Filename(this, false); |
2001 | 0 | if (!filename.empty()) { |
2002 | 0 | return filename; |
2003 | 0 | } |
2004 | | |
2005 | 0 | if (method()->getEPSGCode() == EPSG_CODE_METHOD_NADCON) { |
2006 | 0 | const auto &latitudeFileParameter = |
2007 | 0 | parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, |
2008 | 0 | EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE); |
2009 | 0 | const auto &longitudeFileParameter = |
2010 | 0 | parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, |
2011 | 0 | EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE); |
2012 | 0 | if (latitudeFileParameter && |
2013 | 0 | latitudeFileParameter->type() == ParameterValue::Type::FILENAME && |
2014 | 0 | longitudeFileParameter && |
2015 | 0 | longitudeFileParameter->type() == ParameterValue::Type::FILENAME) { |
2016 | 0 | return latitudeFileParameter->valueFile(); |
2017 | 0 | } |
2018 | 0 | } |
2019 | | |
2020 | 0 | if (ci_equal(method()->nameStr(), |
2021 | 0 | PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF)) { |
2022 | 0 | const auto &fileParameter = parameterValue( |
2023 | 0 | EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, |
2024 | 0 | EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); |
2025 | 0 | if (fileParameter && |
2026 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2027 | 0 | return fileParameter->valueFile(); |
2028 | 0 | } |
2029 | 0 | } |
2030 | | |
2031 | 0 | return nullString; |
2032 | 0 | } |
2033 | | //! @endcond |
2034 | | |
2035 | | // --------------------------------------------------------------------------- |
2036 | | |
2037 | | //! @cond Doxygen_Suppress |
2038 | | static const std::string &_getCTABLE2Filename(const SingleOperation *op, |
2039 | 0 | bool allowInverse) { |
2040 | 0 | const auto &l_method = op->method(); |
2041 | 0 | const auto &methodName = l_method->nameStr(); |
2042 | 0 | if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_CTABLE2) || |
2043 | 0 | (allowInverse && |
2044 | 0 | ci_equal(methodName, INVERSE_OF + PROJ_WKT2_NAME_METHOD_CTABLE2))) { |
2045 | 0 | const auto &fileParameter = op->parameterValue( |
2046 | 0 | EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, |
2047 | 0 | EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); |
2048 | 0 | if (fileParameter && |
2049 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2050 | 0 | return fileParameter->valueFile(); |
2051 | 0 | } |
2052 | 0 | } |
2053 | 0 | return nullString; |
2054 | 0 | } |
2055 | | //! @endcond |
2056 | | |
2057 | | // --------------------------------------------------------------------------- |
2058 | | |
2059 | | //! @cond Doxygen_Suppress |
2060 | | static const std::string & |
2061 | 0 | _getHorizontalShiftGTIFFFilename(const SingleOperation *op, bool allowInverse) { |
2062 | 0 | const auto &l_method = op->method(); |
2063 | 0 | const auto &methodName = l_method->nameStr(); |
2064 | 0 | if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF) || |
2065 | 0 | ci_equal(methodName, PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF) || |
2066 | 0 | (allowInverse && |
2067 | 0 | ci_equal(methodName, |
2068 | 0 | INVERSE_OF + PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF)) || |
2069 | 0 | (allowInverse && |
2070 | 0 | ci_equal(methodName, |
2071 | 0 | INVERSE_OF + PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF))) { |
2072 | 0 | { |
2073 | 0 | const auto &fileParameter = op->parameterValue( |
2074 | 0 | EPSG_NAME_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE, |
2075 | 0 | EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE); |
2076 | 0 | if (fileParameter && |
2077 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2078 | 0 | return fileParameter->valueFile(); |
2079 | 0 | } |
2080 | 0 | } |
2081 | 0 | { |
2082 | 0 | const auto &fileParameter = op->parameterValue( |
2083 | 0 | PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE, |
2084 | 0 | 0); |
2085 | 0 | if (fileParameter && |
2086 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2087 | 0 | return fileParameter->valueFile(); |
2088 | 0 | } |
2089 | 0 | } |
2090 | 0 | } |
2091 | 0 | return nullString; |
2092 | 0 | } |
2093 | | //! @endcond |
2094 | | |
2095 | | // --------------------------------------------------------------------------- |
2096 | | |
2097 | | //! @cond Doxygen_Suppress |
2098 | | static const std::string & |
2099 | | _getGeocentricTranslationFilename(const SingleOperation *op, |
2100 | 0 | bool allowInverse) { |
2101 | |
|
2102 | 0 | const auto &l_method = op->method(); |
2103 | 0 | const auto &methodName = l_method->nameStr(); |
2104 | 0 | if (l_method->getEPSGCode() == |
2105 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN || |
2106 | 0 | (allowInverse && |
2107 | 0 | ci_equal( |
2108 | 0 | methodName, |
2109 | 0 | INVERSE_OF + |
2110 | 0 | EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN))) { |
2111 | 0 | const auto &fileParameter = |
2112 | 0 | op->parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, |
2113 | 0 | EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE); |
2114 | 0 | if (fileParameter && |
2115 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2116 | 0 | return fileParameter->valueFile(); |
2117 | 0 | } |
2118 | 0 | } |
2119 | 0 | return nullString; |
2120 | 0 | } |
2121 | | //! @endcond |
2122 | | |
2123 | | // --------------------------------------------------------------------------- |
2124 | | |
2125 | | //! @cond Doxygen_Suppress |
2126 | | static const std::string & |
2127 | | _getGeographic3DOffsetByVelocityGridFilename(const SingleOperation *op, |
2128 | 0 | bool allowInverse) { |
2129 | |
|
2130 | 0 | const auto &l_method = op->method(); |
2131 | 0 | const auto &methodName = l_method->nameStr(); |
2132 | 0 | if (l_method->getEPSGCode() == |
2133 | 0 | EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL || |
2134 | 0 | (allowInverse && |
2135 | 0 | ci_equal( |
2136 | 0 | methodName, |
2137 | 0 | INVERSE_OF + |
2138 | 0 | EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL))) { |
2139 | 0 | const auto &fileParameter = op->parameterValue( |
2140 | 0 | EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
2141 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); |
2142 | 0 | if (fileParameter && |
2143 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2144 | 0 | return fileParameter->valueFile(); |
2145 | 0 | } |
2146 | 0 | } |
2147 | 0 | return nullString; |
2148 | 0 | } |
2149 | | //! @endcond |
2150 | | |
2151 | | // --------------------------------------------------------------------------- |
2152 | | |
2153 | | //! @cond Doxygen_Suppress |
2154 | | static const std::string & |
2155 | | _getVerticalOffsetByVelocityGridFilename(const SingleOperation *op, |
2156 | 0 | bool allowInverse) { |
2157 | |
|
2158 | 0 | const auto &l_method = op->method(); |
2159 | 0 | const auto &methodName = l_method->nameStr(); |
2160 | 0 | if (l_method->getEPSGCode() == |
2161 | 0 | EPSG_CODE_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL || |
2162 | 0 | (allowInverse && |
2163 | 0 | ci_equal( |
2164 | 0 | methodName, |
2165 | 0 | INVERSE_OF + |
2166 | 0 | EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL))) { |
2167 | 0 | const auto &fileParameter = op->parameterValue( |
2168 | 0 | EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
2169 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); |
2170 | 0 | if (fileParameter && |
2171 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2172 | 0 | return fileParameter->valueFile(); |
2173 | 0 | } |
2174 | 0 | } |
2175 | 0 | return nullString; |
2176 | 0 | } |
2177 | | //! @endcond |
2178 | | |
2179 | | // --------------------------------------------------------------------------- |
2180 | | |
2181 | | //! @cond Doxygen_Suppress |
2182 | | static const std::string & |
2183 | 0 | _getHeightToGeographic3DFilename(const SingleOperation *op, bool allowInverse) { |
2184 | |
|
2185 | 0 | const auto &methodName = op->method()->nameStr(); |
2186 | |
|
2187 | 0 | if (ci_equal(methodName, PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D) || |
2188 | 0 | (allowInverse && |
2189 | 0 | ci_equal(methodName, |
2190 | 0 | INVERSE_OF + PROJ_WKT2_NAME_METHOD_HEIGHT_TO_GEOG3D))) { |
2191 | 0 | const auto &fileParameter = |
2192 | 0 | op->parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, |
2193 | 0 | EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); |
2194 | 0 | if (fileParameter && |
2195 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2196 | 0 | return fileParameter->valueFile(); |
2197 | 0 | } |
2198 | 0 | } |
2199 | 0 | return nullString; |
2200 | 0 | } |
2201 | | //! @endcond |
2202 | | |
2203 | | // --------------------------------------------------------------------------- |
2204 | | |
2205 | | //! @cond Doxygen_Suppress |
2206 | | bool Transformation::isGeographic3DToGravityRelatedHeight( |
2207 | 0 | const OperationMethodNNPtr &method, bool allowInverse) { |
2208 | 0 | const auto &methodName = method->nameStr(); |
2209 | 0 | static const char *const methodCodes[] = { |
2210 | 0 | "1025", // Geographic3D to GravityRelatedHeight (EGM2008) |
2211 | 0 | "1030", // Geographic3D to GravityRelatedHeight (NZgeoid) |
2212 | 0 | "1045", // Geographic3D to GravityRelatedHeight (OSGM02-Ire) |
2213 | 0 | "1047", // Geographic3D to GravityRelatedHeight (Gravsoft) |
2214 | 0 | "1048", // Geographic3D to GravityRelatedHeight (Ausgeoid v2) |
2215 | 0 | "1050", // Geographic3D to GravityRelatedHeight (CI) |
2216 | 0 | "1059", // Geographic3D to GravityRelatedHeight (PNG) |
2217 | 0 | "1088", // Geog3D to Geog2D+GravityRelatedHeight (gtx) |
2218 | 0 | "1060", // Geographic3D to GravityRelatedHeight (CGG2013) |
2219 | 0 | "1072", // Geographic3D to GravityRelatedHeight (OSGM15-Ire) |
2220 | 0 | "1073", // Geographic3D to GravityRelatedHeight (IGN2009) |
2221 | 0 | "1081", // Geographic3D to GravityRelatedHeight (BEV AT) |
2222 | 0 | "1083", // Geog3D to Geog2D+Vertical (AUSGeoid v2) |
2223 | 0 | "1089", // Geog3D to Geog2D+GravityRelatedHeight (BEV AT) |
2224 | 0 | "1090", // Geog3D to Geog2D+GravityRelatedHeight (CGG 2013) |
2225 | 0 | "1091", // Geog3D to Geog2D+GravityRelatedHeight (CI) |
2226 | 0 | "1092", // Geog3D to Geog2D+GravityRelatedHeight (EGM2008) |
2227 | 0 | "1093", // Geog3D to Geog2D+GravityRelatedHeight (Gravsoft) |
2228 | 0 | "1094", // Geog3D to Geog2D+GravityRelatedHeight (IGN1997) |
2229 | 0 | "1095", // Geog3D to Geog2D+GravityRelatedHeight (IGN2009) |
2230 | 0 | "1096", // Geog3D to Geog2D+GravityRelatedHeight (OSGM15-Ire) |
2231 | 0 | "1097", // Geog3D to Geog2D+GravityRelatedHeight (OSGM-GB) |
2232 | 0 | "1098", // Geog3D to Geog2D+GravityRelatedHeight (SA 2010) |
2233 | 0 | "1100", // Geog3D to Geog2D+GravityRelatedHeight (PL txt) |
2234 | 0 | "1103", // Geog3D to Geog2D+GravityRelatedHeight (EGM) |
2235 | 0 | "1105", // Geog3D to Geog2D+GravityRelatedHeight (ITAL2005) |
2236 | 0 | "1109", // Geographic3D to Depth (Gravsoft) |
2237 | 0 | "1110", // Geog3D to Geog2D+Depth (Gravsoft) |
2238 | 0 | "1115", // Geog3D to Geog2D+Depth (txt) |
2239 | 0 | "1118", // Geog3D to Geog2D+GravityRelatedHeight (ISG) |
2240 | 0 | "1122", // Geog3D to Geog2D+Depth (gtx) |
2241 | 0 | "1124", // Geog3D to Geog2D+GravityRelatedHeight (gtg) |
2242 | 0 | "1126", // Vertical change by geoid grid difference (NRCan) |
2243 | 0 | "1127", // Geographic3D to Depth (gtg) |
2244 | 0 | "1128", // Geog3D to Geog2D+Depth (gtg) |
2245 | 0 | "1135", // Geog3D to Geog2D+GravityRelatedHeight (NGS bin) |
2246 | 0 | "9661", // Geographic3D to GravityRelatedHeight (EGM) |
2247 | 0 | "9662", // Geographic3D to GravityRelatedHeight (Ausgeoid98) |
2248 | 0 | "9663", // Geographic3D to GravityRelatedHeight (OSGM-GB) |
2249 | 0 | "9664", // Geographic3D to GravityRelatedHeight (IGN1997) |
2250 | 0 | "9665", // Geographic3D to GravityRelatedHeight (US .gtx) |
2251 | 0 | "9635", // Geog3D to Geog2D+GravityRelatedHeight (US .gtx) |
2252 | 0 | }; |
2253 | |
|
2254 | 0 | if (ci_find(methodName, "Geographic3D to GravityRelatedHeight") == 0) { |
2255 | 0 | return true; |
2256 | 0 | } |
2257 | 0 | if (allowInverse && |
2258 | 0 | ci_find(methodName, |
2259 | 0 | INVERSE_OF + "Geographic3D to GravityRelatedHeight") == 0) { |
2260 | 0 | return true; |
2261 | 0 | } |
2262 | | |
2263 | 0 | for (const auto &code : methodCodes) { |
2264 | 0 | for (const auto &idSrc : method->identifiers()) { |
2265 | 0 | const auto &srcAuthName = *(idSrc->codeSpace()); |
2266 | 0 | const auto &srcCode = idSrc->code(); |
2267 | 0 | if (ci_equal(srcAuthName, "EPSG") && srcCode == code) { |
2268 | 0 | return true; |
2269 | 0 | } |
2270 | 0 | if (allowInverse && ci_equal(srcAuthName, "INVERSE(EPSG)") && |
2271 | 0 | srcCode == code) { |
2272 | 0 | return true; |
2273 | 0 | } |
2274 | 0 | } |
2275 | 0 | } |
2276 | 0 | return false; |
2277 | 0 | } |
2278 | | //! @endcond |
2279 | | |
2280 | | // --------------------------------------------------------------------------- |
2281 | | |
2282 | | //! @cond Doxygen_Suppress |
2283 | 0 | const std::string &Transformation::getHeightToGeographic3DFilename() const { |
2284 | |
|
2285 | 0 | const std::string &ret = _getHeightToGeographic3DFilename(this, false); |
2286 | 0 | if (!ret.empty()) |
2287 | 0 | return ret; |
2288 | 0 | if (isGeographic3DToGravityRelatedHeight(method(), false)) { |
2289 | 0 | const auto &fileParameter = |
2290 | 0 | parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, |
2291 | 0 | EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); |
2292 | 0 | if (fileParameter && |
2293 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2294 | 0 | return fileParameter->valueFile(); |
2295 | 0 | } |
2296 | 0 | } |
2297 | 0 | return nullString; |
2298 | 0 | } |
2299 | | //! @endcond |
2300 | | |
2301 | | // --------------------------------------------------------------------------- |
2302 | | |
2303 | | //! @cond Doxygen_Suppress |
2304 | | static util::PropertyMap |
2305 | 0 | createSimilarPropertiesOperation(const CoordinateOperationNNPtr &obj) { |
2306 | 0 | util::PropertyMap map; |
2307 | | |
2308 | | // The domain(s) are unchanged |
2309 | 0 | addDomains(map, obj.get()); |
2310 | |
|
2311 | 0 | const std::string &forwardName = obj->nameStr(); |
2312 | 0 | if (!forwardName.empty()) { |
2313 | 0 | map.set(common::IdentifiedObject::NAME_KEY, forwardName); |
2314 | 0 | } |
2315 | |
|
2316 | 0 | const std::string &remarks = obj->remarks(); |
2317 | 0 | if (!remarks.empty()) { |
2318 | 0 | map.set(common::IdentifiedObject::REMARKS_KEY, remarks); |
2319 | 0 | } |
2320 | |
|
2321 | 0 | addModifiedIdentifier(map, obj.get(), false, true); |
2322 | |
|
2323 | 0 | return map; |
2324 | 0 | } |
2325 | | //! @endcond |
2326 | | |
2327 | | // --------------------------------------------------------------------------- |
2328 | | |
2329 | | //! @cond Doxygen_Suppress |
2330 | | static TransformationNNPtr |
2331 | | createNTv1(const util::PropertyMap &properties, |
2332 | | const crs::CRSNNPtr &sourceCRSIn, const crs::CRSNNPtr &targetCRSIn, |
2333 | | const std::string &filename, |
2334 | 0 | const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { |
2335 | 0 | const VectorOfParameters parameters{createOpParamNameEPSGCode( |
2336 | 0 | EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; |
2337 | 0 | const VectorOfValues values{ParameterValue::createFilename(filename)}; |
2338 | 0 | return Transformation::create( |
2339 | 0 | properties, sourceCRSIn, targetCRSIn, nullptr, |
2340 | 0 | createMethodMapNameEPSGCode(EPSG_CODE_METHOD_NTV1), parameters, values, |
2341 | 0 | accuracies); |
2342 | 0 | } |
2343 | | //! @endcond |
2344 | | |
2345 | | // --------------------------------------------------------------------------- |
2346 | | |
2347 | | //! @cond Doxygen_Suppress |
2348 | | static util::PropertyMap |
2349 | 0 | createSimilarPropertiesMethod(common::IdentifiedObjectNNPtr obj) { |
2350 | 0 | util::PropertyMap map; |
2351 | |
|
2352 | 0 | const std::string &forwardName = obj->nameStr(); |
2353 | 0 | if (!forwardName.empty()) { |
2354 | 0 | map.set(common::IdentifiedObject::NAME_KEY, forwardName); |
2355 | 0 | } |
2356 | |
|
2357 | 0 | { |
2358 | 0 | auto ar = util::ArrayOfBaseObject::create(); |
2359 | 0 | for (const auto &idSrc : obj->identifiers()) { |
2360 | 0 | const auto &srcAuthName = *(idSrc->codeSpace()); |
2361 | 0 | const auto &srcCode = idSrc->code(); |
2362 | 0 | auto idsProp = util::PropertyMap().set( |
2363 | 0 | metadata::Identifier::CODESPACE_KEY, srcAuthName); |
2364 | 0 | ar->add(metadata::Identifier::create(srcCode, idsProp)); |
2365 | 0 | } |
2366 | 0 | if (!ar->empty()) { |
2367 | 0 | map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); |
2368 | 0 | } |
2369 | 0 | } |
2370 | |
|
2371 | 0 | return map; |
2372 | 0 | } |
2373 | | //! @endcond |
2374 | | |
2375 | | // --------------------------------------------------------------------------- |
2376 | | |
2377 | | static bool isRegularVerticalGridMethod(int methodEPSGCode, |
2378 | 0 | bool &reverseOffsetSign) { |
2379 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NRCAN_BYN || |
2380 | 0 | methodEPSGCode == |
2381 | 0 | EPSG_CODE_METHOD_VERTICALCHANGE_BY_GEOID_GRID_DIFFERENCE_NRCAN) { |
2382 | | // NRCAN vertical shift grids use a reverse convention from other |
2383 | | // grids: the value in the grid is the value to subtract from the |
2384 | | // source vertical CRS to get the target value. |
2385 | 0 | reverseOffsetSign = true; |
2386 | 0 | return true; |
2387 | 0 | } |
2388 | 0 | reverseOffsetSign = false; |
2389 | 0 | return methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_NZLVD || |
2390 | 0 | methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_BEV_AT || |
2391 | 0 | methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTX || |
2392 | 0 | methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_ASC || |
2393 | 0 | methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_GTG || |
2394 | 0 | methodEPSGCode == EPSG_CODE_METHOD_VERTICALGRID_PL_TXT; |
2395 | 0 | } |
2396 | | |
2397 | | // --------------------------------------------------------------------------- |
2398 | | |
2399 | | /** \brief Return an equivalent transformation to the current one, but using |
2400 | | * PROJ alternative grid names. |
2401 | | */ |
2402 | | TransformationNNPtr SingleOperation::substitutePROJAlternativeGridNames( |
2403 | 0 | io::DatabaseContextNNPtr databaseContext) const { |
2404 | 0 | auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>( |
2405 | 0 | shared_from_this().as_nullable())); |
2406 | |
|
2407 | 0 | const auto &l_method = method(); |
2408 | 0 | const int methodEPSGCode = l_method->getEPSGCode(); |
2409 | |
|
2410 | 0 | std::string projFilename; |
2411 | 0 | std::string projGridFormat; |
2412 | 0 | bool inverseDirection = false; |
2413 | |
|
2414 | 0 | const auto &NTv1Filename = _getNTv1Filename(this, false); |
2415 | 0 | const auto &NTv2Filename = _getNTv2Filename(this, false); |
2416 | 0 | std::string lasFilename; |
2417 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_NADCON || |
2418 | 0 | methodEPSGCode == EPSG_CODE_METHOD_NADCON5_2D || |
2419 | 0 | methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D) { |
2420 | 0 | const auto &latitudeFileParameter = |
2421 | 0 | parameterValue(EPSG_NAME_PARAMETER_LATITUDE_DIFFERENCE_FILE, |
2422 | 0 | EPSG_CODE_PARAMETER_LATITUDE_DIFFERENCE_FILE); |
2423 | 0 | const auto &longitudeFileParameter = |
2424 | 0 | parameterValue(EPSG_NAME_PARAMETER_LONGITUDE_DIFFERENCE_FILE, |
2425 | 0 | EPSG_CODE_PARAMETER_LONGITUDE_DIFFERENCE_FILE); |
2426 | 0 | if (latitudeFileParameter && |
2427 | 0 | latitudeFileParameter->type() == ParameterValue::Type::FILENAME && |
2428 | 0 | longitudeFileParameter && |
2429 | 0 | longitudeFileParameter->type() == ParameterValue::Type::FILENAME) { |
2430 | 0 | lasFilename = latitudeFileParameter->valueFile(); |
2431 | 0 | } |
2432 | 0 | } |
2433 | 0 | const auto &horizontalGridName = !NTv1Filename.empty() ? NTv1Filename |
2434 | 0 | : !NTv2Filename.empty() ? NTv2Filename |
2435 | 0 | : lasFilename; |
2436 | 0 | const auto l_interpolationCRS = interpolationCRS(); |
2437 | |
|
2438 | 0 | if (!horizontalGridName.empty() && databaseContext->lookForGridAlternative( |
2439 | 0 | horizontalGridName, projFilename, |
2440 | 0 | projGridFormat, inverseDirection)) { |
2441 | |
|
2442 | 0 | if (horizontalGridName == projFilename) { |
2443 | 0 | if (inverseDirection) { |
2444 | 0 | throw util::UnsupportedOperationException( |
2445 | 0 | "Inverse direction for " + projFilename + " not supported"); |
2446 | 0 | } |
2447 | 0 | return self; |
2448 | 0 | } |
2449 | | |
2450 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2451 | 0 | const auto l_targetCRSNull = targetCRS(); |
2452 | 0 | if (l_sourceCRSNull == nullptr) { |
2453 | 0 | throw util::UnsupportedOperationException("Missing sourceCRS"); |
2454 | 0 | } |
2455 | 0 | if (l_targetCRSNull == nullptr) { |
2456 | 0 | throw util::UnsupportedOperationException("Missing targetCRS"); |
2457 | 0 | } |
2458 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2459 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2460 | 0 | const auto &l_accuracies = coordinateOperationAccuracies(); |
2461 | 0 | if (projGridFormat == "GTiff") { |
2462 | 0 | const VectorOfParameters parameters{ |
2463 | 0 | methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D |
2464 | 0 | ? OperationParameter::create(util::PropertyMap().set( |
2465 | 0 | common::IdentifiedObject::NAME_KEY, |
2466 | 0 | PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE)) |
2467 | 0 | : createOpParamNameEPSGCode( |
2468 | 0 | EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; |
2469 | 0 | auto methodProperties = util::PropertyMap().set( |
2470 | 0 | common::IdentifiedObject::NAME_KEY, |
2471 | 0 | (methodEPSGCode == EPSG_CODE_METHOD_NADCON5_2D || |
2472 | 0 | methodEPSGCode == EPSG_CODE_METHOD_NADCON5_3D) |
2473 | 0 | ? PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF |
2474 | 0 | : PROJ_WKT2_NAME_METHOD_HORIZONTAL_SHIFT_GTIFF); |
2475 | 0 | const VectorOfValues values{ |
2476 | 0 | ParameterValue::createFilename(projFilename)}; |
2477 | 0 | if (inverseDirection) { |
2478 | 0 | return Transformation::create( |
2479 | 0 | createPropertiesForInverse(self.as_nullable().get(), |
2480 | 0 | true, false), |
2481 | 0 | l_targetCRS, l_sourceCRS, l_interpolationCRS, |
2482 | 0 | methodProperties, parameters, values, l_accuracies) |
2483 | 0 | ->inverseAsTransformation(); |
2484 | |
|
2485 | 0 | } else { |
2486 | 0 | return Transformation::create( |
2487 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2488 | 0 | l_targetCRS, l_interpolationCRS, methodProperties, |
2489 | 0 | parameters, values, l_accuracies); |
2490 | 0 | } |
2491 | 0 | } else if (projGridFormat == "NTv1") { |
2492 | 0 | if (inverseDirection) { |
2493 | 0 | return createNTv1(createPropertiesForInverse( |
2494 | 0 | self.as_nullable().get(), true, false), |
2495 | 0 | l_targetCRS, l_sourceCRS, projFilename, |
2496 | 0 | l_accuracies) |
2497 | 0 | ->inverseAsTransformation(); |
2498 | 0 | } else { |
2499 | 0 | return createNTv1(createSimilarPropertiesOperation(self), |
2500 | 0 | l_sourceCRS, l_targetCRS, projFilename, |
2501 | 0 | l_accuracies); |
2502 | 0 | } |
2503 | 0 | } else if (projGridFormat == "NTv2") { |
2504 | 0 | if (inverseDirection) { |
2505 | 0 | return Transformation::createNTv2( |
2506 | 0 | createPropertiesForInverse(self.as_nullable().get(), |
2507 | 0 | true, false), |
2508 | 0 | l_targetCRS, l_sourceCRS, projFilename, l_accuracies) |
2509 | 0 | ->inverseAsTransformation(); |
2510 | 0 | } else { |
2511 | 0 | return Transformation::createNTv2( |
2512 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2513 | 0 | l_targetCRS, projFilename, l_accuracies); |
2514 | 0 | } |
2515 | 0 | } else if (projGridFormat == "CTable2") { |
2516 | 0 | const VectorOfParameters parameters{createOpParamNameEPSGCode( |
2517 | 0 | EPSG_CODE_PARAMETER_LATITUDE_LONGITUDE_DIFFERENCE_FILE)}; |
2518 | 0 | auto methodProperties = |
2519 | 0 | util::PropertyMap().set(common::IdentifiedObject::NAME_KEY, |
2520 | 0 | PROJ_WKT2_NAME_METHOD_CTABLE2); |
2521 | 0 | const VectorOfValues values{ |
2522 | 0 | ParameterValue::createFilename(projFilename)}; |
2523 | 0 | if (inverseDirection) { |
2524 | 0 | return Transformation::create( |
2525 | 0 | createPropertiesForInverse(self.as_nullable().get(), |
2526 | 0 | true, false), |
2527 | 0 | l_targetCRS, l_sourceCRS, l_interpolationCRS, |
2528 | 0 | methodProperties, parameters, values, l_accuracies) |
2529 | 0 | ->inverseAsTransformation(); |
2530 | |
|
2531 | 0 | } else { |
2532 | 0 | return Transformation::create( |
2533 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2534 | 0 | l_targetCRS, l_interpolationCRS, methodProperties, |
2535 | 0 | parameters, values, l_accuracies); |
2536 | 0 | } |
2537 | 0 | } |
2538 | 0 | } |
2539 | | |
2540 | 0 | if (Transformation::isGeographic3DToGravityRelatedHeight(method(), false)) { |
2541 | 0 | const auto &fileParameter = |
2542 | 0 | parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, |
2543 | 0 | EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); |
2544 | 0 | if (fileParameter && |
2545 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2546 | 0 | const auto &filename = fileParameter->valueFile(); |
2547 | 0 | if (databaseContext->lookForGridAlternative( |
2548 | 0 | filename, projFilename, projGridFormat, inverseDirection)) { |
2549 | |
|
2550 | 0 | if (inverseDirection) { |
2551 | 0 | throw util::UnsupportedOperationException( |
2552 | 0 | "Inverse direction for " |
2553 | 0 | "Geographic3DToGravityRelatedHeight not supported"); |
2554 | 0 | } |
2555 | | |
2556 | 0 | if (filename == projFilename) { |
2557 | 0 | return self; |
2558 | 0 | } |
2559 | | |
2560 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2561 | 0 | const auto l_targetCRSNull = targetCRS(); |
2562 | 0 | if (l_sourceCRSNull == nullptr) { |
2563 | 0 | throw util::UnsupportedOperationException( |
2564 | 0 | "Missing sourceCRS"); |
2565 | 0 | } |
2566 | 0 | if (l_targetCRSNull == nullptr) { |
2567 | 0 | throw util::UnsupportedOperationException( |
2568 | 0 | "Missing targetCRS"); |
2569 | 0 | } |
2570 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2571 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2572 | 0 | const VectorOfParameters parameters{createOpParamNameEPSGCode( |
2573 | 0 | EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME)}; |
2574 | 0 | const VectorOfValues values{ |
2575 | 0 | ParameterValue::createFilename(projFilename)}; |
2576 | | #ifdef disabled_for_now |
2577 | | if (inverseDirection) { |
2578 | | return Transformation::create( |
2579 | | createPropertiesForInverse( |
2580 | | self.as_nullable().get(), true, false), |
2581 | | l_targetCRS, l_sourceCRS, l_interpolationCRS, |
2582 | | createSimilarPropertiesMethod(method()), |
2583 | | parameters, values, |
2584 | | coordinateOperationAccuracies()) |
2585 | | ->inverseAsTransformation(); |
2586 | | } else |
2587 | | #endif |
2588 | 0 | { |
2589 | 0 | return Transformation::create( |
2590 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2591 | 0 | l_targetCRS, l_interpolationCRS, |
2592 | 0 | createSimilarPropertiesMethod(method()), parameters, |
2593 | 0 | values, coordinateOperationAccuracies()); |
2594 | 0 | } |
2595 | 0 | } |
2596 | 0 | } |
2597 | 0 | } |
2598 | | |
2599 | 0 | const auto &geocentricTranslationFilename = |
2600 | 0 | _getGeocentricTranslationFilename(this, false); |
2601 | 0 | if (!geocentricTranslationFilename.empty()) { |
2602 | 0 | if (databaseContext->lookForGridAlternative( |
2603 | 0 | geocentricTranslationFilename, projFilename, projGridFormat, |
2604 | 0 | inverseDirection)) { |
2605 | |
|
2606 | 0 | if (inverseDirection) { |
2607 | 0 | throw util::UnsupportedOperationException( |
2608 | 0 | "Inverse direction for " |
2609 | 0 | "GeocentricTranslation not supported"); |
2610 | 0 | } |
2611 | | |
2612 | 0 | if (geocentricTranslationFilename == projFilename) { |
2613 | 0 | return self; |
2614 | 0 | } |
2615 | | |
2616 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2617 | 0 | const auto l_targetCRSNull = targetCRS(); |
2618 | 0 | if (l_sourceCRSNull == nullptr) { |
2619 | 0 | throw util::UnsupportedOperationException("Missing sourceCRS"); |
2620 | 0 | } |
2621 | 0 | if (l_targetCRSNull == nullptr) { |
2622 | 0 | throw util::UnsupportedOperationException("Missing targetCRS"); |
2623 | 0 | } |
2624 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2625 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2626 | 0 | const VectorOfParameters parameters{createOpParamNameEPSGCode( |
2627 | 0 | EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE)}; |
2628 | 0 | const VectorOfValues values{ |
2629 | 0 | ParameterValue::createFilename(projFilename)}; |
2630 | 0 | return Transformation::create( |
2631 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2632 | 0 | l_targetCRS, l_interpolationCRS, |
2633 | 0 | createSimilarPropertiesMethod(method()), parameters, values, |
2634 | 0 | coordinateOperationAccuracies()); |
2635 | 0 | } |
2636 | 0 | } |
2637 | | |
2638 | 0 | const auto &geographic3DOffsetByVelocityGridFilename = |
2639 | 0 | _getGeographic3DOffsetByVelocityGridFilename(this, false); |
2640 | 0 | if (!geographic3DOffsetByVelocityGridFilename.empty()) { |
2641 | 0 | if (databaseContext->lookForGridAlternative( |
2642 | 0 | geographic3DOffsetByVelocityGridFilename, projFilename, |
2643 | 0 | projGridFormat, inverseDirection)) { |
2644 | |
|
2645 | 0 | if (inverseDirection) { |
2646 | 0 | throw util::UnsupportedOperationException( |
2647 | 0 | "Inverse direction for " |
2648 | 0 | "Geographic3DOFffsetByVelocityGrid not supported"); |
2649 | 0 | } |
2650 | | |
2651 | 0 | if (geographic3DOffsetByVelocityGridFilename == projFilename) { |
2652 | 0 | return self; |
2653 | 0 | } |
2654 | | |
2655 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2656 | 0 | const auto l_targetCRSNull = targetCRS(); |
2657 | 0 | if (l_sourceCRSNull == nullptr) { |
2658 | 0 | throw util::UnsupportedOperationException("Missing sourceCRS"); |
2659 | 0 | } |
2660 | 0 | if (l_targetCRSNull == nullptr) { |
2661 | 0 | throw util::UnsupportedOperationException("Missing targetCRS"); |
2662 | 0 | } |
2663 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2664 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2665 | 0 | const VectorOfParameters parameters{createOpParamNameEPSGCode( |
2666 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE)}; |
2667 | 0 | const VectorOfValues values{ |
2668 | 0 | ParameterValue::createFilename(projFilename)}; |
2669 | 0 | return Transformation::create( |
2670 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2671 | 0 | l_targetCRS, l_interpolationCRS, |
2672 | 0 | createSimilarPropertiesMethod(method()), parameters, values, |
2673 | 0 | coordinateOperationAccuracies()); |
2674 | 0 | } |
2675 | 0 | } |
2676 | | |
2677 | 0 | const auto &verticalOffsetByVelocityGridFilename = |
2678 | 0 | _getVerticalOffsetByVelocityGridFilename(this, false); |
2679 | 0 | if (!verticalOffsetByVelocityGridFilename.empty()) { |
2680 | 0 | if (databaseContext->lookForGridAlternative( |
2681 | 0 | verticalOffsetByVelocityGridFilename, projFilename, |
2682 | 0 | projGridFormat, inverseDirection)) { |
2683 | |
|
2684 | 0 | if (inverseDirection) { |
2685 | 0 | throw util::UnsupportedOperationException( |
2686 | 0 | "Inverse direction for " |
2687 | 0 | "VerticalOffsetByVelocityGrid not supported"); |
2688 | 0 | } |
2689 | | |
2690 | 0 | if (verticalOffsetByVelocityGridFilename == projFilename) { |
2691 | 0 | return self; |
2692 | 0 | } |
2693 | | |
2694 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2695 | 0 | const auto l_targetCRSNull = targetCRS(); |
2696 | 0 | if (l_sourceCRSNull == nullptr) { |
2697 | 0 | throw util::UnsupportedOperationException("Missing sourceCRS"); |
2698 | 0 | } |
2699 | 0 | if (l_targetCRSNull == nullptr) { |
2700 | 0 | throw util::UnsupportedOperationException("Missing targetCRS"); |
2701 | 0 | } |
2702 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2703 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2704 | 0 | const VectorOfParameters parameters{createOpParamNameEPSGCode( |
2705 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE)}; |
2706 | 0 | const VectorOfValues values{ |
2707 | 0 | ParameterValue::createFilename(projFilename)}; |
2708 | 0 | return Transformation::create( |
2709 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2710 | 0 | l_targetCRS, l_interpolationCRS, |
2711 | 0 | createSimilarPropertiesMethod(method()), parameters, values, |
2712 | 0 | coordinateOperationAccuracies()); |
2713 | 0 | } |
2714 | 0 | } |
2715 | | |
2716 | 0 | bool reverseOffsetSign = false; |
2717 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON || |
2718 | 0 | isRegularVerticalGridMethod(methodEPSGCode, reverseOffsetSign)) { |
2719 | 0 | int parameterCode = EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE; |
2720 | 0 | auto fileParameter = parameterValue( |
2721 | 0 | EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, parameterCode); |
2722 | 0 | if (!fileParameter) { |
2723 | 0 | parameterCode = EPSG_CODE_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE; |
2724 | 0 | fileParameter = parameterValue( |
2725 | 0 | EPSG_NAME_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE, parameterCode); |
2726 | 0 | } |
2727 | 0 | if (fileParameter && |
2728 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2729 | |
|
2730 | 0 | const auto &filename = fileParameter->valueFile(); |
2731 | 0 | if (databaseContext->lookForGridAlternative( |
2732 | 0 | filename, projFilename, projGridFormat, inverseDirection)) { |
2733 | |
|
2734 | 0 | if (filename == projFilename) { |
2735 | 0 | if (inverseDirection) { |
2736 | 0 | throw util::UnsupportedOperationException( |
2737 | 0 | "Inverse direction for " + projFilename + |
2738 | 0 | " not supported"); |
2739 | 0 | } |
2740 | 0 | return self; |
2741 | 0 | } |
2742 | | |
2743 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2744 | 0 | const auto l_targetCRSNull = targetCRS(); |
2745 | 0 | if (l_sourceCRSNull == nullptr) { |
2746 | 0 | throw util::UnsupportedOperationException( |
2747 | 0 | "Missing sourceCRS"); |
2748 | 0 | } |
2749 | 0 | if (l_targetCRSNull == nullptr) { |
2750 | 0 | throw util::UnsupportedOperationException( |
2751 | 0 | "Missing targetCRS"); |
2752 | 0 | } |
2753 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2754 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2755 | 0 | const VectorOfParameters parameters{ |
2756 | 0 | createOpParamNameEPSGCode(parameterCode)}; |
2757 | 0 | const VectorOfValues values{ |
2758 | 0 | ParameterValue::createFilename(projFilename)}; |
2759 | 0 | if (inverseDirection) { |
2760 | 0 | return Transformation::create( |
2761 | 0 | createPropertiesForInverse( |
2762 | 0 | self.as_nullable().get(), true, false), |
2763 | 0 | l_targetCRS, l_sourceCRS, l_interpolationCRS, |
2764 | 0 | createSimilarPropertiesMethod(method()), |
2765 | 0 | parameters, values, |
2766 | 0 | coordinateOperationAccuracies()) |
2767 | 0 | ->inverseAsTransformation(); |
2768 | 0 | } else { |
2769 | 0 | return Transformation::create( |
2770 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2771 | 0 | l_targetCRS, l_interpolationCRS, |
2772 | 0 | createSimilarPropertiesMethod(method()), parameters, |
2773 | 0 | values, coordinateOperationAccuracies()); |
2774 | 0 | } |
2775 | 0 | } |
2776 | 0 | } |
2777 | 0 | } |
2778 | | |
2779 | 0 | static const struct { |
2780 | 0 | int methodEPSGCode; |
2781 | 0 | int gridFilenameParamEPSGCode; |
2782 | 0 | const char *gridFilenameParamName; |
2783 | 0 | } gridTransformations[] = { |
2784 | 0 | {EPSG_CODE_METHOD_NEW_ZEALAND_DEFORMATION_MODEL, |
2785 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
2786 | 0 | EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE}, |
2787 | 0 | {EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON, |
2788 | 0 | EPSG_CODE_PARAMETER_TIN_OFFSET_FILE, |
2789 | 0 | EPSG_NAME_PARAMETER_TIN_OFFSET_FILE}, |
2790 | 0 | {EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON, |
2791 | 0 | EPSG_CODE_PARAMETER_TIN_OFFSET_FILE, |
2792 | 0 | EPSG_NAME_PARAMETER_TIN_OFFSET_FILE}, |
2793 | 0 | {EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON, |
2794 | 0 | EPSG_CODE_PARAMETER_TIN_OFFSET_FILE, |
2795 | 0 | EPSG_NAME_PARAMETER_TIN_OFFSET_FILE}, |
2796 | 0 | }; |
2797 | |
|
2798 | 0 | for (const auto &gridTransf : gridTransformations) { |
2799 | 0 | if (methodEPSGCode == gridTransf.methodEPSGCode) { |
2800 | 0 | auto fileParameter = |
2801 | 0 | parameterValue(gridTransf.gridFilenameParamName, |
2802 | 0 | gridTransf.gridFilenameParamEPSGCode); |
2803 | 0 | if (fileParameter && |
2804 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
2805 | |
|
2806 | 0 | const auto &filename = fileParameter->valueFile(); |
2807 | 0 | if (databaseContext->lookForGridAlternative( |
2808 | 0 | filename, projFilename, projGridFormat, |
2809 | 0 | inverseDirection)) { |
2810 | |
|
2811 | 0 | if (filename == projFilename) { |
2812 | 0 | if (inverseDirection) { |
2813 | 0 | throw util::UnsupportedOperationException( |
2814 | 0 | "Inverse direction for " + projFilename + |
2815 | 0 | " not supported"); |
2816 | 0 | } |
2817 | 0 | return self; |
2818 | 0 | } |
2819 | | |
2820 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2821 | 0 | const auto l_targetCRSNull = targetCRS(); |
2822 | 0 | if (l_sourceCRSNull == nullptr) { |
2823 | 0 | throw util::UnsupportedOperationException( |
2824 | 0 | "Missing sourceCRS"); |
2825 | 0 | } |
2826 | 0 | if (l_targetCRSNull == nullptr) { |
2827 | 0 | throw util::UnsupportedOperationException( |
2828 | 0 | "Missing targetCRS"); |
2829 | 0 | } |
2830 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2831 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2832 | 0 | const VectorOfParameters parameters{ |
2833 | 0 | createOpParamNameEPSGCode( |
2834 | 0 | gridTransf.gridFilenameParamEPSGCode)}; |
2835 | 0 | const VectorOfValues values{ |
2836 | 0 | ParameterValue::createFilename(projFilename)}; |
2837 | 0 | if (inverseDirection) { |
2838 | 0 | return Transformation::create( |
2839 | 0 | createPropertiesForInverse( |
2840 | 0 | self.as_nullable().get(), true, false), |
2841 | 0 | l_targetCRS, l_sourceCRS, l_interpolationCRS, |
2842 | 0 | createSimilarPropertiesMethod(method()), |
2843 | 0 | parameters, values, |
2844 | 0 | coordinateOperationAccuracies()) |
2845 | 0 | ->inverseAsTransformation(); |
2846 | 0 | } else { |
2847 | 0 | return Transformation::create( |
2848 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2849 | 0 | l_targetCRS, l_interpolationCRS, |
2850 | 0 | createSimilarPropertiesMethod(method()), parameters, |
2851 | 0 | values, coordinateOperationAccuracies()); |
2852 | 0 | } |
2853 | 0 | } |
2854 | 0 | } |
2855 | 0 | break; |
2856 | 0 | } |
2857 | 0 | } |
2858 | | |
2859 | 0 | if (methodEPSGCode == |
2860 | 0 | EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG || |
2861 | 0 | methodEPSGCode == |
2862 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG) { |
2863 | 0 | auto fileParameter = |
2864 | 0 | parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
2865 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); |
2866 | 0 | if (fileParameter && |
2867 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME && |
2868 | 0 | databaseContext->lookForGridAlternative( |
2869 | 0 | fileParameter->valueFile(), projFilename, projGridFormat, |
2870 | 0 | inverseDirection)) { |
2871 | |
|
2872 | 0 | if (inverseDirection) { |
2873 | 0 | throw util::UnsupportedOperationException( |
2874 | 0 | "Inverse direction not supported"); |
2875 | 0 | } |
2876 | | |
2877 | 0 | if (fileParameter->valueFile() == projFilename) { |
2878 | 0 | return self; |
2879 | 0 | } |
2880 | | |
2881 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2882 | 0 | const auto l_targetCRSNull = targetCRS(); |
2883 | 0 | if (l_sourceCRSNull == nullptr) { |
2884 | 0 | throw util::UnsupportedOperationException("Missing sourceCRS"); |
2885 | 0 | } |
2886 | 0 | if (l_targetCRSNull == nullptr) { |
2887 | 0 | throw util::UnsupportedOperationException("Missing targetCRS"); |
2888 | 0 | } |
2889 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2890 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2891 | |
|
2892 | 0 | std::vector<OperationParameterNNPtr> parameters; |
2893 | 0 | std::vector<ParameterValueNNPtr> values; |
2894 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
2895 | 0 | auto opParamvalue = |
2896 | 0 | dynamic_cast<const OperationParameterValue *>( |
2897 | 0 | genOpParamvalue.get()); |
2898 | 0 | if (!opParamvalue) { |
2899 | 0 | throw util::UnsupportedOperationException( |
2900 | 0 | "non OperationParameterValue parameter value"); |
2901 | 0 | } |
2902 | 0 | const auto ¶meter = opParamvalue->parameter(); |
2903 | 0 | parameters.push_back(parameter); |
2904 | 0 | if (parameter->getEPSGCode() == |
2905 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE) { |
2906 | 0 | values.push_back( |
2907 | 0 | ParameterValue::createFilename(projFilename)); |
2908 | 0 | } else { |
2909 | 0 | values.push_back(opParamvalue->parameterValue()); |
2910 | 0 | } |
2911 | 0 | } |
2912 | 0 | return Transformation::create( |
2913 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2914 | 0 | l_targetCRS, l_interpolationCRS, |
2915 | 0 | createSimilarPropertiesMethod(method()), parameters, values, |
2916 | 0 | coordinateOperationAccuracies()); |
2917 | 0 | } |
2918 | 0 | } |
2919 | | |
2920 | 0 | if (methodEPSGCode == |
2921 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) { |
2922 | 0 | std::string projFilenameGTF; |
2923 | 0 | std::string projFilenamePMV; |
2924 | 0 | auto fileParameterGTF = |
2925 | 0 | parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, |
2926 | 0 | EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE); |
2927 | 0 | auto fileParameterPMV = |
2928 | 0 | parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
2929 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); |
2930 | 0 | if (fileParameterGTF && |
2931 | 0 | fileParameterGTF->type() == ParameterValue::Type::FILENAME && |
2932 | 0 | databaseContext->lookForGridAlternative( |
2933 | 0 | fileParameterGTF->valueFile(), projFilenameGTF, projGridFormat, |
2934 | 0 | inverseDirection) && |
2935 | 0 | fileParameterPMV && |
2936 | 0 | fileParameterPMV->type() == ParameterValue::Type::FILENAME && |
2937 | 0 | databaseContext->lookForGridAlternative( |
2938 | 0 | fileParameterPMV->valueFile(), projFilenamePMV, projGridFormat, |
2939 | 0 | inverseDirection)) { |
2940 | |
|
2941 | 0 | if (inverseDirection) { |
2942 | 0 | throw util::UnsupportedOperationException( |
2943 | 0 | "Inverse direction not supported"); |
2944 | 0 | } |
2945 | | |
2946 | 0 | if (fileParameterGTF->valueFile() == projFilenameGTF && |
2947 | 0 | fileParameterPMV->valueFile() == projFilenamePMV) { |
2948 | 0 | return self; |
2949 | 0 | } |
2950 | | |
2951 | 0 | const auto l_sourceCRSNull = sourceCRS(); |
2952 | 0 | const auto l_targetCRSNull = targetCRS(); |
2953 | 0 | if (l_sourceCRSNull == nullptr) { |
2954 | 0 | throw util::UnsupportedOperationException("Missing sourceCRS"); |
2955 | 0 | } |
2956 | 0 | if (l_targetCRSNull == nullptr) { |
2957 | 0 | throw util::UnsupportedOperationException("Missing targetCRS"); |
2958 | 0 | } |
2959 | 0 | auto l_sourceCRS = NN_NO_CHECK(l_sourceCRSNull); |
2960 | 0 | auto l_targetCRS = NN_NO_CHECK(l_targetCRSNull); |
2961 | |
|
2962 | 0 | std::vector<OperationParameterNNPtr> parameters; |
2963 | 0 | std::vector<ParameterValueNNPtr> values; |
2964 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
2965 | 0 | auto opParamvalue = |
2966 | 0 | dynamic_cast<const OperationParameterValue *>( |
2967 | 0 | genOpParamvalue.get()); |
2968 | 0 | if (!opParamvalue) { |
2969 | 0 | throw util::UnsupportedOperationException( |
2970 | 0 | "non OperationParameterValue parameter value"); |
2971 | 0 | } |
2972 | 0 | const auto ¶meter = opParamvalue->parameter(); |
2973 | 0 | parameters.push_back(parameter); |
2974 | 0 | if (parameter->getEPSGCode() == |
2975 | 0 | EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE) { |
2976 | 0 | values.push_back( |
2977 | 0 | ParameterValue::createFilename(projFilenameGTF)); |
2978 | 0 | } else if ( |
2979 | 0 | parameter->getEPSGCode() == |
2980 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE) { |
2981 | 0 | values.push_back( |
2982 | 0 | ParameterValue::createFilename(projFilenamePMV)); |
2983 | 0 | } else { |
2984 | 0 | values.push_back(opParamvalue->parameterValue()); |
2985 | 0 | } |
2986 | 0 | } |
2987 | 0 | return Transformation::create( |
2988 | 0 | createSimilarPropertiesOperation(self), l_sourceCRS, |
2989 | 0 | l_targetCRS, l_interpolationCRS, |
2990 | 0 | createSimilarPropertiesMethod(method()), parameters, values, |
2991 | 0 | coordinateOperationAccuracies()); |
2992 | 0 | } |
2993 | 0 | } |
2994 | | |
2995 | 0 | return self; |
2996 | 0 | } |
2997 | | |
2998 | | //! @cond Doxygen_Suppress |
2999 | | // --------------------------------------------------------------------------- |
3000 | | |
3001 | 0 | InvalidOperation::InvalidOperation(const char *message) : Exception(message) {} |
3002 | | |
3003 | | // --------------------------------------------------------------------------- |
3004 | | |
3005 | | InvalidOperation::InvalidOperation(const std::string &message) |
3006 | 0 | : Exception(message) {} |
3007 | | |
3008 | | // --------------------------------------------------------------------------- |
3009 | | |
3010 | 0 | InvalidOperation::InvalidOperation(const InvalidOperation &) = default; |
3011 | | |
3012 | | // --------------------------------------------------------------------------- |
3013 | | |
3014 | 0 | InvalidOperation::~InvalidOperation() = default; |
3015 | | //! @endcond |
3016 | | |
3017 | | // --------------------------------------------------------------------------- |
3018 | | |
3019 | | GeneralParameterValueNNPtr |
3020 | | SingleOperation::createOperationParameterValueFromInterpolationCRS( |
3021 | 0 | int methodEPSGCode, int crsEPSGCode) { |
3022 | 0 | util::PropertyMap propertiesParameter; |
3023 | 0 | propertiesParameter.set( |
3024 | 0 | common::IdentifiedObject::NAME_KEY, |
3025 | 0 | methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE |
3026 | 0 | ? EPSG_NAME_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS |
3027 | 0 | : EPSG_NAME_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS); |
3028 | 0 | propertiesParameter.set( |
3029 | 0 | metadata::Identifier::CODE_KEY, |
3030 | 0 | methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE |
3031 | 0 | ? EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS |
3032 | 0 | : EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS); |
3033 | 0 | propertiesParameter.set(metadata::Identifier::CODESPACE_KEY, |
3034 | 0 | metadata::Identifier::EPSG); |
3035 | 0 | return OperationParameterValue::create( |
3036 | 0 | OperationParameter::create(propertiesParameter), |
3037 | 0 | ParameterValue::create(crsEPSGCode)); |
3038 | 0 | } |
3039 | | |
3040 | | // --------------------------------------------------------------------------- |
3041 | | |
3042 | | void SingleOperation::exportTransformationToWKT( |
3043 | 0 | io::WKTFormatter *formatter) const { |
3044 | 0 | const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; |
3045 | 0 | if (!isWKT2) { |
3046 | 0 | throw io::FormattingException( |
3047 | 0 | "Transformation can only be exported to WKT2"); |
3048 | 0 | } |
3049 | | |
3050 | 0 | if (formatter->abridgedTransformation()) { |
3051 | 0 | formatter->startNode(io::WKTConstants::ABRIDGEDTRANSFORMATION, |
3052 | 0 | !identifiers().empty()); |
3053 | 0 | } else { |
3054 | 0 | formatter->startNode(io::WKTConstants::COORDINATEOPERATION, |
3055 | 0 | !identifiers().empty()); |
3056 | 0 | } |
3057 | |
|
3058 | 0 | formatter->addQuotedString(nameStr()); |
3059 | |
|
3060 | 0 | if (formatter->use2019Keywords()) { |
3061 | 0 | const auto &version = operationVersion(); |
3062 | 0 | if (version.has_value()) { |
3063 | 0 | formatter->startNode(io::WKTConstants::VERSION, false); |
3064 | 0 | formatter->addQuotedString(*version); |
3065 | 0 | formatter->endNode(); |
3066 | 0 | } |
3067 | 0 | } |
3068 | |
|
3069 | 0 | if (!formatter->abridgedTransformation()) { |
3070 | 0 | exportSourceCRSAndTargetCRSToWKT(this, formatter); |
3071 | 0 | } |
3072 | |
|
3073 | 0 | const auto &l_method = method(); |
3074 | 0 | l_method->_exportToWKT(formatter); |
3075 | |
|
3076 | 0 | bool hasInterpolationCRSParameter = false; |
3077 | 0 | for (const auto ¶mValue : parameterValues()) { |
3078 | 0 | const auto opParamvalue = |
3079 | 0 | dynamic_cast<const OperationParameterValue *>(paramValue.get()); |
3080 | 0 | const int paramEPSGCode = |
3081 | 0 | opParamvalue ? opParamvalue->parameter()->getEPSGCode() : 0; |
3082 | 0 | if (paramEPSGCode == |
3083 | 0 | EPSG_CODE_PARAMETER_EPSG_CODE_FOR_INTERPOLATION_CRS || |
3084 | 0 | paramEPSGCode == EPSG_CODE_PARAMETER_EPSG_CODE_FOR_HORIZONTAL_CRS) { |
3085 | 0 | hasInterpolationCRSParameter = true; |
3086 | 0 | } |
3087 | 0 | paramValue->_exportToWKT(formatter, nullptr); |
3088 | 0 | } |
3089 | |
|
3090 | 0 | const auto l_interpolationCRS = interpolationCRS(); |
3091 | 0 | if (formatter->abridgedTransformation()) { |
3092 | | // If we have an interpolation CRS that has a EPSG code, then |
3093 | | // we can export it as a PARAMETER[] |
3094 | 0 | if (!hasInterpolationCRSParameter && l_interpolationCRS) { |
3095 | 0 | const auto code = l_interpolationCRS->getEPSGCode(); |
3096 | 0 | if (code != 0) { |
3097 | 0 | const auto methodEPSGCode = l_method->getEPSGCode(); |
3098 | 0 | createOperationParameterValueFromInterpolationCRS( |
3099 | 0 | methodEPSGCode, code) |
3100 | 0 | ->_exportToWKT(formatter, nullptr); |
3101 | 0 | } |
3102 | 0 | } |
3103 | 0 | } else { |
3104 | 0 | if (l_interpolationCRS) { |
3105 | 0 | formatter->startNode(io::WKTConstants::INTERPOLATIONCRS, false); |
3106 | 0 | interpolationCRS()->_exportToWKT(formatter); |
3107 | 0 | formatter->endNode(); |
3108 | 0 | } |
3109 | |
|
3110 | 0 | if (!coordinateOperationAccuracies().empty()) { |
3111 | 0 | formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false); |
3112 | 0 | formatter->add(coordinateOperationAccuracies()[0]->value()); |
3113 | 0 | formatter->endNode(); |
3114 | 0 | } |
3115 | 0 | } |
3116 | |
|
3117 | 0 | ObjectUsage::baseExportToWKT(formatter); |
3118 | 0 | formatter->endNode(); |
3119 | 0 | } |
3120 | | |
3121 | | // --------------------------------------------------------------------------- |
3122 | | |
3123 | | //! @cond Doxygen_Suppress |
3124 | | |
3125 | | // If crs is a geographic CRS, or a compound CRS of a geographic CRS, |
3126 | | // or a compoundCRS of a bound CRS of a geographic CRS, return that |
3127 | | // geographic CRS |
3128 | | static crs::GeographicCRSPtr |
3129 | 0 | extractGeographicCRSIfGeographicCRSOrEquivalent(const crs::CRSNNPtr &crs) { |
3130 | 0 | auto geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>(crs); |
3131 | 0 | if (!geogCRS) { |
3132 | 0 | auto compoundCRS = util::nn_dynamic_pointer_cast<crs::CompoundCRS>(crs); |
3133 | 0 | if (compoundCRS) { |
3134 | 0 | const auto &components = compoundCRS->componentReferenceSystems(); |
3135 | 0 | if (!components.empty()) { |
3136 | 0 | geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>( |
3137 | 0 | components[0]); |
3138 | 0 | if (!geogCRS) { |
3139 | 0 | auto boundCRS = |
3140 | 0 | util::nn_dynamic_pointer_cast<crs::BoundCRS>( |
3141 | 0 | components[0]); |
3142 | 0 | if (boundCRS) { |
3143 | 0 | geogCRS = |
3144 | 0 | util::nn_dynamic_pointer_cast<crs::GeographicCRS>( |
3145 | 0 | boundCRS->baseCRS()); |
3146 | 0 | } |
3147 | 0 | } |
3148 | 0 | } |
3149 | 0 | } else { |
3150 | 0 | auto boundCRS = util::nn_dynamic_pointer_cast<crs::BoundCRS>(crs); |
3151 | 0 | if (boundCRS) { |
3152 | 0 | geogCRS = util::nn_dynamic_pointer_cast<crs::GeographicCRS>( |
3153 | 0 | boundCRS->baseCRS()); |
3154 | 0 | } |
3155 | 0 | } |
3156 | 0 | } |
3157 | 0 | return geogCRS; |
3158 | 0 | } |
3159 | | |
3160 | | // --------------------------------------------------------------------------- |
3161 | | |
3162 | | [[noreturn]] static void |
3163 | 0 | ThrowExceptionNotGeodeticGeographic(const char *trfrm_name) { |
3164 | 0 | throw io::FormattingException(concat("Can apply ", std::string(trfrm_name), |
3165 | 0 | " only to GeodeticCRS / " |
3166 | 0 | "GeographicCRS")); |
3167 | 0 | } |
3168 | | |
3169 | | // --------------------------------------------------------------------------- |
3170 | | |
3171 | | static void setupPROJGeodeticSourceCRS(io::PROJStringFormatter *formatter, |
3172 | | const crs::CRSNNPtr &crs, bool addPushV3, |
3173 | 0 | const char *trfrm_name) { |
3174 | 0 | auto sourceCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs); |
3175 | 0 | if (sourceCRSGeog) { |
3176 | 0 | formatter->startInversion(); |
3177 | 0 | sourceCRSGeog->_exportToPROJString(formatter); |
3178 | 0 | formatter->stopInversion(); |
3179 | 0 | if (util::isOfExactType<crs::DerivedGeographicCRS>( |
3180 | 0 | *(sourceCRSGeog.get()))) { |
3181 | 0 | const auto derivedGeogCRS = |
3182 | 0 | dynamic_cast<const crs::DerivedGeographicCRS *>( |
3183 | 0 | sourceCRSGeog.get()); |
3184 | | // The export of a DerivedGeographicCRS in non-CRS mode adds |
3185 | | // unit conversion and axis swapping to the base CRS. |
3186 | | // We must compensate for that formatter->startInversion(); |
3187 | 0 | formatter->startInversion(); |
3188 | 0 | derivedGeogCRS->baseCRS()->addAngularUnitConvertAndAxisSwap( |
3189 | 0 | formatter); |
3190 | 0 | formatter->stopInversion(); |
3191 | 0 | } |
3192 | |
|
3193 | 0 | if (addPushV3) { |
3194 | 0 | formatter->addStep("push"); |
3195 | 0 | formatter->addParam("v_3"); |
3196 | 0 | } |
3197 | |
|
3198 | 0 | formatter->addStep("cart"); |
3199 | 0 | sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
3200 | 0 | } else { |
3201 | 0 | auto sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get()); |
3202 | 0 | if (!sourceCRSGeod) { |
3203 | 0 | ThrowExceptionNotGeodeticGeographic(trfrm_name); |
3204 | 0 | } |
3205 | 0 | formatter->startInversion(); |
3206 | 0 | sourceCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); |
3207 | 0 | formatter->stopInversion(); |
3208 | 0 | } |
3209 | 0 | } |
3210 | | // --------------------------------------------------------------------------- |
3211 | | |
3212 | | static void setupPROJGeodeticTargetCRS(io::PROJStringFormatter *formatter, |
3213 | | const crs::CRSNNPtr &crs, bool addPopV3, |
3214 | 0 | const char *trfrm_name) { |
3215 | 0 | auto targetCRSGeog = extractGeographicCRSIfGeographicCRSOrEquivalent(crs); |
3216 | 0 | if (targetCRSGeog) { |
3217 | 0 | formatter->addStep("cart"); |
3218 | 0 | formatter->setCurrentStepInverted(true); |
3219 | 0 | targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
3220 | |
|
3221 | 0 | if (addPopV3) { |
3222 | 0 | formatter->addStep("pop"); |
3223 | 0 | formatter->addParam("v_3"); |
3224 | 0 | } |
3225 | 0 | if (util::isOfExactType<crs::DerivedGeographicCRS>( |
3226 | 0 | *(targetCRSGeog.get()))) { |
3227 | | // The export of a DerivedGeographicCRS in non-CRS mode adds |
3228 | | // unit conversion and axis swapping to the base CRS. |
3229 | | // We must compensate for that formatter->startInversion(); |
3230 | 0 | const auto derivedGeogCRS = |
3231 | 0 | dynamic_cast<const crs::DerivedGeographicCRS *>( |
3232 | 0 | targetCRSGeog.get()); |
3233 | 0 | derivedGeogCRS->baseCRS()->addAngularUnitConvertAndAxisSwap( |
3234 | 0 | formatter); |
3235 | 0 | } |
3236 | 0 | targetCRSGeog->_exportToPROJString(formatter); |
3237 | 0 | } else { |
3238 | 0 | auto targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>(crs.get()); |
3239 | 0 | if (!targetCRSGeod) { |
3240 | 0 | ThrowExceptionNotGeodeticGeographic(trfrm_name); |
3241 | 0 | } |
3242 | 0 | targetCRSGeod->addGeocentricUnitConversionIntoPROJString(formatter); |
3243 | 0 | } |
3244 | 0 | } |
3245 | | |
3246 | | //! @endcond |
3247 | | |
3248 | | // --------------------------------------------------------------------------- |
3249 | | |
3250 | | /* static */ |
3251 | | void SingleOperation::exportToPROJStringChangeVerticalUnit( |
3252 | 0 | io::PROJStringFormatter *formatter, double convFactor) { |
3253 | |
|
3254 | 0 | const auto uom = common::UnitOfMeasure(std::string(), convFactor, |
3255 | 0 | common::UnitOfMeasure::Type::LINEAR) |
3256 | 0 | .exportToPROJString(); |
3257 | 0 | const std::string reverse_uom( |
3258 | 0 | convFactor == 0.0 |
3259 | 0 | ? std::string() |
3260 | 0 | : common::UnitOfMeasure(std::string(), 1.0 / convFactor, |
3261 | 0 | common::UnitOfMeasure::Type::LINEAR) |
3262 | 0 | .exportToPROJString()); |
3263 | 0 | if (uom == "m") { |
3264 | | // do nothing |
3265 | 0 | } else if (!uom.empty()) { |
3266 | 0 | formatter->addStep("unitconvert"); |
3267 | 0 | formatter->addParam("z_in", uom); |
3268 | 0 | formatter->addParam("z_out", "m"); |
3269 | 0 | } else if (!reverse_uom.empty()) { |
3270 | 0 | formatter->addStep("unitconvert"); |
3271 | 0 | formatter->addParam("z_in", "m"); |
3272 | 0 | formatter->addParam("z_out", reverse_uom); |
3273 | 0 | } else if (fabs(convFactor - |
3274 | 0 | common::UnitOfMeasure::FOOT.conversionToSI() / |
3275 | 0 | common::UnitOfMeasure::US_FOOT.conversionToSI()) < |
3276 | 0 | 1e-10) { |
3277 | 0 | formatter->addStep("unitconvert"); |
3278 | 0 | formatter->addParam("z_in", "ft"); |
3279 | 0 | formatter->addParam("z_out", "us-ft"); |
3280 | 0 | } else if (fabs(convFactor - |
3281 | 0 | common::UnitOfMeasure::US_FOOT.conversionToSI() / |
3282 | 0 | common::UnitOfMeasure::FOOT.conversionToSI()) < 1e-10) { |
3283 | 0 | formatter->addStep("unitconvert"); |
3284 | 0 | formatter->addParam("z_in", "us-ft"); |
3285 | 0 | formatter->addParam("z_out", "ft"); |
3286 | 0 | } else { |
3287 | 0 | formatter->addStep("affine"); |
3288 | 0 | formatter->addParam("s33", convFactor); |
3289 | 0 | } |
3290 | 0 | } |
3291 | | |
3292 | | // --------------------------------------------------------------------------- |
3293 | | |
3294 | | bool SingleOperation::exportToPROJStringGeneric( |
3295 | 0 | io::PROJStringFormatter *formatter) const { |
3296 | 0 | const int methodEPSGCode = method()->getEPSGCode(); |
3297 | |
|
3298 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_AFFINE_PARAMETRIC_TRANSFORMATION) { |
3299 | 0 | const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value(); |
3300 | 0 | const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value(); |
3301 | 0 | const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value(); |
3302 | 0 | const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value(); |
3303 | 0 | const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value(); |
3304 | 0 | const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value(); |
3305 | | |
3306 | | // Do not mess with axis unit and order for that transformation |
3307 | |
|
3308 | 0 | formatter->addStep("affine"); |
3309 | 0 | formatter->addParam("xoff", A0); |
3310 | 0 | formatter->addParam("s11", A1); |
3311 | 0 | formatter->addParam("s12", A2); |
3312 | 0 | formatter->addParam("yoff", B0); |
3313 | 0 | formatter->addParam("s21", B1); |
3314 | 0 | formatter->addParam("s22", B2); |
3315 | |
|
3316 | 0 | return true; |
3317 | 0 | } |
3318 | | |
3319 | 0 | if (method()->nameStr() == |
3320 | 0 | PROJ_WKT2_NAME_METHOD_3D_AFFINE_PARAMETRIC_TRANSFORMATION) { |
3321 | 0 | const double A0 = parameterValueMeasure(EPSG_CODE_PARAMETER_A0).value(); |
3322 | 0 | const double A1 = parameterValueMeasure(EPSG_CODE_PARAMETER_A1).value(); |
3323 | 0 | const double A2 = parameterValueMeasure(EPSG_CODE_PARAMETER_A2).value(); |
3324 | 0 | const double A3 = |
3325 | 0 | parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_A3).value(); |
3326 | 0 | const double B0 = parameterValueMeasure(EPSG_CODE_PARAMETER_B0).value(); |
3327 | 0 | const double B1 = parameterValueMeasure(EPSG_CODE_PARAMETER_B1).value(); |
3328 | 0 | const double B2 = parameterValueMeasure(EPSG_CODE_PARAMETER_B2).value(); |
3329 | 0 | const double B3 = |
3330 | 0 | parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_B3).value(); |
3331 | 0 | const double C0 = |
3332 | 0 | parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_C0).value(); |
3333 | 0 | const double C1 = |
3334 | 0 | parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_C1).value(); |
3335 | 0 | const double C2 = |
3336 | 0 | parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_C2).value(); |
3337 | 0 | const double C3 = |
3338 | 0 | parameterValueMeasure(PROJ_WKT2_NAME_PARAMETER_C3).value(); |
3339 | | |
3340 | | // Do not mess with axis unit and order for that transformation |
3341 | |
|
3342 | 0 | formatter->addStep("affine"); |
3343 | 0 | formatter->addParam("xoff", A0); |
3344 | 0 | formatter->addParam("s11", A1); |
3345 | 0 | formatter->addParam("s12", A2); |
3346 | 0 | formatter->addParam("s13", A3); |
3347 | 0 | formatter->addParam("yoff", B0); |
3348 | 0 | formatter->addParam("s21", B1); |
3349 | 0 | formatter->addParam("s22", B2); |
3350 | 0 | formatter->addParam("s23", B3); |
3351 | 0 | formatter->addParam("zoff", C0); |
3352 | 0 | formatter->addParam("s31", C1); |
3353 | 0 | formatter->addParam("s32", C2); |
3354 | 0 | formatter->addParam("s33", C3); |
3355 | |
|
3356 | 0 | return true; |
3357 | 0 | } |
3358 | | |
3359 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_SIMILARITY_TRANSFORMATION) { |
3360 | 0 | const double XT0 = |
3361 | 0 | parameterValueMeasure( |
3362 | 0 | EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT_TARGET_CRS) |
3363 | 0 | .value(); |
3364 | 0 | const double YT0 = |
3365 | 0 | parameterValueMeasure( |
3366 | 0 | EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT_TARGET_CRS) |
3367 | 0 | .value(); |
3368 | 0 | const double M = |
3369 | 0 | parameterValueMeasure( |
3370 | 0 | EPSG_CODE_PARAMETER_SCALE_FACTOR_FOR_SOURCE_CRS_AXES) |
3371 | 0 | .value(); |
3372 | 0 | const double q = parameterValueNumeric( |
3373 | 0 | EPSG_CODE_PARAMETER_ROTATION_ANGLE_OF_SOURCE_CRS_AXES, |
3374 | 0 | common::UnitOfMeasure::RADIAN); |
3375 | | |
3376 | | // Do not mess with axis unit and order for that transformation |
3377 | |
|
3378 | 0 | formatter->addStep("affine"); |
3379 | 0 | formatter->addParam("xoff", XT0); |
3380 | 0 | formatter->addParam("s11", M * cos(q)); |
3381 | 0 | formatter->addParam("s12", M * sin(q)); |
3382 | 0 | formatter->addParam("yoff", YT0); |
3383 | 0 | formatter->addParam("s21", -M * sin(q)); |
3384 | 0 | formatter->addParam("s22", M * cos(q)); |
3385 | |
|
3386 | 0 | return true; |
3387 | 0 | } |
3388 | | |
3389 | 0 | if (isAxisOrderReversal(methodEPSGCode)) { |
3390 | 0 | formatter->addStep("axisswap"); |
3391 | 0 | formatter->addParam("order", "2,1"); |
3392 | 0 | auto sourceCRSGeog = |
3393 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); |
3394 | 0 | auto targetCRSGeog = |
3395 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); |
3396 | 0 | if (sourceCRSGeog && targetCRSGeog) { |
3397 | 0 | const auto &unitSrc = |
3398 | 0 | sourceCRSGeog->coordinateSystem()->axisList()[0]->unit(); |
3399 | 0 | const auto &unitDst = |
3400 | 0 | targetCRSGeog->coordinateSystem()->axisList()[0]->unit(); |
3401 | 0 | if (!unitSrc._isEquivalentTo( |
3402 | 0 | unitDst, util::IComparable::Criterion::EQUIVALENT)) { |
3403 | 0 | formatter->addStep("unitconvert"); |
3404 | 0 | auto projUnit = unitSrc.exportToPROJString(); |
3405 | 0 | if (projUnit.empty()) { |
3406 | 0 | formatter->addParam("xy_in", unitSrc.conversionToSI()); |
3407 | 0 | } else { |
3408 | 0 | formatter->addParam("xy_in", projUnit); |
3409 | 0 | } |
3410 | 0 | projUnit = unitDst.exportToPROJString(); |
3411 | 0 | if (projUnit.empty()) { |
3412 | 0 | formatter->addParam("xy_out", unitDst.conversionToSI()); |
3413 | 0 | } else { |
3414 | 0 | formatter->addParam("xy_out", projUnit); |
3415 | 0 | } |
3416 | 0 | } |
3417 | 0 | } |
3418 | 0 | return true; |
3419 | 0 | } |
3420 | | |
3421 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC_GEOCENTRIC) { |
3422 | |
|
3423 | 0 | auto sourceCRSGeod = |
3424 | 0 | dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get()); |
3425 | 0 | if (!sourceCRSGeod) { |
3426 | 0 | auto sourceCRSCompound = |
3427 | 0 | dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get()); |
3428 | 0 | if (sourceCRSCompound) { |
3429 | 0 | sourceCRSGeod = dynamic_cast<const crs::GeodeticCRS *>( |
3430 | 0 | sourceCRSCompound->componentReferenceSystems() |
3431 | 0 | .front() |
3432 | 0 | .get()); |
3433 | 0 | } |
3434 | 0 | } |
3435 | 0 | auto targetCRSGeod = |
3436 | 0 | dynamic_cast<const crs::GeodeticCRS *>(targetCRS().get()); |
3437 | 0 | if (!targetCRSGeod) { |
3438 | 0 | auto targetCRSCompound = |
3439 | 0 | dynamic_cast<const crs::CompoundCRS *>(targetCRS().get()); |
3440 | 0 | if (targetCRSCompound) { |
3441 | 0 | targetCRSGeod = dynamic_cast<const crs::GeodeticCRS *>( |
3442 | 0 | targetCRSCompound->componentReferenceSystems() |
3443 | 0 | .front() |
3444 | 0 | .get()); |
3445 | 0 | } |
3446 | 0 | } |
3447 | 0 | if (sourceCRSGeod && targetCRSGeod) { |
3448 | 0 | auto sourceCRSGeog = |
3449 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRSGeod); |
3450 | 0 | auto targetCRSGeog = |
3451 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRSGeod); |
3452 | 0 | bool isSrcGeocentric = sourceCRSGeod->isGeocentric(); |
3453 | 0 | bool isSrcGeographic = sourceCRSGeog != nullptr; |
3454 | 0 | bool isTargetGeocentric = targetCRSGeod->isGeocentric(); |
3455 | 0 | bool isTargetGeographic = targetCRSGeog != nullptr; |
3456 | 0 | if ((isSrcGeocentric && isTargetGeographic) || |
3457 | 0 | (isSrcGeographic && isTargetGeocentric)) { |
3458 | |
|
3459 | 0 | formatter->startInversion(); |
3460 | 0 | sourceCRSGeod->_exportToPROJString(formatter); |
3461 | 0 | formatter->stopInversion(); |
3462 | |
|
3463 | 0 | targetCRSGeod->_exportToPROJString(formatter); |
3464 | |
|
3465 | 0 | return true; |
3466 | 0 | } |
3467 | 0 | } |
3468 | | |
3469 | 0 | throw io::FormattingException("Invalid nature of source and/or " |
3470 | 0 | "targetCRS for Geographic/Geocentric " |
3471 | 0 | "conversion"); |
3472 | 0 | } |
3473 | | |
3474 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_CHANGE_VERTICAL_UNIT) { |
3475 | 0 | const double convFactor = parameterValueNumericAsSI( |
3476 | 0 | EPSG_CODE_PARAMETER_UNIT_CONVERSION_SCALAR); |
3477 | 0 | exportToPROJStringChangeVerticalUnit(formatter, convFactor); |
3478 | 0 | return true; |
3479 | 0 | } |
3480 | | |
3481 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_HEIGHT_DEPTH_REVERSAL) { |
3482 | 0 | formatter->addStep("axisswap"); |
3483 | 0 | formatter->addParam("order", "1,2,-3"); |
3484 | 0 | return true; |
3485 | 0 | } |
3486 | | |
3487 | 0 | formatter->setCoordinateOperationOptimizations(true); |
3488 | |
|
3489 | 0 | bool positionVectorConvention = true; |
3490 | 0 | bool sevenParamsTransform = false; |
3491 | 0 | bool threeParamsTransform = false; |
3492 | 0 | bool fifteenParamsTransform = false; |
3493 | 0 | bool fullMatrix = false; |
3494 | 0 | const auto &l_method = method(); |
3495 | 0 | const auto &methodName = l_method->nameStr(); |
3496 | 0 | const bool isMethodInverseOf = starts_with(methodName, INVERSE_OF); |
3497 | 0 | const auto paramCount = parameterValues().size(); |
3498 | 0 | const bool l_isTimeDependent = isTimeDependent(methodName); |
3499 | 0 | const bool isPositionVector = |
3500 | 0 | ci_find(methodName, "Position Vector") != std::string::npos || |
3501 | 0 | ci_find(methodName, "PV") != std::string::npos; |
3502 | 0 | const bool isCoordinateFrame = |
3503 | 0 | ci_find(methodName, "Coordinate Frame") != std::string::npos || |
3504 | 0 | ci_find(methodName, "CF") != std::string::npos; |
3505 | 0 | if (methodEPSGCode == |
3506 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOCENTRIC || |
3507 | 0 | methodEPSGCode == |
3508 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_2D || |
3509 | 0 | methodEPSGCode == |
3510 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_FULL_MATRIX_GEOGRAPHIC_3D) { |
3511 | 0 | positionVectorConvention = false; |
3512 | 0 | sevenParamsTransform = true; |
3513 | 0 | fullMatrix = true; |
3514 | 0 | } else if ((paramCount == 7 && isCoordinateFrame && !l_isTimeDependent) || |
3515 | 0 | methodEPSGCode == EPSG_CODE_METHOD_COORDINATE_FRAME_GEOCENTRIC || |
3516 | 0 | methodEPSGCode == |
3517 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_2D || |
3518 | 0 | methodEPSGCode == |
3519 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_GEOGRAPHIC_3D || |
3520 | 0 | methodEPSGCode == |
3521 | 0 | EPSG_CODE_METHOD_COORDINATE_FRAME_GEOG3D_TO_COMPOUND) { |
3522 | 0 | positionVectorConvention = false; |
3523 | 0 | sevenParamsTransform = true; |
3524 | 0 | } else if ( |
3525 | 0 | (paramCount == 15 && isCoordinateFrame && l_isTimeDependent) || |
3526 | 0 | methodEPSGCode == |
3527 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOCENTRIC || |
3528 | 0 | methodEPSGCode == |
3529 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_2D || |
3530 | 0 | methodEPSGCode == |
3531 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_COORDINATE_FRAME_GEOGRAPHIC_3D) { |
3532 | 0 | positionVectorConvention = false; |
3533 | 0 | fifteenParamsTransform = true; |
3534 | 0 | } else if ((paramCount == 7 && isPositionVector && !l_isTimeDependent) || |
3535 | 0 | methodEPSGCode == EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC || |
3536 | 0 | methodEPSGCode == |
3537 | 0 | EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_2D || |
3538 | 0 | methodEPSGCode == |
3539 | 0 | EPSG_CODE_METHOD_POSITION_VECTOR_GEOGRAPHIC_3D) { |
3540 | 0 | sevenParamsTransform = true; |
3541 | 0 | } else if ( |
3542 | 0 | (paramCount == 15 && isPositionVector && l_isTimeDependent) || |
3543 | 0 | methodEPSGCode == |
3544 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOCENTRIC || |
3545 | 0 | methodEPSGCode == |
3546 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_2D || |
3547 | 0 | methodEPSGCode == |
3548 | 0 | EPSG_CODE_METHOD_TIME_DEPENDENT_POSITION_VECTOR_GEOGRAPHIC_3D) { |
3549 | 0 | fifteenParamsTransform = true; |
3550 | 0 | } else if ((paramCount == 3 && |
3551 | 0 | ci_find(methodName, "Geocentric translations") != |
3552 | 0 | std::string::npos) || |
3553 | 0 | methodEPSGCode == |
3554 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOCENTRIC || |
3555 | 0 | methodEPSGCode == |
3556 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_2D || |
3557 | 0 | methodEPSGCode == |
3558 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATION_GEOGRAPHIC_3D) { |
3559 | 0 | threeParamsTransform = true; |
3560 | 0 | } |
3561 | 0 | if (threeParamsTransform || sevenParamsTransform || |
3562 | 0 | fifteenParamsTransform) { |
3563 | 0 | double x = |
3564 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); |
3565 | 0 | double y = |
3566 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); |
3567 | 0 | double z = |
3568 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); |
3569 | |
|
3570 | 0 | auto l_sourceCRS = sourceCRS(); |
3571 | 0 | auto l_targetCRS = targetCRS(); |
3572 | 0 | auto sourceCRSGeog = |
3573 | 0 | dynamic_cast<const crs::GeographicCRS *>(l_sourceCRS.get()); |
3574 | 0 | auto targetCRSGeog = |
3575 | 0 | dynamic_cast<const crs::GeographicCRS *>(l_targetCRS.get()); |
3576 | 0 | const bool sourceIsCompound = |
3577 | 0 | !sourceCRSGeog && |
3578 | 0 | dynamic_cast<const crs::CompoundCRS *>(l_sourceCRS.get()); |
3579 | 0 | const bool targetIsCompound = |
3580 | 0 | !targetCRSGeog && |
3581 | 0 | dynamic_cast<const crs::CompoundCRS *>(l_targetCRS.get()); |
3582 | 0 | const bool addPushPopV3 = |
3583 | 0 | (((sourceCRSGeog && |
3584 | 0 | sourceCRSGeog->coordinateSystem()->axisList().size() == 2) || |
3585 | 0 | (targetCRSGeog && |
3586 | 0 | targetCRSGeog->coordinateSystem()->axisList().size() == 2)) || |
3587 | 0 | sourceIsCompound || targetIsCompound) && |
3588 | | // Below check is for example for |
3589 | | // EPSG:10905 ("ETRS89/DREF91/2016 to Asse 2025 + Asse 2025 height |
3590 | | // (1)") whose target CRS is a compound CRS |
3591 | 0 | !(sourceCRSGeog && |
3592 | 0 | sourceCRSGeog->coordinateSystem()->axisList().size() == 3 && |
3593 | 0 | targetIsCompound) && |
3594 | 0 | !(targetCRSGeog && |
3595 | 0 | targetCRSGeog->coordinateSystem()->axisList().size() == 3 && |
3596 | 0 | sourceIsCompound); |
3597 | |
|
3598 | 0 | if (l_sourceCRS) { |
3599 | 0 | setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), |
3600 | 0 | addPushPopV3, "Helmert"); |
3601 | 0 | } |
3602 | |
|
3603 | 0 | double sourceYear = |
3604 | 0 | sourceCoordinateEpoch().has_value() |
3605 | 0 | ? getRoundedEpochInDecimalYear( |
3606 | 0 | sourceCoordinateEpoch()->coordinateEpoch().convertToUnit( |
3607 | 0 | common::UnitOfMeasure::YEAR)) |
3608 | 0 | : 0; |
3609 | 0 | double targetYear = |
3610 | 0 | targetCoordinateEpoch().has_value() |
3611 | 0 | ? getRoundedEpochInDecimalYear( |
3612 | 0 | targetCoordinateEpoch()->coordinateEpoch().convertToUnit( |
3613 | 0 | common::UnitOfMeasure::YEAR)) |
3614 | 0 | : 0; |
3615 | 0 | if (sourceYear > 0 && targetYear == 0) |
3616 | 0 | targetYear = sourceYear; |
3617 | 0 | else if (targetYear > 0 && sourceYear == 0) |
3618 | 0 | sourceYear = targetYear; |
3619 | 0 | if (sourceYear > 0) { |
3620 | 0 | formatter->addStep("set"); |
3621 | 0 | formatter->addParam("v_4", sourceYear); |
3622 | 0 | } |
3623 | |
|
3624 | 0 | formatter->addStep("helmert"); |
3625 | 0 | if (fullMatrix) |
3626 | 0 | formatter->addParam("exact"); |
3627 | 0 | formatter->addParam("x", x); |
3628 | 0 | formatter->addParam("y", y); |
3629 | 0 | formatter->addParam("z", z); |
3630 | 0 | if (sevenParamsTransform || fifteenParamsTransform) { |
3631 | 0 | double rx = |
3632 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, |
3633 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3634 | 0 | double ry = |
3635 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, |
3636 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3637 | 0 | double rz = |
3638 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, |
3639 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3640 | 0 | double scaleDiff = |
3641 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, |
3642 | 0 | common::UnitOfMeasure::PARTS_PER_MILLION); |
3643 | 0 | formatter->addParam("rx", rx); |
3644 | 0 | formatter->addParam("ry", ry); |
3645 | 0 | formatter->addParam("rz", rz); |
3646 | 0 | formatter->addParam("s", scaleDiff); |
3647 | 0 | if (fifteenParamsTransform) { |
3648 | 0 | double rate_x = parameterValueNumeric( |
3649 | 0 | EPSG_CODE_PARAMETER_RATE_X_AXIS_TRANSLATION, |
3650 | 0 | common::UnitOfMeasure::METRE_PER_YEAR); |
3651 | 0 | double rate_y = parameterValueNumeric( |
3652 | 0 | EPSG_CODE_PARAMETER_RATE_Y_AXIS_TRANSLATION, |
3653 | 0 | common::UnitOfMeasure::METRE_PER_YEAR); |
3654 | 0 | double rate_z = parameterValueNumeric( |
3655 | 0 | EPSG_CODE_PARAMETER_RATE_Z_AXIS_TRANSLATION, |
3656 | 0 | common::UnitOfMeasure::METRE_PER_YEAR); |
3657 | 0 | double rate_rx = parameterValueNumeric( |
3658 | 0 | EPSG_CODE_PARAMETER_RATE_X_AXIS_ROTATION, |
3659 | 0 | common::UnitOfMeasure::ARC_SECOND_PER_YEAR); |
3660 | 0 | double rate_ry = parameterValueNumeric( |
3661 | 0 | EPSG_CODE_PARAMETER_RATE_Y_AXIS_ROTATION, |
3662 | 0 | common::UnitOfMeasure::ARC_SECOND_PER_YEAR); |
3663 | 0 | double rate_rz = parameterValueNumeric( |
3664 | 0 | EPSG_CODE_PARAMETER_RATE_Z_AXIS_ROTATION, |
3665 | 0 | common::UnitOfMeasure::ARC_SECOND_PER_YEAR); |
3666 | 0 | double rate_scaleDiff = parameterValueNumeric( |
3667 | 0 | EPSG_CODE_PARAMETER_RATE_SCALE_DIFFERENCE, |
3668 | 0 | common::UnitOfMeasure::PPM_PER_YEAR); |
3669 | 0 | double referenceEpochYear = |
3670 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_REFERENCE_EPOCH, |
3671 | 0 | common::UnitOfMeasure::YEAR); |
3672 | 0 | formatter->addParam("dx", rate_x); |
3673 | 0 | formatter->addParam("dy", rate_y); |
3674 | 0 | formatter->addParam("dz", rate_z); |
3675 | 0 | formatter->addParam("drx", rate_rx); |
3676 | 0 | formatter->addParam("dry", rate_ry); |
3677 | 0 | formatter->addParam("drz", rate_rz); |
3678 | 0 | formatter->addParam("ds", rate_scaleDiff); |
3679 | 0 | formatter->addParam("t_epoch", referenceEpochYear); |
3680 | 0 | } |
3681 | 0 | if (positionVectorConvention) { |
3682 | 0 | formatter->addParam("convention", "position_vector"); |
3683 | 0 | } else { |
3684 | 0 | formatter->addParam("convention", "coordinate_frame"); |
3685 | 0 | } |
3686 | 0 | } |
3687 | |
|
3688 | 0 | if (targetYear > 0) { |
3689 | 0 | formatter->addStep("set"); |
3690 | 0 | formatter->addParam("v_4", targetYear); |
3691 | 0 | } |
3692 | |
|
3693 | 0 | if (l_targetCRS) { |
3694 | 0 | setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), |
3695 | 0 | addPushPopV3, "Helmert"); |
3696 | 0 | } |
3697 | |
|
3698 | 0 | return true; |
3699 | 0 | } |
3700 | | |
3701 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOCENTRIC || |
3702 | 0 | methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || |
3703 | 0 | methodEPSGCode == |
3704 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_3D || |
3705 | 0 | methodEPSGCode == |
3706 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || |
3707 | 0 | methodEPSGCode == |
3708 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D || |
3709 | 0 | methodEPSGCode == |
3710 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D) { |
3711 | |
|
3712 | 0 | positionVectorConvention = |
3713 | 0 | isPositionVector || |
3714 | 0 | methodEPSGCode == |
3715 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOCENTRIC || |
3716 | 0 | methodEPSGCode == |
3717 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_3D || |
3718 | 0 | methodEPSGCode == |
3719 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D; |
3720 | |
|
3721 | 0 | double x = |
3722 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); |
3723 | 0 | double y = |
3724 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); |
3725 | 0 | double z = |
3726 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); |
3727 | 0 | double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, |
3728 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3729 | 0 | double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, |
3730 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3731 | 0 | double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, |
3732 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3733 | 0 | double scaleDiff = |
3734 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, |
3735 | 0 | common::UnitOfMeasure::PARTS_PER_MILLION); |
3736 | |
|
3737 | 0 | double px = parameterValueNumericAsSI( |
3738 | 0 | EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT); |
3739 | 0 | double py = parameterValueNumericAsSI( |
3740 | 0 | EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT); |
3741 | 0 | double pz = parameterValueNumericAsSI( |
3742 | 0 | EPSG_CODE_PARAMETER_ORDINATE_3_EVAL_POINT); |
3743 | |
|
3744 | 0 | bool addPushPopV3 = |
3745 | 0 | (methodEPSGCode == |
3746 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_PV_GEOGRAPHIC_2D || |
3747 | 0 | methodEPSGCode == |
3748 | 0 | EPSG_CODE_METHOD_MOLODENSKY_BADEKAS_CF_GEOGRAPHIC_2D); |
3749 | |
|
3750 | 0 | auto l_sourceCRS = sourceCRS(); |
3751 | 0 | if (l_sourceCRS) { |
3752 | 0 | setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), |
3753 | 0 | addPushPopV3, "Molodensky-Badekas"); |
3754 | 0 | } |
3755 | |
|
3756 | 0 | formatter->addStep("molobadekas"); |
3757 | 0 | formatter->addParam("x", x); |
3758 | 0 | formatter->addParam("y", y); |
3759 | 0 | formatter->addParam("z", z); |
3760 | 0 | formatter->addParam("rx", rx); |
3761 | 0 | formatter->addParam("ry", ry); |
3762 | 0 | formatter->addParam("rz", rz); |
3763 | 0 | formatter->addParam("s", scaleDiff); |
3764 | 0 | formatter->addParam("px", px); |
3765 | 0 | formatter->addParam("py", py); |
3766 | 0 | formatter->addParam("pz", pz); |
3767 | 0 | if (positionVectorConvention) { |
3768 | 0 | formatter->addParam("convention", "position_vector"); |
3769 | 0 | } else { |
3770 | 0 | formatter->addParam("convention", "coordinate_frame"); |
3771 | 0 | } |
3772 | |
|
3773 | 0 | auto l_targetCRS = targetCRS(); |
3774 | 0 | if (l_targetCRS) { |
3775 | 0 | setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), |
3776 | 0 | addPushPopV3, "Molodensky-Badekas"); |
3777 | 0 | } |
3778 | |
|
3779 | 0 | return true; |
3780 | 0 | } |
3781 | | |
3782 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_MOLODENSKY || |
3783 | 0 | methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { |
3784 | 0 | double x = |
3785 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); |
3786 | 0 | double y = |
3787 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); |
3788 | 0 | double z = |
3789 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); |
3790 | 0 | double da = parameterValueNumericAsSI( |
3791 | 0 | EPSG_CODE_PARAMETER_SEMI_MAJOR_AXIS_DIFFERENCE); |
3792 | 0 | double df = parameterValueNumericAsSI( |
3793 | 0 | EPSG_CODE_PARAMETER_FLATTENING_DIFFERENCE); |
3794 | |
|
3795 | 0 | auto sourceCRSGeog = |
3796 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); |
3797 | 0 | if (!sourceCRSGeog) { |
3798 | 0 | throw io::FormattingException( |
3799 | 0 | "Can apply Molodensky only to GeographicCRS"); |
3800 | 0 | } |
3801 | | |
3802 | 0 | auto targetCRSGeog = |
3803 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); |
3804 | 0 | if (!targetCRSGeog) { |
3805 | 0 | throw io::FormattingException( |
3806 | 0 | "Can apply Molodensky only to GeographicCRS"); |
3807 | 0 | } |
3808 | | |
3809 | 0 | formatter->startInversion(); |
3810 | 0 | sourceCRSGeog->_exportToPROJString(formatter); |
3811 | 0 | formatter->stopInversion(); |
3812 | |
|
3813 | 0 | formatter->addStep("molodensky"); |
3814 | 0 | sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
3815 | 0 | formatter->addParam("dx", x); |
3816 | 0 | formatter->addParam("dy", y); |
3817 | 0 | formatter->addParam("dz", z); |
3818 | 0 | formatter->addParam("da", da); |
3819 | 0 | formatter->addParam("df", df); |
3820 | |
|
3821 | 0 | if (ci_find(methodName, "Abridged") != std::string::npos || |
3822 | 0 | methodEPSGCode == EPSG_CODE_METHOD_ABRIDGED_MOLODENSKY) { |
3823 | 0 | formatter->addParam("abridged"); |
3824 | 0 | } |
3825 | |
|
3826 | 0 | targetCRSGeog->_exportToPROJString(formatter); |
3827 | |
|
3828 | 0 | return true; |
3829 | 0 | } |
3830 | | |
3831 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS) { |
3832 | 0 | double offsetLat = |
3833 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, |
3834 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3835 | 0 | double offsetLong = |
3836 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, |
3837 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3838 | |
|
3839 | 0 | auto l_sourceCRS = sourceCRS(); |
3840 | 0 | auto sourceCRSGeog = |
3841 | 0 | l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( |
3842 | 0 | NN_NO_CHECK(l_sourceCRS)) |
3843 | 0 | : nullptr; |
3844 | 0 | if (!sourceCRSGeog) { |
3845 | 0 | throw io::FormattingException( |
3846 | 0 | "Can apply Geographic 2D offsets only to GeographicCRS"); |
3847 | 0 | } |
3848 | | |
3849 | 0 | auto l_targetCRS = targetCRS(); |
3850 | 0 | auto targetCRSGeog = |
3851 | 0 | l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( |
3852 | 0 | NN_NO_CHECK(l_targetCRS)) |
3853 | 0 | : nullptr; |
3854 | 0 | if (!targetCRSGeog) { |
3855 | 0 | throw io::FormattingException( |
3856 | 0 | "Can apply Geographic 2D offsets only to GeographicCRS"); |
3857 | 0 | } |
3858 | | |
3859 | 0 | formatter->startInversion(); |
3860 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
3861 | 0 | formatter->stopInversion(); |
3862 | |
|
3863 | 0 | if (offsetLat != 0.0 || offsetLong != 0.0) { |
3864 | 0 | formatter->addStep("geogoffset"); |
3865 | 0 | formatter->addParam("dlat", offsetLat); |
3866 | 0 | formatter->addParam("dlon", offsetLong); |
3867 | 0 | } |
3868 | |
|
3869 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
3870 | |
|
3871 | 0 | return true; |
3872 | 0 | } |
3873 | | |
3874 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC3D_OFFSETS) { |
3875 | 0 | double offsetLat = |
3876 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, |
3877 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3878 | 0 | double offsetLong = |
3879 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, |
3880 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3881 | 0 | double offsetHeight = |
3882 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); |
3883 | |
|
3884 | 0 | auto sourceCRSGeog = |
3885 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); |
3886 | 0 | if (!sourceCRSGeog) { |
3887 | 0 | auto boundCRS = |
3888 | 0 | dynamic_cast<const crs::BoundCRS *>(sourceCRS().get()); |
3889 | 0 | if (boundCRS) { |
3890 | 0 | sourceCRSGeog = dynamic_cast<crs::GeographicCRS *>( |
3891 | 0 | boundCRS->baseCRS().get()); |
3892 | 0 | } |
3893 | 0 | if (!sourceCRSGeog) { |
3894 | 0 | throw io::FormattingException( |
3895 | 0 | "Can apply Geographic 3D offsets only to GeographicCRS"); |
3896 | 0 | } |
3897 | 0 | } |
3898 | | |
3899 | 0 | auto targetCRSGeog = |
3900 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); |
3901 | 0 | if (!targetCRSGeog) { |
3902 | 0 | auto boundCRS = |
3903 | 0 | dynamic_cast<const crs::BoundCRS *>(targetCRS().get()); |
3904 | 0 | if (boundCRS) { |
3905 | 0 | targetCRSGeog = dynamic_cast<const crs::GeographicCRS *>( |
3906 | 0 | boundCRS->baseCRS().get()); |
3907 | 0 | } |
3908 | 0 | if (!targetCRSGeog) { |
3909 | 0 | throw io::FormattingException( |
3910 | 0 | "Can apply Geographic 3D offsets only to GeographicCRS"); |
3911 | 0 | } |
3912 | 0 | } |
3913 | | |
3914 | 0 | formatter->startInversion(); |
3915 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
3916 | 0 | formatter->stopInversion(); |
3917 | |
|
3918 | 0 | if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { |
3919 | 0 | formatter->addStep("geogoffset"); |
3920 | 0 | formatter->addParam("dlat", offsetLat); |
3921 | 0 | formatter->addParam("dlon", offsetLong); |
3922 | 0 | formatter->addParam("dh", offsetHeight); |
3923 | 0 | } |
3924 | |
|
3925 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
3926 | |
|
3927 | 0 | return true; |
3928 | 0 | } |
3929 | | |
3930 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_GEOGRAPHIC2D_WITH_HEIGHT_OFFSETS) { |
3931 | 0 | double offsetLat = |
3932 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_LATITUDE_OFFSET, |
3933 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3934 | 0 | double offsetLong = |
3935 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, |
3936 | 0 | common::UnitOfMeasure::ARC_SECOND); |
3937 | 0 | double offsetHeight = |
3938 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT); |
3939 | |
|
3940 | 0 | auto sourceCRSGeog = |
3941 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); |
3942 | 0 | if (!sourceCRSGeog) { |
3943 | 0 | auto sourceCRSCompound = |
3944 | 0 | dynamic_cast<const crs::CompoundCRS *>(sourceCRS().get()); |
3945 | 0 | if (sourceCRSCompound) { |
3946 | 0 | sourceCRSGeog = sourceCRSCompound->extractGeographicCRS().get(); |
3947 | 0 | } |
3948 | 0 | if (!sourceCRSGeog) { |
3949 | 0 | throw io::FormattingException("Can apply Geographic 2D with " |
3950 | 0 | "height offsets only to " |
3951 | 0 | "GeographicCRS / CompoundCRS"); |
3952 | 0 | } |
3953 | 0 | } |
3954 | | |
3955 | 0 | auto targetCRSGeog = |
3956 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); |
3957 | 0 | if (!targetCRSGeog) { |
3958 | 0 | auto targetCRSCompound = |
3959 | 0 | dynamic_cast<const crs::CompoundCRS *>(targetCRS().get()); |
3960 | 0 | if (targetCRSCompound) { |
3961 | 0 | targetCRSGeog = targetCRSCompound->extractGeographicCRS().get(); |
3962 | 0 | } |
3963 | 0 | if (!targetCRSGeog) { |
3964 | 0 | throw io::FormattingException("Can apply Geographic 2D with " |
3965 | 0 | "height offsets only to " |
3966 | 0 | "GeographicCRS / CompoundCRS"); |
3967 | 0 | } |
3968 | 0 | } |
3969 | | |
3970 | 0 | formatter->startInversion(); |
3971 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
3972 | 0 | formatter->stopInversion(); |
3973 | |
|
3974 | 0 | if (offsetLat != 0.0 || offsetLong != 0.0 || offsetHeight != 0.0) { |
3975 | 0 | formatter->addStep("geogoffset"); |
3976 | 0 | formatter->addParam("dlat", offsetLat); |
3977 | 0 | formatter->addParam("dlon", offsetLong); |
3978 | 0 | formatter->addParam("dh", offsetHeight); |
3979 | 0 | } |
3980 | |
|
3981 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
3982 | |
|
3983 | 0 | return true; |
3984 | 0 | } |
3985 | | |
3986 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS) { |
3987 | 0 | double eastingOffset = parameterValueNumeric( |
3988 | 0 | EPSG_CODE_PARAMETER_EASTING_OFFSET, common::UnitOfMeasure::METRE); |
3989 | 0 | double northingOffset = parameterValueNumeric( |
3990 | 0 | EPSG_CODE_PARAMETER_NORTHING_OFFSET, common::UnitOfMeasure::METRE); |
3991 | |
|
3992 | 0 | const auto checkIfCompatEngineeringCRS = [](const crs::CRSPtr &crs) { |
3993 | 0 | auto engineeringCRS = |
3994 | 0 | dynamic_cast<const crs::EngineeringCRS *>(crs.get()); |
3995 | 0 | if (engineeringCRS) { |
3996 | 0 | auto cs = dynamic_cast<cs::CartesianCS *>( |
3997 | 0 | engineeringCRS->coordinateSystem().get()); |
3998 | 0 | if (!cs) { |
3999 | 0 | throw io::FormattingException( |
4000 | 0 | "Can apply Cartesian grid offsets only to " |
4001 | 0 | "EngineeringCRS with CartesianCS"); |
4002 | 0 | } |
4003 | 0 | const auto &unit = cs->axisList()[0]->unit(); |
4004 | 0 | if (!unit._isEquivalentTo( |
4005 | 0 | common::UnitOfMeasure::METRE, |
4006 | 0 | util::IComparable::Criterion::EQUIVALENT)) { |
4007 | | // Could be enhanced to support other units... |
4008 | 0 | throw io::FormattingException( |
4009 | 0 | "Can apply Cartesian grid offsets only to " |
4010 | 0 | "EngineeringCRS with CartesianCS with metre unit"); |
4011 | 0 | } |
4012 | 0 | } else { |
4013 | 0 | throw io::FormattingException( |
4014 | 0 | "Can apply Cartesian grid offsets only to ProjectedCRS or " |
4015 | 0 | "EngineeringCRS"); |
4016 | 0 | } |
4017 | 0 | }; |
4018 | |
|
4019 | 0 | auto l_sourceCRS = sourceCRS(); |
4020 | 0 | auto sourceCRSProj = |
4021 | 0 | dynamic_cast<const crs::ProjectedCRS *>(l_sourceCRS.get()); |
4022 | 0 | if (!sourceCRSProj) { |
4023 | 0 | checkIfCompatEngineeringCRS(l_sourceCRS); |
4024 | 0 | } |
4025 | |
|
4026 | 0 | auto l_targetCRS = targetCRS(); |
4027 | 0 | auto targetCRSProj = |
4028 | 0 | dynamic_cast<const crs::ProjectedCRS *>(l_targetCRS.get()); |
4029 | 0 | if (!targetCRSProj) { |
4030 | 0 | checkIfCompatEngineeringCRS(l_targetCRS); |
4031 | 0 | } |
4032 | |
|
4033 | 0 | if (sourceCRSProj) { |
4034 | 0 | formatter->startInversion(); |
4035 | 0 | sourceCRSProj->addUnitConvertAndAxisSwap(formatter, false); |
4036 | 0 | formatter->stopInversion(); |
4037 | 0 | } |
4038 | |
|
4039 | 0 | if (eastingOffset != 0.0 || northingOffset != 0.0) { |
4040 | 0 | formatter->addStep("affine"); |
4041 | 0 | formatter->addParam("xoff", eastingOffset); |
4042 | 0 | formatter->addParam("yoff", northingOffset); |
4043 | 0 | } |
4044 | |
|
4045 | 0 | if (targetCRSProj) { |
4046 | 0 | targetCRSProj->addUnitConvertAndAxisSwap(formatter, false); |
4047 | 0 | } |
4048 | |
|
4049 | 0 | return true; |
4050 | 0 | } |
4051 | | |
4052 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET) { |
4053 | |
|
4054 | 0 | const crs::CRS *srcCRS = sourceCRS().get(); |
4055 | 0 | const crs::CRS *tgtCRS = targetCRS().get(); |
4056 | |
|
4057 | 0 | const auto sourceCRSCompound = |
4058 | 0 | dynamic_cast<const crs::CompoundCRS *>(srcCRS); |
4059 | 0 | const auto targetCRSCompound = |
4060 | 0 | dynamic_cast<const crs::CompoundCRS *>(tgtCRS); |
4061 | 0 | if (sourceCRSCompound && targetCRSCompound && |
4062 | 0 | sourceCRSCompound->componentReferenceSystems()[0]->_isEquivalentTo( |
4063 | 0 | targetCRSCompound->componentReferenceSystems()[0].get(), |
4064 | 0 | util::IComparable::Criterion::EQUIVALENT)) { |
4065 | 0 | srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get(); |
4066 | 0 | tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get(); |
4067 | 0 | } |
4068 | |
|
4069 | 0 | auto sourceCRSVert = dynamic_cast<const crs::VerticalCRS *>(srcCRS); |
4070 | 0 | if (!sourceCRSVert) { |
4071 | 0 | throw io::FormattingException( |
4072 | 0 | "Can apply Vertical offset only to VerticalCRS"); |
4073 | 0 | } |
4074 | | |
4075 | 0 | auto targetCRSVert = dynamic_cast<const crs::VerticalCRS *>(tgtCRS); |
4076 | 0 | if (!targetCRSVert) { |
4077 | 0 | throw io::FormattingException( |
4078 | 0 | "Can apply Vertical offset only to VerticalCRS"); |
4079 | 0 | } |
4080 | | |
4081 | 0 | auto offsetHeight = |
4082 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); |
4083 | |
|
4084 | 0 | formatter->startInversion(); |
4085 | 0 | sourceCRSVert->addLinearUnitConvert(formatter); |
4086 | 0 | formatter->stopInversion(); |
4087 | |
|
4088 | 0 | if (offsetHeight != 0) { |
4089 | 0 | formatter->addStep("geogoffset"); |
4090 | 0 | formatter->addParam("dh", offsetHeight); |
4091 | 0 | } |
4092 | |
|
4093 | 0 | targetCRSVert->addLinearUnitConvert(formatter); |
4094 | |
|
4095 | 0 | return true; |
4096 | 0 | } |
4097 | | |
4098 | 0 | if (methodEPSGCode == |
4099 | 0 | EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT || |
4100 | 0 | methodEPSGCode == |
4101 | 0 | EPSG_CODE_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT) { |
4102 | 0 | const crs::CRS *tgtCRS = targetCRS().get(); |
4103 | 0 | if (const auto targetCRSCompound = |
4104 | 0 | dynamic_cast<const crs::CompoundCRS *>(tgtCRS)) { |
4105 | 0 | tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get(); |
4106 | 0 | } |
4107 | 0 | auto targetCRSVert = dynamic_cast<const crs::VerticalCRS *>(tgtCRS); |
4108 | 0 | if (!targetCRSVert) { |
4109 | 0 | throw io::FormattingException( |
4110 | 0 | "Can apply Geographic3D to GravityRelatedHeight only to " |
4111 | 0 | "VerticalCRS"); |
4112 | 0 | } |
4113 | | |
4114 | 0 | auto geoidHeight = |
4115 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT); |
4116 | |
|
4117 | 0 | if (geoidHeight != 0) { |
4118 | 0 | formatter->addStep("affine"); |
4119 | | // In the forward direction (Geographic3D to GravityRelatedHeight) |
4120 | | // we subtract the geoid height |
4121 | 0 | formatter->addParam("zoff", |
4122 | 0 | isMethodInverseOf ? geoidHeight : -geoidHeight); |
4123 | 0 | } |
4124 | |
|
4125 | 0 | targetCRSVert->addLinearUnitConvert(formatter); |
4126 | |
|
4127 | 0 | return true; |
4128 | 0 | } else if ( |
4129 | 0 | ci_equal(l_method->nameStr(), |
4130 | 0 | INVERSE_OF + |
4131 | 0 | EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GRAVITYRELATEDHEIGHT) || |
4132 | 0 | ci_equal( |
4133 | 0 | l_method->nameStr(), |
4134 | 0 | INVERSE_OF + |
4135 | 0 | EPSG_NAME_METHOD_GEOGRAPHIC3D_TO_GEOG2D_GRAVITYRELATEDHEIGHT)) { |
4136 | 0 | const crs::CRS *srcCRS = sourceCRS().get(); |
4137 | 0 | if (const auto sourceCRSCompound = |
4138 | 0 | dynamic_cast<const crs::CompoundCRS *>(srcCRS)) { |
4139 | 0 | srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get(); |
4140 | 0 | } |
4141 | 0 | auto sourceCRSVert = dynamic_cast<const crs::VerticalCRS *>(srcCRS); |
4142 | 0 | if (!sourceCRSVert) { |
4143 | 0 | throw io::FormattingException( |
4144 | 0 | "Can apply Inverse of Geographic3D to GravityRelatedHeight " |
4145 | 0 | "only to VerticalCRS"); |
4146 | 0 | } |
4147 | | |
4148 | 0 | auto geoidHeight = |
4149 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_GEOID_HEIGHT); |
4150 | |
|
4151 | 0 | formatter->startInversion(); |
4152 | 0 | sourceCRSVert->addLinearUnitConvert(formatter); |
4153 | 0 | formatter->stopInversion(); |
4154 | |
|
4155 | 0 | if (geoidHeight != 0) { |
4156 | 0 | formatter->addStep("affine"); |
4157 | | // In the forward direction (Geographic3D to GravityRelatedHeight) |
4158 | | // we subtract the geoid height |
4159 | 0 | formatter->addParam("zoff", geoidHeight); |
4160 | 0 | } |
4161 | |
|
4162 | 0 | return true; |
4163 | 0 | } |
4164 | | |
4165 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_VERTICAL_OFFSET_AND_SLOPE) { |
4166 | |
|
4167 | 0 | const crs::CRS *srcCRS = sourceCRS().get(); |
4168 | 0 | const crs::CRS *tgtCRS = targetCRS().get(); |
4169 | |
|
4170 | 0 | const auto sourceCRSCompound = |
4171 | 0 | dynamic_cast<const crs::CompoundCRS *>(srcCRS); |
4172 | 0 | const auto targetCRSCompound = |
4173 | 0 | dynamic_cast<const crs::CompoundCRS *>(tgtCRS); |
4174 | 0 | if (sourceCRSCompound && targetCRSCompound && |
4175 | 0 | sourceCRSCompound->componentReferenceSystems()[0]->_isEquivalentTo( |
4176 | 0 | targetCRSCompound->componentReferenceSystems()[0].get(), |
4177 | 0 | util::IComparable::Criterion::EQUIVALENT)) { |
4178 | 0 | srcCRS = sourceCRSCompound->componentReferenceSystems()[1].get(); |
4179 | 0 | tgtCRS = targetCRSCompound->componentReferenceSystems()[1].get(); |
4180 | 0 | } |
4181 | |
|
4182 | 0 | auto sourceCRSVert = dynamic_cast<const crs::VerticalCRS *>(srcCRS); |
4183 | 0 | if (!sourceCRSVert) { |
4184 | 0 | throw io::FormattingException( |
4185 | 0 | "Can apply Vertical offset and slope only to VerticalCRS"); |
4186 | 0 | } |
4187 | | |
4188 | 0 | auto targetCRSVert = dynamic_cast<const crs::VerticalCRS *>(tgtCRS); |
4189 | 0 | if (!targetCRSVert) { |
4190 | 0 | throw io::FormattingException( |
4191 | 0 | "Can apply Vertical offset and slope only to VerticalCRS"); |
4192 | 0 | } |
4193 | | |
4194 | 0 | const auto latitudeEvaluationPoint = |
4195 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_ORDINATE_1_EVAL_POINT, |
4196 | 0 | common::UnitOfMeasure::DEGREE); |
4197 | 0 | const auto longitudeEvaluationPoint = |
4198 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_ORDINATE_2_EVAL_POINT, |
4199 | 0 | common::UnitOfMeasure::DEGREE); |
4200 | 0 | const auto offsetHeight = |
4201 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_VERTICAL_OFFSET); |
4202 | 0 | const auto inclinationLatitude = |
4203 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_INCLINATION_IN_LATITUDE, |
4204 | 0 | common::UnitOfMeasure::ARC_SECOND); |
4205 | 0 | const auto inclinationLongitude = |
4206 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_INCLINATION_IN_LONGITUDE, |
4207 | 0 | common::UnitOfMeasure::ARC_SECOND); |
4208 | |
|
4209 | 0 | formatter->startInversion(); |
4210 | 0 | sourceCRSVert->addLinearUnitConvert(formatter); |
4211 | 0 | formatter->stopInversion(); |
4212 | |
|
4213 | 0 | formatter->addStep("vertoffset"); |
4214 | 0 | formatter->addParam("lat_0", latitudeEvaluationPoint); |
4215 | 0 | formatter->addParam("lon_0", longitudeEvaluationPoint); |
4216 | 0 | formatter->addParam("dh", offsetHeight); |
4217 | 0 | formatter->addParam("slope_lat", inclinationLatitude); |
4218 | 0 | formatter->addParam("slope_lon", inclinationLongitude); |
4219 | |
|
4220 | 0 | targetCRSVert->addLinearUnitConvert(formatter); |
4221 | |
|
4222 | 0 | return true; |
4223 | 0 | } |
4224 | | |
4225 | | // Substitute grid names with PROJ friendly names. |
4226 | 0 | if (formatter->databaseContext()) { |
4227 | 0 | auto alternate = substitutePROJAlternativeGridNames( |
4228 | 0 | NN_NO_CHECK(formatter->databaseContext())); |
4229 | 0 | auto self = NN_NO_CHECK(std::dynamic_pointer_cast<Transformation>( |
4230 | 0 | shared_from_this().as_nullable())); |
4231 | |
|
4232 | 0 | if (alternate != self) { |
4233 | 0 | alternate->_exportToPROJString(formatter); |
4234 | 0 | return true; |
4235 | 0 | } |
4236 | 0 | } |
4237 | | |
4238 | 0 | const auto &NTv1Filename = _getNTv1Filename(this, true); |
4239 | 0 | const auto &NTv2Filename = _getNTv2Filename(this, true); |
4240 | 0 | const auto &CTABLE2Filename = _getCTABLE2Filename(this, true); |
4241 | 0 | const auto &HorizontalShiftGTIFFFilename = |
4242 | 0 | _getHorizontalShiftGTIFFFilename(this, true); |
4243 | 0 | const auto &hGridShiftFilename = !HorizontalShiftGTIFFFilename.empty() |
4244 | 0 | ? HorizontalShiftGTIFFFilename |
4245 | 0 | : !NTv1Filename.empty() ? NTv1Filename |
4246 | 0 | : !NTv2Filename.empty() ? NTv2Filename |
4247 | 0 | : CTABLE2Filename; |
4248 | 0 | if (!hGridShiftFilename.empty()) { |
4249 | 0 | auto l_sourceCRS = sourceCRS(); |
4250 | 0 | auto sourceCRSGeog = |
4251 | 0 | l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( |
4252 | 0 | NN_NO_CHECK(l_sourceCRS)) |
4253 | 0 | : nullptr; |
4254 | 0 | if (!sourceCRSGeog) { |
4255 | 0 | throw io::FormattingException( |
4256 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4257 | 0 | } |
4258 | | |
4259 | 0 | auto l_targetCRS = targetCRS(); |
4260 | 0 | auto targetCRSGeog = |
4261 | 0 | l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( |
4262 | 0 | NN_NO_CHECK(l_targetCRS)) |
4263 | 0 | : nullptr; |
4264 | 0 | if (!targetCRSGeog) { |
4265 | 0 | throw io::FormattingException( |
4266 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4267 | 0 | } |
4268 | | |
4269 | 0 | if (!formatter->omitHorizontalConversionInVertTransformation()) { |
4270 | 0 | formatter->startInversion(); |
4271 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4272 | 0 | formatter->stopInversion(); |
4273 | 0 | } |
4274 | |
|
4275 | 0 | if (isMethodInverseOf) { |
4276 | 0 | formatter->startInversion(); |
4277 | 0 | } |
4278 | 0 | if (methodName.find(PROJ_WKT2_NAME_METHOD_GENERAL_SHIFT_GTIFF) != |
4279 | 0 | std::string::npos) { |
4280 | 0 | formatter->addStep("gridshift"); |
4281 | 0 | if (sourceCRSGeog->coordinateSystem()->axisList().size() == 2 && |
4282 | 0 | parameterValue( |
4283 | 0 | PROJ_WKT2_PARAMETER_LATITUDE_LONGITUDE_ELLIPOISDAL_HEIGHT_DIFFERENCE_FILE, |
4284 | 0 | 0) != nullptr) { |
4285 | 0 | formatter->addParam("no_z_transform"); |
4286 | 0 | } |
4287 | 0 | } else |
4288 | 0 | formatter->addStep("hgridshift"); |
4289 | 0 | formatter->addParam("grids", hGridShiftFilename); |
4290 | 0 | if (isMethodInverseOf) { |
4291 | 0 | formatter->stopInversion(); |
4292 | 0 | } |
4293 | |
|
4294 | 0 | if (!formatter->omitHorizontalConversionInVertTransformation()) { |
4295 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4296 | 0 | } |
4297 | |
|
4298 | 0 | return true; |
4299 | 0 | } |
4300 | | |
4301 | 0 | const auto &geocentricTranslationFilename = |
4302 | 0 | _getGeocentricTranslationFilename(this, true); |
4303 | 0 | if (!geocentricTranslationFilename.empty()) { |
4304 | 0 | auto sourceCRSGeog = |
4305 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); |
4306 | 0 | if (!sourceCRSGeog) { |
4307 | 0 | throw io::FormattingException( |
4308 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4309 | 0 | } |
4310 | | |
4311 | 0 | auto targetCRSGeog = |
4312 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); |
4313 | 0 | if (!targetCRSGeog) { |
4314 | 0 | throw io::FormattingException( |
4315 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4316 | 0 | } |
4317 | | |
4318 | 0 | const auto &interpCRS = interpolationCRS(); |
4319 | 0 | if (!interpCRS) { |
4320 | 0 | throw io::FormattingException( |
4321 | 0 | "InterpolationCRS required " |
4322 | 0 | "for" |
4323 | 0 | " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN); |
4324 | 0 | } |
4325 | 0 | const bool interpIsSrc = interpCRS->_isEquivalentTo( |
4326 | 0 | sourceCRS().get(), |
4327 | 0 | util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS); |
4328 | 0 | const bool interpIsTarget = interpCRS->_isEquivalentTo( |
4329 | 0 | targetCRS().get(), |
4330 | 0 | util::IComparable::Criterion::EQUIVALENT_EXCEPT_AXIS_ORDER_GEOGCRS); |
4331 | 0 | if (!interpIsSrc && !interpIsTarget) { |
4332 | 0 | throw io::FormattingException( |
4333 | 0 | "For" |
4334 | 0 | " " EPSG_NAME_METHOD_GEOCENTRIC_TRANSLATIONS_GEOG2D_DOMAIN_BY_GRID_IGN |
4335 | 0 | ", interpolation CRS should be the source or target CRS"); |
4336 | 0 | } |
4337 | | |
4338 | 0 | formatter->startInversion(); |
4339 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4340 | 0 | formatter->stopInversion(); |
4341 | |
|
4342 | 0 | if (isMethodInverseOf) { |
4343 | 0 | formatter->startInversion(); |
4344 | 0 | } |
4345 | |
|
4346 | 0 | formatter->addStep("push"); |
4347 | 0 | formatter->addParam("v_3"); |
4348 | |
|
4349 | 0 | formatter->addStep("cart"); |
4350 | 0 | sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4351 | |
|
4352 | 0 | formatter->addStep("xyzgridshift"); |
4353 | 0 | formatter->addParam("grids", geocentricTranslationFilename); |
4354 | 0 | formatter->addParam("grid_ref", |
4355 | 0 | interpIsTarget ? "output_crs" : "input_crs"); |
4356 | 0 | (interpIsTarget ? targetCRSGeog : sourceCRSGeog) |
4357 | 0 | ->ellipsoid() |
4358 | 0 | ->_exportToPROJString(formatter); |
4359 | |
|
4360 | 0 | formatter->startInversion(); |
4361 | 0 | formatter->addStep("cart"); |
4362 | 0 | targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4363 | 0 | formatter->stopInversion(); |
4364 | |
|
4365 | 0 | formatter->addStep("pop"); |
4366 | 0 | formatter->addParam("v_3"); |
4367 | |
|
4368 | 0 | if (isMethodInverseOf) { |
4369 | 0 | formatter->stopInversion(); |
4370 | 0 | } |
4371 | |
|
4372 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4373 | |
|
4374 | 0 | return true; |
4375 | 0 | } |
4376 | | |
4377 | 0 | const auto &geographic3DOffsetByVelocityGridFilename = |
4378 | 0 | _getGeographic3DOffsetByVelocityGridFilename(this, true); |
4379 | 0 | if (!geographic3DOffsetByVelocityGridFilename.empty()) { |
4380 | 0 | auto sourceCRSGeog = |
4381 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); |
4382 | 0 | if (!sourceCRSGeog) { |
4383 | 0 | throw io::FormattingException( |
4384 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4385 | 0 | } |
4386 | | |
4387 | 0 | const auto &srcEpoch = |
4388 | 0 | sourceCRSGeog->datumNonNull(formatter->databaseContext()) |
4389 | 0 | ->anchorEpoch(); |
4390 | 0 | if (!srcEpoch.has_value()) { |
4391 | 0 | throw io::FormattingException( |
4392 | 0 | "For" |
4393 | 0 | " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL |
4394 | 0 | ", missing epoch for source CRS"); |
4395 | 0 | } |
4396 | | |
4397 | 0 | auto targetCRSGeog = |
4398 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); |
4399 | 0 | if (!targetCRSGeog) { |
4400 | 0 | throw io::FormattingException( |
4401 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4402 | 0 | } |
4403 | | |
4404 | 0 | const auto &dstEpoch = |
4405 | 0 | targetCRSGeog->datumNonNull(formatter->databaseContext()) |
4406 | 0 | ->anchorEpoch(); |
4407 | 0 | if (!dstEpoch.has_value()) { |
4408 | 0 | throw io::FormattingException( |
4409 | 0 | "For" |
4410 | 0 | " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL |
4411 | 0 | ", missing epoch for target CRS"); |
4412 | 0 | } |
4413 | | |
4414 | 0 | const auto &interpCRS = interpolationCRS(); |
4415 | 0 | if (!interpCRS) { |
4416 | 0 | throw io::FormattingException( |
4417 | 0 | "InterpolationCRS required " |
4418 | 0 | "for" |
4419 | 0 | " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL); |
4420 | 0 | } |
4421 | 0 | const bool interpIsSrc = interpCRS->_isEquivalentTo( |
4422 | 0 | sourceCRS()->demoteTo2D(std::string(), nullptr).get(), |
4423 | 0 | util::IComparable::Criterion::EQUIVALENT); |
4424 | 0 | const bool interpIsTarget = interpCRS->_isEquivalentTo( |
4425 | 0 | targetCRS()->demoteTo2D(std::string(), nullptr).get(), |
4426 | 0 | util::IComparable::Criterion::EQUIVALENT); |
4427 | 0 | if (!interpIsSrc && !interpIsTarget) { |
4428 | 0 | throw io::FormattingException( |
4429 | 0 | "For" |
4430 | 0 | " " EPSG_NAME_METHOD_GEOGRAPHIC3D_OFFSET_BY_VELOCITY_GRID_NTV2_VEL |
4431 | 0 | ", interpolation CRS should be the source or target CRS"); |
4432 | 0 | } |
4433 | | |
4434 | 0 | formatter->startInversion(); |
4435 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4436 | 0 | formatter->stopInversion(); |
4437 | |
|
4438 | 0 | if (isMethodInverseOf) { |
4439 | 0 | formatter->startInversion(); |
4440 | 0 | } |
4441 | |
|
4442 | 0 | const bool addPushPopV3 = |
4443 | 0 | ((sourceCRSGeog && |
4444 | 0 | sourceCRSGeog->coordinateSystem()->axisList().size() == 2) || |
4445 | 0 | (targetCRSGeog && |
4446 | 0 | targetCRSGeog->coordinateSystem()->axisList().size() == 2)); |
4447 | |
|
4448 | 0 | if (addPushPopV3) { |
4449 | 0 | formatter->addStep("push"); |
4450 | 0 | formatter->addParam("v_3"); |
4451 | 0 | } |
4452 | |
|
4453 | 0 | formatter->addStep("cart"); |
4454 | 0 | sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4455 | |
|
4456 | 0 | formatter->addStep("deformation"); |
4457 | |
|
4458 | 0 | const double sourceYear = |
4459 | 0 | srcEpoch->convertToUnit(common::UnitOfMeasure::YEAR); |
4460 | 0 | const double targetYear = |
4461 | 0 | dstEpoch->convertToUnit(common::UnitOfMeasure::YEAR); |
4462 | |
|
4463 | 0 | formatter->addParam("dt", targetYear - sourceYear); |
4464 | 0 | formatter->addParam("grids", geographic3DOffsetByVelocityGridFilename); |
4465 | 0 | (interpIsTarget ? targetCRSGeog : sourceCRSGeog) |
4466 | 0 | ->ellipsoid() |
4467 | 0 | ->_exportToPROJString(formatter); |
4468 | |
|
4469 | 0 | formatter->startInversion(); |
4470 | 0 | formatter->addStep("cart"); |
4471 | 0 | targetCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4472 | 0 | formatter->stopInversion(); |
4473 | |
|
4474 | 0 | if (addPushPopV3) { |
4475 | 0 | formatter->addStep("pop"); |
4476 | 0 | formatter->addParam("v_3"); |
4477 | 0 | } |
4478 | |
|
4479 | 0 | if (isMethodInverseOf) { |
4480 | 0 | formatter->stopInversion(); |
4481 | 0 | } |
4482 | |
|
4483 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4484 | |
|
4485 | 0 | return true; |
4486 | 0 | } |
4487 | | |
4488 | 0 | const auto &verticalOffsetByVelocityGridFilename = |
4489 | 0 | _getVerticalOffsetByVelocityGridFilename(this, true); |
4490 | 0 | if (!verticalOffsetByVelocityGridFilename.empty()) { |
4491 | |
|
4492 | 0 | const auto &interpCRS = interpolationCRS(); |
4493 | 0 | if (!interpCRS) { |
4494 | 0 | throw io::FormattingException( |
4495 | 0 | "InterpolationCRS required " |
4496 | 0 | "for" |
4497 | 0 | " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL); |
4498 | 0 | } |
4499 | | |
4500 | 0 | auto interpCRSGeog = |
4501 | 0 | dynamic_cast<const crs::GeographicCRS *>(interpCRS.get()); |
4502 | 0 | if (!interpCRSGeog) { |
4503 | 0 | throw io::FormattingException( |
4504 | 0 | concat("Can apply ", methodName, |
4505 | 0 | " only to a GeographicCRS interpolation CRS")); |
4506 | 0 | } |
4507 | | |
4508 | 0 | const auto vertSrc = |
4509 | 0 | dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get()); |
4510 | 0 | if (!vertSrc) { |
4511 | 0 | throw io::FormattingException(concat( |
4512 | 0 | "Can apply ", methodName, " only to a source VerticalCRS")); |
4513 | 0 | } |
4514 | | |
4515 | 0 | const auto &srcEpoch = |
4516 | 0 | vertSrc->datumNonNull(formatter->databaseContext())->anchorEpoch(); |
4517 | 0 | if (!srcEpoch.has_value()) { |
4518 | 0 | throw io::FormattingException( |
4519 | 0 | "For" |
4520 | 0 | " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL |
4521 | 0 | ", missing epoch for source CRS"); |
4522 | 0 | } |
4523 | | |
4524 | 0 | const auto vertDst = |
4525 | 0 | dynamic_cast<const crs::VerticalCRS *>(targetCRS().get()); |
4526 | 0 | if (!vertDst) { |
4527 | 0 | throw io::FormattingException(concat( |
4528 | 0 | "Can apply ", methodName, " only to a target VerticalCRS")); |
4529 | 0 | } |
4530 | | |
4531 | 0 | const auto &dstEpoch = |
4532 | 0 | vertDst->datumNonNull(formatter->databaseContext())->anchorEpoch(); |
4533 | 0 | if (!dstEpoch.has_value()) { |
4534 | 0 | throw io::FormattingException( |
4535 | 0 | "For" |
4536 | 0 | " " EPSG_NAME_METHOD_VERTICAL_OFFSET_USING_NEU_VELOCITY_GRID_NTV2_VEL |
4537 | 0 | ", missing epoch for target CRS"); |
4538 | 0 | } |
4539 | | |
4540 | 0 | const double sourceYear = |
4541 | 0 | srcEpoch->convertToUnit(common::UnitOfMeasure::YEAR); |
4542 | 0 | const double targetYear = |
4543 | 0 | dstEpoch->convertToUnit(common::UnitOfMeasure::YEAR); |
4544 | |
|
4545 | 0 | if (isMethodInverseOf) { |
4546 | 0 | formatter->startInversion(); |
4547 | 0 | } |
4548 | 0 | formatter->addStep("push"); |
4549 | 0 | formatter->addParam("v_1"); |
4550 | 0 | formatter->addParam("v_2"); |
4551 | |
|
4552 | 0 | formatter->addStep("cart"); |
4553 | 0 | interpCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4554 | |
|
4555 | 0 | formatter->addStep("deformation"); |
4556 | 0 | formatter->addParam("dt", targetYear - sourceYear); |
4557 | 0 | formatter->addParam("grids", verticalOffsetByVelocityGridFilename); |
4558 | 0 | interpCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4559 | |
|
4560 | 0 | formatter->startInversion(); |
4561 | 0 | formatter->addStep("cart"); |
4562 | 0 | interpCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4563 | 0 | formatter->stopInversion(); |
4564 | |
|
4565 | 0 | formatter->addStep("pop"); |
4566 | 0 | formatter->addParam("v_1"); |
4567 | 0 | formatter->addParam("v_2"); |
4568 | |
|
4569 | 0 | if (isMethodInverseOf) { |
4570 | 0 | formatter->stopInversion(); |
4571 | 0 | } |
4572 | |
|
4573 | 0 | return true; |
4574 | 0 | } |
4575 | | |
4576 | 0 | const auto &heightFilename = _getHeightToGeographic3DFilename(this, true); |
4577 | 0 | if (!heightFilename.empty()) { |
4578 | 0 | auto l_targetCRS = targetCRS(); |
4579 | 0 | auto targetCRSGeog = |
4580 | 0 | l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( |
4581 | 0 | NN_NO_CHECK(l_targetCRS)) |
4582 | 0 | : nullptr; |
4583 | 0 | if (!targetCRSGeog) { |
4584 | 0 | throw io::FormattingException( |
4585 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4586 | 0 | } |
4587 | | |
4588 | 0 | if (!formatter->omitHorizontalConversionInVertTransformation()) { |
4589 | 0 | formatter->startInversion(); |
4590 | 0 | formatter->pushOmitZUnitConversion(); |
4591 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4592 | 0 | formatter->popOmitZUnitConversion(); |
4593 | 0 | formatter->stopInversion(); |
4594 | 0 | } |
4595 | |
|
4596 | 0 | if (isMethodInverseOf) { |
4597 | 0 | formatter->startInversion(); |
4598 | 0 | } |
4599 | 0 | formatter->addStep("vgridshift"); |
4600 | 0 | formatter->addParam("grids", heightFilename); |
4601 | 0 | formatter->addParam("multiplier", 1.0); |
4602 | 0 | if (isMethodInverseOf) { |
4603 | 0 | formatter->stopInversion(); |
4604 | 0 | } |
4605 | |
|
4606 | 0 | if (!formatter->omitHorizontalConversionInVertTransformation()) { |
4607 | 0 | formatter->pushOmitZUnitConversion(); |
4608 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4609 | 0 | formatter->popOmitZUnitConversion(); |
4610 | 0 | } |
4611 | |
|
4612 | 0 | return true; |
4613 | 0 | } |
4614 | | |
4615 | 0 | if (Transformation::isGeographic3DToGravityRelatedHeight(method(), true)) { |
4616 | 0 | auto fileParameter = |
4617 | 0 | parameterValue(EPSG_NAME_PARAMETER_GEOID_CORRECTION_FILENAME, |
4618 | 0 | EPSG_CODE_PARAMETER_GEOID_CORRECTION_FILENAME); |
4619 | 0 | if (fileParameter && |
4620 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
4621 | 0 | const auto &filename = fileParameter->valueFile(); |
4622 | |
|
4623 | 0 | auto l_sourceCRS = sourceCRS(); |
4624 | 0 | auto sourceCRSGeog = |
4625 | 0 | l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( |
4626 | 0 | NN_NO_CHECK(l_sourceCRS)) |
4627 | 0 | : nullptr; |
4628 | 0 | if (!sourceCRSGeog) { |
4629 | 0 | throw io::FormattingException( |
4630 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4631 | 0 | } |
4632 | | |
4633 | 0 | auto l_targetCRS = targetCRS(); |
4634 | 0 | auto targetVertCRS = |
4635 | 0 | l_targetCRS ? l_targetCRS->extractVerticalCRS() : nullptr; |
4636 | 0 | if (!targetVertCRS) { |
4637 | 0 | throw io::FormattingException( |
4638 | 0 | concat("Can apply ", methodName, |
4639 | 0 | " only to a target CRS that has a VerticalCRS")); |
4640 | 0 | } |
4641 | | |
4642 | 0 | if (!formatter->omitHorizontalConversionInVertTransformation()) { |
4643 | 0 | formatter->startInversion(); |
4644 | 0 | formatter->pushOmitZUnitConversion(); |
4645 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4646 | 0 | formatter->popOmitZUnitConversion(); |
4647 | 0 | formatter->stopInversion(); |
4648 | 0 | } |
4649 | |
|
4650 | 0 | bool doInversion = isMethodInverseOf; |
4651 | | // The EPSG Geog3DToHeight is the reverse convention of PROJ ! |
4652 | 0 | doInversion = !doInversion; |
4653 | 0 | if (doInversion) { |
4654 | 0 | formatter->startInversion(); |
4655 | 0 | } |
4656 | | |
4657 | | // For Geographic3D to Depth methods, we rely on the vertical axis |
4658 | | // direction instead of the name/code of the transformation method. |
4659 | 0 | if (targetVertCRS->coordinateSystem()->axisList()[0]->direction() == |
4660 | 0 | cs::AxisDirection::DOWN) { |
4661 | 0 | formatter->addStep("axisswap"); |
4662 | 0 | formatter->addParam("order", "1,2,-3"); |
4663 | 0 | } |
4664 | |
|
4665 | 0 | formatter->addStep("vgridshift"); |
4666 | 0 | formatter->addParam("grids", filename); |
4667 | 0 | formatter->addParam("multiplier", 1.0); |
4668 | 0 | if (doInversion) { |
4669 | 0 | formatter->stopInversion(); |
4670 | 0 | } |
4671 | |
|
4672 | 0 | if (!formatter->omitHorizontalConversionInVertTransformation()) { |
4673 | 0 | formatter->pushOmitZUnitConversion(); |
4674 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4675 | 0 | formatter->popOmitZUnitConversion(); |
4676 | 0 | } |
4677 | |
|
4678 | 0 | return true; |
4679 | 0 | } |
4680 | 0 | } |
4681 | | |
4682 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_VERTCON) { |
4683 | 0 | auto fileParameter = |
4684 | 0 | parameterValue(EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, |
4685 | 0 | EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE); |
4686 | 0 | if (fileParameter && |
4687 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
4688 | 0 | formatter->addStep("vgridshift"); |
4689 | 0 | formatter->addParam("grids", fileParameter->valueFile()); |
4690 | 0 | if (fileParameter->valueFile().find(".tif") != std::string::npos) { |
4691 | 0 | formatter->addParam("multiplier", 1.0); |
4692 | 0 | } else { |
4693 | | // The vertcon grids go from NGVD 29 to NAVD 88, with units |
4694 | | // in millimeter (see |
4695 | | // https://github.com/OSGeo/proj.4/issues/1071), for gtx files |
4696 | 0 | formatter->addParam("multiplier", 0.001); |
4697 | 0 | } |
4698 | 0 | return true; |
4699 | 0 | } |
4700 | 0 | } |
4701 | | |
4702 | 0 | bool reverseOffsetSign = false; |
4703 | 0 | if (isRegularVerticalGridMethod(methodEPSGCode, reverseOffsetSign)) { |
4704 | 0 | int parameterCode = EPSG_CODE_PARAMETER_VERTICAL_OFFSET_FILE; |
4705 | 0 | auto fileParameter = parameterValue( |
4706 | 0 | EPSG_NAME_PARAMETER_VERTICAL_OFFSET_FILE, parameterCode); |
4707 | 0 | if (!fileParameter) { |
4708 | 0 | parameterCode = EPSG_CODE_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE; |
4709 | 0 | fileParameter = parameterValue( |
4710 | 0 | EPSG_NAME_PARAMETER_GEOID_MODEL_DIFFERENCE_FILE, parameterCode); |
4711 | 0 | } |
4712 | 0 | if (fileParameter && |
4713 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
4714 | 0 | formatter->addStep("vgridshift"); |
4715 | 0 | formatter->addParam("grids", fileParameter->valueFile()); |
4716 | 0 | formatter->addParam("multiplier", reverseOffsetSign ? -1.0 : 1.0); |
4717 | 0 | return true; |
4718 | 0 | } |
4719 | 0 | } |
4720 | | |
4721 | 0 | if (isLongitudeRotation()) { |
4722 | 0 | double offsetDeg = |
4723 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_LONGITUDE_OFFSET, |
4724 | 0 | common::UnitOfMeasure::DEGREE); |
4725 | 0 | auto l_sourceCRS = sourceCRS(); |
4726 | 0 | auto sourceCRSGeog = |
4727 | 0 | l_sourceCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( |
4728 | 0 | NN_NO_CHECK(l_sourceCRS)) |
4729 | 0 | : nullptr; |
4730 | 0 | if (!sourceCRSGeog) { |
4731 | 0 | throw io::FormattingException( |
4732 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4733 | 0 | } |
4734 | | |
4735 | 0 | auto l_targetCRS = targetCRS(); |
4736 | 0 | auto targetCRSGeog = |
4737 | 0 | l_targetCRS ? extractGeographicCRSIfGeographicCRSOrEquivalent( |
4738 | 0 | NN_NO_CHECK(l_targetCRS)) |
4739 | 0 | : nullptr; |
4740 | 0 | if (!targetCRSGeog) { |
4741 | 0 | throw io::FormattingException( |
4742 | 0 | concat("Can apply ", methodName + " only to GeographicCRS")); |
4743 | 0 | } |
4744 | | |
4745 | 0 | if (!sourceCRSGeog->ellipsoid()->_isEquivalentTo( |
4746 | 0 | targetCRSGeog->ellipsoid().get(), |
4747 | 0 | util::IComparable::Criterion::EQUIVALENT)) { |
4748 | | // This is arguable if we should check this... |
4749 | 0 | throw io::FormattingException("Can apply Longitude rotation " |
4750 | 0 | "only to SRS with same " |
4751 | 0 | "ellipsoid"); |
4752 | 0 | } |
4753 | | |
4754 | 0 | formatter->startInversion(); |
4755 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4756 | 0 | formatter->stopInversion(); |
4757 | |
|
4758 | 0 | bool done = false; |
4759 | 0 | if (offsetDeg != 0.0) { |
4760 | | // Optimization: as we are doing nominally a +step=inv, |
4761 | | // if the negation of the offset value is a well-known name, |
4762 | | // then use forward case with this name. |
4763 | 0 | auto projPMName = datum::PrimeMeridian::getPROJStringWellKnownName( |
4764 | 0 | common::Angle(-offsetDeg)); |
4765 | 0 | if (!projPMName.empty()) { |
4766 | 0 | done = true; |
4767 | 0 | formatter->addStep("longlat"); |
4768 | 0 | sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4769 | 0 | formatter->addParam("pm", projPMName); |
4770 | 0 | } |
4771 | 0 | } |
4772 | 0 | if (!done) { |
4773 | | // To actually add the offset, we must use the reverse longlat |
4774 | | // operation. |
4775 | 0 | formatter->startInversion(); |
4776 | 0 | formatter->addStep("longlat"); |
4777 | 0 | sourceCRSGeog->ellipsoid()->_exportToPROJString(formatter); |
4778 | 0 | datum::PrimeMeridian::create(util::PropertyMap(), |
4779 | 0 | common::Angle(offsetDeg)) |
4780 | 0 | ->_exportToPROJString(formatter); |
4781 | 0 | formatter->stopInversion(); |
4782 | 0 | } |
4783 | |
|
4784 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4785 | |
|
4786 | 0 | return true; |
4787 | 0 | } |
4788 | | |
4789 | 0 | if (methodEPSGCode == EPSG_CODE_METHOD_NEW_ZEALAND_DEFORMATION_MODEL) { |
4790 | 0 | auto sourceCRSGeog = |
4791 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); |
4792 | 0 | if (!sourceCRSGeog) { |
4793 | 0 | throw io::FormattingException( |
4794 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4795 | 0 | } |
4796 | | |
4797 | 0 | auto targetCRSGeog = |
4798 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); |
4799 | 0 | if (!targetCRSGeog) { |
4800 | 0 | throw io::FormattingException( |
4801 | 0 | concat("Can apply ", methodName, " only to GeographicCRS")); |
4802 | 0 | } |
4803 | | |
4804 | 0 | auto fileParameter = |
4805 | 0 | parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
4806 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); |
4807 | 0 | if (fileParameter && |
4808 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
4809 | |
|
4810 | 0 | formatter->startInversion(); |
4811 | 0 | sourceCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4812 | 0 | formatter->stopInversion(); |
4813 | |
|
4814 | 0 | double sourceYear = |
4815 | 0 | sourceCoordinateEpoch().has_value() |
4816 | 0 | ? getRoundedEpochInDecimalYear( |
4817 | 0 | sourceCoordinateEpoch() |
4818 | 0 | ->coordinateEpoch() |
4819 | 0 | .convertToUnit(common::UnitOfMeasure::YEAR)) |
4820 | 0 | : 0; |
4821 | 0 | double targetYear = |
4822 | 0 | targetCoordinateEpoch().has_value() |
4823 | 0 | ? getRoundedEpochInDecimalYear( |
4824 | 0 | targetCoordinateEpoch() |
4825 | 0 | ->coordinateEpoch() |
4826 | 0 | .convertToUnit(common::UnitOfMeasure::YEAR)) |
4827 | 0 | : 0; |
4828 | 0 | if (sourceYear > 0 && targetYear == 0) |
4829 | 0 | targetYear = sourceYear; |
4830 | 0 | else if (targetYear > 0 && sourceYear == 0) |
4831 | 0 | sourceYear = targetYear; |
4832 | 0 | if (sourceYear > 0) { |
4833 | 0 | formatter->addStep("set"); |
4834 | 0 | formatter->addParam("v_4", sourceYear); |
4835 | 0 | } |
4836 | |
|
4837 | 0 | if (isMethodInverseOf) { |
4838 | 0 | formatter->startInversion(); |
4839 | 0 | } |
4840 | | |
4841 | | // Operations are registered in EPSG with inverse order as |
4842 | | // the +proj=defmodel implementation |
4843 | 0 | formatter->startInversion(); |
4844 | 0 | formatter->addStep("defmodel"); |
4845 | 0 | formatter->addParam("model", fileParameter->valueFile()); |
4846 | 0 | formatter->stopInversion(); |
4847 | |
|
4848 | 0 | if (isMethodInverseOf) { |
4849 | 0 | formatter->stopInversion(); |
4850 | 0 | } |
4851 | |
|
4852 | 0 | if (targetYear > 0) { |
4853 | 0 | formatter->addStep("set"); |
4854 | 0 | formatter->addParam("v_4", targetYear); |
4855 | 0 | } |
4856 | |
|
4857 | 0 | targetCRSGeog->addAngularUnitConvertAndAxisSwap(formatter); |
4858 | |
|
4859 | 0 | return true; |
4860 | 0 | } |
4861 | 0 | } |
4862 | | |
4863 | 0 | if (methodEPSGCode == |
4864 | 0 | EPSG_CODE_METHOD_CARTESIAN_GRID_OFFSETS_BY_TIN_INTERPOLATION_JSON) { |
4865 | 0 | auto sourceCRSProj = |
4866 | 0 | dynamic_cast<const crs::ProjectedCRS *>(sourceCRS().get()); |
4867 | 0 | if (!sourceCRSProj) { |
4868 | 0 | throw io::FormattingException( |
4869 | 0 | concat("Can apply ", methodName, " only to ProjectedCRS")); |
4870 | 0 | } |
4871 | | |
4872 | 0 | auto targetCRSProj = |
4873 | 0 | dynamic_cast<const crs::ProjectedCRS *>(targetCRS().get()); |
4874 | 0 | if (!targetCRSProj) { |
4875 | 0 | throw io::FormattingException( |
4876 | 0 | concat("Can apply ", methodName, " only to ProjectedCRS")); |
4877 | 0 | } |
4878 | | |
4879 | 0 | auto fileParameter = |
4880 | 0 | parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE, |
4881 | 0 | EPSG_CODE_PARAMETER_TIN_OFFSET_FILE); |
4882 | 0 | if (fileParameter && |
4883 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
4884 | |
|
4885 | 0 | formatter->startInversion(); |
4886 | 0 | sourceCRSProj->addUnitConvertAndAxisSwap(formatter, false); |
4887 | 0 | formatter->stopInversion(); |
4888 | |
|
4889 | 0 | if (isMethodInverseOf) { |
4890 | 0 | formatter->startInversion(); |
4891 | 0 | } |
4892 | |
|
4893 | 0 | formatter->addStep("tinshift"); |
4894 | 0 | formatter->addParam("file", fileParameter->valueFile()); |
4895 | |
|
4896 | 0 | if (isMethodInverseOf) { |
4897 | 0 | formatter->stopInversion(); |
4898 | 0 | } |
4899 | |
|
4900 | 0 | targetCRSProj->addUnitConvertAndAxisSwap(formatter, false); |
4901 | |
|
4902 | 0 | return true; |
4903 | 0 | } |
4904 | 0 | } |
4905 | | |
4906 | 0 | if (methodEPSGCode == |
4907 | 0 | EPSG_CODE_METHOD_GEOGRAPHIC2D_OFFSETS_BY_TIN_INTERPOLATION_JSON) { |
4908 | 0 | auto sourceCRSGeog = |
4909 | 0 | dynamic_cast<const crs::GeographicCRS *>(sourceCRS().get()); |
4910 | 0 | if (!sourceCRSGeog) { |
4911 | 0 | throw io::FormattingException(concat( |
4912 | 0 | "Can apply ", methodName, " only to source GeographicCRS")); |
4913 | 0 | } |
4914 | | |
4915 | 0 | const auto hasDegreeUnit = [](const crs::GeographicCRS *crs) { |
4916 | 0 | const auto &axisList = crs->coordinateSystem()->axisList(); |
4917 | 0 | return axisList[0]->unit() == common::UnitOfMeasure::DEGREE; |
4918 | 0 | }; |
4919 | |
|
4920 | 0 | if (!hasDegreeUnit(sourceCRSGeog)) { |
4921 | 0 | throw io::FormattingException( |
4922 | 0 | concat("Can apply ", methodName, |
4923 | 0 | " only to source GeographicCRS with degree axis unit")); |
4924 | 0 | } |
4925 | | |
4926 | 0 | auto targetCRSGeog = |
4927 | 0 | dynamic_cast<const crs::GeographicCRS *>(targetCRS().get()); |
4928 | 0 | if (!targetCRSGeog) { |
4929 | 0 | throw io::FormattingException(concat( |
4930 | 0 | "Can apply ", methodName, " only to target GeographicCRS")); |
4931 | 0 | } |
4932 | | |
4933 | 0 | if (!hasDegreeUnit(targetCRSGeog)) { |
4934 | 0 | throw io::FormattingException( |
4935 | 0 | concat("Can apply ", methodName, |
4936 | 0 | " only to target GeographicCRS with degree axis unit")); |
4937 | 0 | } |
4938 | | |
4939 | 0 | auto fileParameter = |
4940 | 0 | parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE, |
4941 | 0 | EPSG_CODE_PARAMETER_TIN_OFFSET_FILE); |
4942 | 0 | if (fileParameter && |
4943 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
4944 | |
|
4945 | 0 | formatter->startInversion(); |
4946 | 0 | sourceCRSGeog->addAxisSwap(formatter); |
4947 | 0 | formatter->stopInversion(); |
4948 | |
|
4949 | 0 | if (isMethodInverseOf) { |
4950 | 0 | formatter->startInversion(); |
4951 | 0 | } |
4952 | |
|
4953 | 0 | formatter->addStep("tinshift"); |
4954 | 0 | formatter->addParam("file", fileParameter->valueFile()); |
4955 | |
|
4956 | 0 | if (isMethodInverseOf) { |
4957 | 0 | formatter->stopInversion(); |
4958 | 0 | } |
4959 | |
|
4960 | 0 | targetCRSGeog->addAxisSwap(formatter); |
4961 | |
|
4962 | 0 | return true; |
4963 | 0 | } |
4964 | 0 | } |
4965 | | |
4966 | 0 | if (methodEPSGCode == |
4967 | 0 | EPSG_CODE_METHOD_VERTICAL_OFFSET_BY_TIN_INTERPOLATION_JSON) { |
4968 | 0 | auto sourceCRSVert = |
4969 | 0 | dynamic_cast<const crs::VerticalCRS *>(sourceCRS().get()); |
4970 | 0 | if (!sourceCRSVert) { |
4971 | 0 | throw io::FormattingException( |
4972 | 0 | concat("Can apply ", methodName, " only to VerticalCRS")); |
4973 | 0 | } |
4974 | | |
4975 | 0 | auto targetCRSVert = |
4976 | 0 | dynamic_cast<const crs::VerticalCRS *>(targetCRS().get()); |
4977 | 0 | if (!targetCRSVert) { |
4978 | 0 | throw io::FormattingException( |
4979 | 0 | concat("Can apply ", methodName, " only to VerticalCRS")); |
4980 | 0 | } |
4981 | | |
4982 | 0 | auto fileParameter = |
4983 | 0 | parameterValue(EPSG_NAME_PARAMETER_TIN_OFFSET_FILE, |
4984 | 0 | EPSG_CODE_PARAMETER_TIN_OFFSET_FILE); |
4985 | |
|
4986 | 0 | if (fileParameter && |
4987 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
4988 | |
|
4989 | 0 | if (isMethodInverseOf) { |
4990 | 0 | formatter->startInversion(); |
4991 | 0 | } |
4992 | |
|
4993 | 0 | formatter->addStep("tinshift"); |
4994 | 0 | formatter->addParam("file", fileParameter->valueFile()); |
4995 | |
|
4996 | 0 | if (isMethodInverseOf) { |
4997 | 0 | formatter->stopInversion(); |
4998 | 0 | } |
4999 | |
|
5000 | 0 | return true; |
5001 | 0 | } |
5002 | 0 | } |
5003 | | |
5004 | 0 | if (methodEPSGCode == |
5005 | 0 | EPSG_CODE_METHOD_POSITION_VECTOR_GEOCENTRIC_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) { |
5006 | 0 | auto l_sourceCRS = sourceCRS(); |
5007 | 0 | auto l_targetCRS = targetCRS(); |
5008 | 0 | auto sourceCRSGeod = |
5009 | 0 | dynamic_cast<const crs::GeodeticCRS *>(l_sourceCRS.get()); |
5010 | 0 | auto targetCRSGeod = |
5011 | 0 | dynamic_cast<const crs::GeodeticCRS *>(l_targetCRS.get()); |
5012 | 0 | if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) { |
5013 | 0 | throw io::FormattingException( |
5014 | 0 | concat("Can apply ", methodName, " only to a geocentric CRS")); |
5015 | 0 | } |
5016 | 0 | if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) { |
5017 | 0 | throw io::FormattingException( |
5018 | 0 | concat("Can apply ", methodName, " only to a geocentric CRS")); |
5019 | 0 | } |
5020 | | |
5021 | 0 | auto fileParameter = |
5022 | 0 | parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
5023 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); |
5024 | 0 | if (!(fileParameter && |
5025 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME)) { |
5026 | 0 | throw io::FormattingException( |
5027 | 0 | "Missing parameter Point motion velocity grid file"); |
5028 | 0 | } |
5029 | | |
5030 | 0 | if (isMethodInverseOf) { |
5031 | 0 | formatter->startInversion(); |
5032 | 0 | } |
5033 | |
|
5034 | 0 | if (l_sourceCRS) { |
5035 | 0 | setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), |
5036 | 0 | false, "Helmert"); |
5037 | 0 | } |
5038 | |
|
5039 | 0 | formatter->addStep("helmert"); |
5040 | 0 | double x = |
5041 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_X_AXIS_TRANSLATION); |
5042 | 0 | double y = |
5043 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Y_AXIS_TRANSLATION); |
5044 | 0 | double z = |
5045 | 0 | parameterValueNumericAsSI(EPSG_CODE_PARAMETER_Z_AXIS_TRANSLATION); |
5046 | |
|
5047 | 0 | formatter->addParam("x", x); |
5048 | 0 | formatter->addParam("y", y); |
5049 | 0 | formatter->addParam("z", z); |
5050 | |
|
5051 | 0 | double rx = parameterValueNumeric(EPSG_CODE_PARAMETER_X_AXIS_ROTATION, |
5052 | 0 | common::UnitOfMeasure::ARC_SECOND); |
5053 | 0 | double ry = parameterValueNumeric(EPSG_CODE_PARAMETER_Y_AXIS_ROTATION, |
5054 | 0 | common::UnitOfMeasure::ARC_SECOND); |
5055 | 0 | double rz = parameterValueNumeric(EPSG_CODE_PARAMETER_Z_AXIS_ROTATION, |
5056 | 0 | common::UnitOfMeasure::ARC_SECOND); |
5057 | 0 | double scaleDiff = |
5058 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_SCALE_DIFFERENCE, |
5059 | 0 | common::UnitOfMeasure::PARTS_PER_MILLION); |
5060 | 0 | formatter->addParam("rx", rx); |
5061 | 0 | formatter->addParam("ry", ry); |
5062 | 0 | formatter->addParam("rz", rz); |
5063 | 0 | formatter->addParam("s", scaleDiff); |
5064 | 0 | formatter->addParam("convention", "position_vector"); |
5065 | |
|
5066 | 0 | formatter->addStep("deformation"); |
5067 | |
|
5068 | 0 | const bool hasTargetEpoch = [this]() { |
5069 | 0 | const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH); |
5070 | 0 | return val && val->type() == ParameterValue::Type::MEASURE; |
5071 | 0 | }(); |
5072 | 0 | if (hasTargetEpoch) { |
5073 | 0 | const double targetYear = parameterValueNumeric( |
5074 | 0 | EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR); |
5075 | 0 | const bool hasSourceEpoch = [this]() { |
5076 | 0 | const auto &val = |
5077 | 0 | parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH); |
5078 | 0 | return val && val->type() == ParameterValue::Type::MEASURE; |
5079 | 0 | }(); |
5080 | 0 | if (hasSourceEpoch) { |
5081 | 0 | const double sourceYear = |
5082 | 0 | parameterValueNumeric(EPSG_CODE_PARAMETER_SOURCE_EPOCH, |
5083 | 0 | common::UnitOfMeasure::YEAR); |
5084 | 0 | formatter->addParam("dt", targetYear - sourceYear); |
5085 | 0 | } else { |
5086 | 0 | formatter->addParam("t_epoch", targetYear); |
5087 | 0 | } |
5088 | 0 | } else { |
5089 | 0 | throw io::FormattingException("Missing parameter target epoch"); |
5090 | 0 | } |
5091 | | |
5092 | 0 | formatter->addParam("grids", fileParameter->valueFile()); |
5093 | 0 | sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter); |
5094 | |
|
5095 | 0 | if (l_targetCRS) { |
5096 | 0 | setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), |
5097 | 0 | false, "Helmert"); |
5098 | 0 | } |
5099 | |
|
5100 | 0 | if (isMethodInverseOf) { |
5101 | 0 | formatter->stopInversion(); |
5102 | 0 | } |
5103 | |
|
5104 | 0 | return true; |
5105 | 0 | } |
5106 | | |
5107 | 0 | if (methodEPSGCode == |
5108 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_USING_NEU_VELOCITY_GRID_GTG) { |
5109 | 0 | auto l_sourceCRS = sourceCRS(); |
5110 | 0 | auto l_targetCRS = targetCRS(); |
5111 | 0 | auto sourceCRSGeod = |
5112 | 0 | dynamic_cast<const crs::GeodeticCRS *>(l_sourceCRS.get()); |
5113 | 0 | auto targetCRSGeod = |
5114 | 0 | dynamic_cast<const crs::GeodeticCRS *>(l_targetCRS.get()); |
5115 | 0 | if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) { |
5116 | 0 | throw io::FormattingException( |
5117 | 0 | concat("Can apply ", methodName, " only to a geocentric CRS")); |
5118 | 0 | } |
5119 | 0 | if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) { |
5120 | 0 | throw io::FormattingException( |
5121 | 0 | concat("Can apply ", methodName, " only to a geocentric CRS")); |
5122 | 0 | } |
5123 | | |
5124 | 0 | auto fileParameter = |
5125 | 0 | parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
5126 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); |
5127 | 0 | if (!(fileParameter && |
5128 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME)) { |
5129 | 0 | throw io::FormattingException( |
5130 | 0 | "Missing parameter Point motion velocity grid file"); |
5131 | 0 | } |
5132 | | |
5133 | 0 | if (isMethodInverseOf) { |
5134 | 0 | formatter->startInversion(); |
5135 | 0 | } |
5136 | |
|
5137 | 0 | if (l_sourceCRS) { |
5138 | 0 | setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), |
5139 | 0 | false, "Helmert"); |
5140 | 0 | } |
5141 | |
|
5142 | 0 | const bool hasSourceEpoch = [this]() { |
5143 | 0 | const auto &val = parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH); |
5144 | 0 | return val && val->type() == ParameterValue::Type::MEASURE; |
5145 | 0 | }(); |
5146 | 0 | if (hasSourceEpoch) { |
5147 | 0 | throw io::FormattingException("Unsupported parameter source epoch"); |
5148 | 0 | } |
5149 | | |
5150 | 0 | const bool hasTargetEpoch = [this]() { |
5151 | 0 | const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH); |
5152 | 0 | return val && val->type() == ParameterValue::Type::MEASURE; |
5153 | 0 | }(); |
5154 | 0 | if (!hasTargetEpoch) { |
5155 | 0 | throw io::FormattingException("Missing parameter target epoch"); |
5156 | 0 | } |
5157 | | |
5158 | 0 | formatter->startInversion(); |
5159 | 0 | formatter->addStep("deformation"); |
5160 | |
|
5161 | 0 | const double targetYear = parameterValueNumeric( |
5162 | 0 | EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR); |
5163 | 0 | formatter->addParam("t_epoch", targetYear); |
5164 | |
|
5165 | 0 | formatter->addParam("grids", fileParameter->valueFile()); |
5166 | 0 | sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter); |
5167 | 0 | formatter->stopInversion(); |
5168 | |
|
5169 | 0 | if (l_targetCRS) { |
5170 | 0 | setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), |
5171 | 0 | false, "Helmert"); |
5172 | 0 | } |
5173 | |
|
5174 | 0 | if (isMethodInverseOf) { |
5175 | 0 | formatter->stopInversion(); |
5176 | 0 | } |
5177 | |
|
5178 | 0 | return true; |
5179 | 0 | } |
5180 | | |
5181 | 0 | if (methodEPSGCode == |
5182 | 0 | EPSG_CODE_METHOD_GEOCENTRIC_TRANSLATIONS_BY_GRID_GTG_AND_GEOCENTRIC_TRANSLATIONS_NEU_VELOCITIES_GTG) { |
5183 | 0 | auto l_sourceCRS = sourceCRS(); |
5184 | 0 | auto l_targetCRS = targetCRS(); |
5185 | 0 | auto sourceCRSGeod = |
5186 | 0 | dynamic_cast<const crs::GeodeticCRS *>(l_sourceCRS.get()); |
5187 | 0 | auto targetCRSGeod = |
5188 | 0 | dynamic_cast<const crs::GeodeticCRS *>(l_targetCRS.get()); |
5189 | 0 | if (!sourceCRSGeod || !sourceCRSGeod->isGeocentric()) { |
5190 | 0 | throw io::FormattingException( |
5191 | 0 | concat("Can apply ", methodName, " only to a geocentric CRS")); |
5192 | 0 | } |
5193 | 0 | if (!targetCRSGeod || !targetCRSGeod->isGeocentric()) { |
5194 | 0 | throw io::FormattingException( |
5195 | 0 | concat("Can apply ", methodName, " only to a geocentric CRS")); |
5196 | 0 | } |
5197 | | |
5198 | 0 | auto fileParameterGTF = |
5199 | 0 | parameterValue(EPSG_NAME_PARAMETER_GEOCENTRIC_TRANSLATION_FILE, |
5200 | 0 | EPSG_CODE_PARAMETER_GEOCENTRIC_TRANSLATION_FILE); |
5201 | 0 | if (!(fileParameterGTF && |
5202 | 0 | fileParameterGTF->type() == ParameterValue::Type::FILENAME)) { |
5203 | 0 | throw io::FormattingException( |
5204 | 0 | "Missing parameter Geocentric translation file"); |
5205 | 0 | } |
5206 | | |
5207 | 0 | auto fileParameterPMV = |
5208 | 0 | parameterValue(EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE, |
5209 | 0 | EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE); |
5210 | 0 | if (!(fileParameterPMV && |
5211 | 0 | fileParameterPMV->type() == ParameterValue::Type::FILENAME)) { |
5212 | 0 | throw io::FormattingException( |
5213 | 0 | "Missing parameter Point motion velocity grid file"); |
5214 | 0 | } |
5215 | | |
5216 | 0 | if (isMethodInverseOf) { |
5217 | 0 | formatter->startInversion(); |
5218 | 0 | } |
5219 | |
|
5220 | 0 | if (l_sourceCRS) { |
5221 | 0 | setupPROJGeodeticSourceCRS(formatter, NN_NO_CHECK(l_sourceCRS), |
5222 | 0 | false, "Helmert"); |
5223 | 0 | } |
5224 | |
|
5225 | 0 | const bool hasSourceEpoch = [this]() { |
5226 | 0 | const auto &val = parameterValue(EPSG_CODE_PARAMETER_SOURCE_EPOCH); |
5227 | 0 | return val && val->type() == ParameterValue::Type::MEASURE; |
5228 | 0 | }(); |
5229 | 0 | if (!hasSourceEpoch) { |
5230 | 0 | throw io::FormattingException("Missing parameter source epoch"); |
5231 | 0 | } |
5232 | | |
5233 | 0 | const bool hasTargetEpoch = [this]() { |
5234 | 0 | const auto &val = parameterValue(EPSG_CODE_PARAMETER_TARGET_EPOCH); |
5235 | 0 | return val && val->type() == ParameterValue::Type::MEASURE; |
5236 | 0 | }(); |
5237 | 0 | if (!hasTargetEpoch) { |
5238 | 0 | throw io::FormattingException("Missing parameter target epoch"); |
5239 | 0 | } |
5240 | | |
5241 | 0 | formatter->addStep("xyzgridshift"); |
5242 | 0 | formatter->addParam("grids", fileParameterGTF->valueFile()); |
5243 | |
|
5244 | 0 | formatter->addStep("deformation"); |
5245 | |
|
5246 | 0 | const double sourceYear = parameterValueNumeric( |
5247 | 0 | EPSG_CODE_PARAMETER_SOURCE_EPOCH, common::UnitOfMeasure::YEAR); |
5248 | 0 | const double targetYear = parameterValueNumeric( |
5249 | 0 | EPSG_CODE_PARAMETER_TARGET_EPOCH, common::UnitOfMeasure::YEAR); |
5250 | 0 | formatter->addParam("dt", targetYear - sourceYear); |
5251 | |
|
5252 | 0 | formatter->addParam("grids", fileParameterPMV->valueFile()); |
5253 | 0 | sourceCRSGeod->ellipsoid()->_exportToPROJString(formatter); |
5254 | |
|
5255 | 0 | if (l_targetCRS) { |
5256 | 0 | setupPROJGeodeticTargetCRS(formatter, NN_NO_CHECK(l_targetCRS), |
5257 | 0 | false, "Helmert"); |
5258 | 0 | } |
5259 | |
|
5260 | 0 | if (isMethodInverseOf) { |
5261 | 0 | formatter->stopInversion(); |
5262 | 0 | } |
5263 | |
|
5264 | 0 | return true; |
5265 | 0 | } |
5266 | | |
5267 | 0 | const char *prefix = "PROJ-based operation method: "; |
5268 | 0 | if (starts_with(method()->nameStr(), prefix)) { |
5269 | 0 | auto projString = method()->nameStr().substr(strlen(prefix)); |
5270 | 0 | try { |
5271 | 0 | formatter->ingestPROJString(projString); |
5272 | 0 | return true; |
5273 | 0 | } catch (const io::ParsingException &e) { |
5274 | 0 | throw io::FormattingException( |
5275 | 0 | std::string("ingestPROJString() failed: ") + e.what()); |
5276 | 0 | } |
5277 | 0 | } |
5278 | | |
5279 | 0 | return false; |
5280 | 0 | } |
5281 | | |
5282 | | // --------------------------------------------------------------------------- |
5283 | | |
5284 | | //! @cond Doxygen_Suppress |
5285 | | |
5286 | 0 | InverseCoordinateOperation::~InverseCoordinateOperation() = default; |
5287 | | |
5288 | | // --------------------------------------------------------------------------- |
5289 | | |
5290 | | InverseCoordinateOperation::InverseCoordinateOperation( |
5291 | | const CoordinateOperationNNPtr &forwardOperationIn, |
5292 | | bool wktSupportsInversion) |
5293 | 0 | : forwardOperation_(forwardOperationIn), |
5294 | 0 | wktSupportsInversion_(wktSupportsInversion) {} |
5295 | | |
5296 | | // --------------------------------------------------------------------------- |
5297 | | |
5298 | 0 | void InverseCoordinateOperation::setPropertiesFromForward() { |
5299 | 0 | setProperties( |
5300 | 0 | createPropertiesForInverse(forwardOperation_.get(), false, false)); |
5301 | 0 | setAccuracies(forwardOperation_->coordinateOperationAccuracies()); |
5302 | 0 | if (forwardOperation_->sourceCRS() && forwardOperation_->targetCRS()) { |
5303 | 0 | setCRSs(forwardOperation_.get(), true); |
5304 | 0 | } |
5305 | 0 | setHasBallparkTransformation( |
5306 | 0 | forwardOperation_->hasBallparkTransformation()); |
5307 | 0 | setRequiresPerCoordinateInputTime( |
5308 | 0 | forwardOperation_->requiresPerCoordinateInputTime()); |
5309 | 0 | if (auto sourceEpoch = forwardOperation_->sourceCoordinateEpoch()) { |
5310 | 0 | setTargetCoordinateEpoch(sourceEpoch); |
5311 | 0 | } |
5312 | 0 | if (auto targetEpoch = forwardOperation_->targetCoordinateEpoch()) { |
5313 | 0 | setSourceCoordinateEpoch(targetEpoch); |
5314 | 0 | } |
5315 | 0 | } |
5316 | | |
5317 | | // --------------------------------------------------------------------------- |
5318 | | |
5319 | 0 | CoordinateOperationNNPtr InverseCoordinateOperation::inverse() const { |
5320 | 0 | return forwardOperation_; |
5321 | 0 | } |
5322 | | |
5323 | | // --------------------------------------------------------------------------- |
5324 | | |
5325 | | void InverseCoordinateOperation::_exportToPROJString( |
5326 | 0 | io::PROJStringFormatter *formatter) const { |
5327 | 0 | formatter->startInversion(); |
5328 | 0 | forwardOperation_->_exportToPROJString(formatter); |
5329 | 0 | formatter->stopInversion(); |
5330 | 0 | } |
5331 | | |
5332 | | // --------------------------------------------------------------------------- |
5333 | | |
5334 | | bool InverseCoordinateOperation::_isEquivalentTo( |
5335 | | const util::IComparable *other, util::IComparable::Criterion criterion, |
5336 | 0 | const io::DatabaseContextPtr &dbContext) const { |
5337 | 0 | auto otherICO = dynamic_cast<const InverseCoordinateOperation *>(other); |
5338 | 0 | if (otherICO == nullptr || |
5339 | 0 | !ObjectUsage::_isEquivalentTo(other, criterion, dbContext)) { |
5340 | 0 | return false; |
5341 | 0 | } |
5342 | 0 | return inverse()->_isEquivalentTo(otherICO->inverse().get(), criterion, |
5343 | 0 | dbContext); |
5344 | 0 | } |
5345 | | |
5346 | | //! @endcond |
5347 | | |
5348 | | // --------------------------------------------------------------------------- |
5349 | | |
5350 | | //! @cond Doxygen_Suppress |
5351 | 0 | PointMotionOperation::~PointMotionOperation() = default; |
5352 | | //! @endcond |
5353 | | |
5354 | | // --------------------------------------------------------------------------- |
5355 | | |
5356 | | /** \brief Instantiate a point motion operation from a vector of |
5357 | | * GeneralParameterValue. |
5358 | | * |
5359 | | * @param properties See \ref general_properties. At minimum the name should be |
5360 | | * defined. |
5361 | | * @param crsIn Source and target CRS. |
5362 | | * @param methodIn Operation method. |
5363 | | * @param values Vector of GeneralOperationParameterNNPtr. |
5364 | | * @param accuracies Vector of positional accuracy (might be empty). |
5365 | | * @return new PointMotionOperation. |
5366 | | * @throws InvalidOperation if the object cannot be constructed. |
5367 | | */ |
5368 | | PointMotionOperationNNPtr PointMotionOperation::create( |
5369 | | const util::PropertyMap &properties, const crs::CRSNNPtr &crsIn, |
5370 | | const OperationMethodNNPtr &methodIn, |
5371 | | const std::vector<GeneralParameterValueNNPtr> &values, |
5372 | 0 | const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) { |
5373 | 0 | if (methodIn->parameters().size() != values.size()) { |
5374 | 0 | throw InvalidOperation( |
5375 | 0 | "Inconsistent number of parameters and parameter values"); |
5376 | 0 | } |
5377 | 0 | auto pmo = PointMotionOperation::nn_make_shared<PointMotionOperation>( |
5378 | 0 | crsIn, methodIn, values, accuracies); |
5379 | 0 | pmo->assignSelf(pmo); |
5380 | 0 | pmo->setProperties(properties); |
5381 | |
|
5382 | 0 | const std::string l_name = pmo->nameStr(); |
5383 | 0 | auto pos = l_name.find(" from epoch "); |
5384 | 0 | if (pos != std::string::npos) { |
5385 | 0 | pos += strlen(" from epoch "); |
5386 | 0 | const auto pos2 = l_name.find(" to epoch ", pos); |
5387 | 0 | if (pos2 != std::string::npos) { |
5388 | 0 | const double sourceYear = std::stod(l_name.substr(pos, pos2 - pos)); |
5389 | 0 | const double targetYear = |
5390 | 0 | std::stod(l_name.substr(pos2 + strlen(" to epoch "))); |
5391 | 0 | pmo->setSourceCoordinateEpoch( |
5392 | 0 | util::optional<common::DataEpoch>(common::DataEpoch( |
5393 | 0 | common::Measure(sourceYear, common::UnitOfMeasure::YEAR)))); |
5394 | 0 | pmo->setTargetCoordinateEpoch( |
5395 | 0 | util::optional<common::DataEpoch>(common::DataEpoch( |
5396 | 0 | common::Measure(targetYear, common::UnitOfMeasure::YEAR)))); |
5397 | 0 | } |
5398 | 0 | } |
5399 | |
|
5400 | 0 | return pmo; |
5401 | 0 | } |
5402 | | |
5403 | | // --------------------------------------------------------------------------- |
5404 | | |
5405 | | /** \brief Instantiate a point motion operation and its OperationMethod. |
5406 | | * |
5407 | | * @param propertiesOperation The \ref general_properties of the |
5408 | | * PointMotionOperation. |
5409 | | * At minimum the name should be defined. |
5410 | | * @param crsIn Source and target CRS. |
5411 | | * @param propertiesOperationMethod The \ref general_properties of the |
5412 | | * OperationMethod. |
5413 | | * At minimum the name should be defined. |
5414 | | * @param parameters Vector of parameters of the operation method. |
5415 | | * @param values Vector of ParameterValueNNPtr. Constraint: |
5416 | | * values.size() == parameters.size() |
5417 | | * @param accuracies Vector of positional accuracy (might be empty). |
5418 | | * @return new PointMotionOperation. |
5419 | | * @throws InvalidOperation if the object cannot be constructed. |
5420 | | */ |
5421 | | PointMotionOperationNNPtr PointMotionOperation::create( |
5422 | | const util::PropertyMap &propertiesOperation, const crs::CRSNNPtr &crsIn, |
5423 | | const util::PropertyMap &propertiesOperationMethod, |
5424 | | const std::vector<OperationParameterNNPtr> ¶meters, |
5425 | | const std::vector<ParameterValueNNPtr> &values, |
5426 | | const std::vector<metadata::PositionalAccuracyNNPtr> |
5427 | | &accuracies) // throw InvalidOperation |
5428 | 0 | { |
5429 | 0 | OperationMethodNNPtr op( |
5430 | 0 | OperationMethod::create(propertiesOperationMethod, parameters)); |
5431 | |
|
5432 | 0 | if (parameters.size() != values.size()) { |
5433 | 0 | throw InvalidOperation( |
5434 | 0 | "Inconsistent number of parameters and parameter values"); |
5435 | 0 | } |
5436 | 0 | std::vector<GeneralParameterValueNNPtr> generalParameterValues; |
5437 | 0 | generalParameterValues.reserve(values.size()); |
5438 | 0 | for (size_t i = 0; i < values.size(); i++) { |
5439 | 0 | generalParameterValues.push_back( |
5440 | 0 | OperationParameterValue::create(parameters[i], values[i])); |
5441 | 0 | } |
5442 | 0 | return create(propertiesOperation, crsIn, op, generalParameterValues, |
5443 | 0 | accuracies); |
5444 | 0 | } |
5445 | | |
5446 | | // --------------------------------------------------------------------------- |
5447 | | |
5448 | | PointMotionOperation::PointMotionOperation( |
5449 | | const crs::CRSNNPtr &crsIn, const OperationMethodNNPtr &methodIn, |
5450 | | const std::vector<GeneralParameterValueNNPtr> &values, |
5451 | | const std::vector<metadata::PositionalAccuracyNNPtr> &accuracies) |
5452 | 0 | : SingleOperation(methodIn) { |
5453 | 0 | setParameterValues(values); |
5454 | 0 | setCRSs(crsIn, crsIn, nullptr); |
5455 | 0 | setAccuracies(accuracies); |
5456 | 0 | } Unexecuted instantiation: osgeo::proj::operation::PointMotionOperation::PointMotionOperation(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::CRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::OperationMethod> > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> > > > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> > > > const&) Unexecuted instantiation: osgeo::proj::operation::PointMotionOperation::PointMotionOperation(dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::crs::CRS> > const&, dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::OperationMethod> > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::operation::GeneralParameterValue> > > > const&, std::__1::vector<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> >, std::__1::allocator<dropbox::oxygen::nn<std::__1::shared_ptr<osgeo::proj::metadata::PositionalAccuracy> > > > const&) |
5457 | | |
5458 | | // --------------------------------------------------------------------------- |
5459 | | |
5460 | | PointMotionOperation::PointMotionOperation(const PointMotionOperation &other) |
5461 | 0 | : CoordinateOperation(other), SingleOperation(other) {}Unexecuted instantiation: osgeo::proj::operation::PointMotionOperation::PointMotionOperation(osgeo::proj::operation::PointMotionOperation const&) Unexecuted instantiation: osgeo::proj::operation::PointMotionOperation::PointMotionOperation(osgeo::proj::operation::PointMotionOperation const&) |
5462 | | |
5463 | | // --------------------------------------------------------------------------- |
5464 | | |
5465 | 0 | CoordinateOperationNNPtr PointMotionOperation::inverse() const { |
5466 | 0 | auto inverse = shallowClone(); |
5467 | 0 | if (sourceCoordinateEpoch().has_value()) { |
5468 | | // Switch source and target epochs |
5469 | 0 | inverse->setSourceCoordinateEpoch(targetCoordinateEpoch()); |
5470 | 0 | inverse->setTargetCoordinateEpoch(sourceCoordinateEpoch()); |
5471 | |
|
5472 | 0 | auto l_name = inverse->nameStr(); |
5473 | 0 | auto pos = l_name.find(" from epoch "); |
5474 | 0 | if (pos != std::string::npos) |
5475 | 0 | l_name.resize(pos); |
5476 | |
|
5477 | 0 | const double sourceYear = getRoundedEpochInDecimalYear( |
5478 | 0 | inverse->sourceCoordinateEpoch()->coordinateEpoch().convertToUnit( |
5479 | 0 | common::UnitOfMeasure::YEAR)); |
5480 | 0 | const double targetYear = getRoundedEpochInDecimalYear( |
5481 | 0 | inverse->targetCoordinateEpoch()->coordinateEpoch().convertToUnit( |
5482 | 0 | common::UnitOfMeasure::YEAR)); |
5483 | |
|
5484 | 0 | l_name += " from epoch "; |
5485 | 0 | l_name += toString(sourceYear); |
5486 | 0 | l_name += " to epoch "; |
5487 | 0 | l_name += toString(targetYear); |
5488 | 0 | util::PropertyMap newProperties; |
5489 | 0 | newProperties.set(IdentifiedObject::NAME_KEY, l_name); |
5490 | 0 | inverse->setProperties(newProperties); |
5491 | 0 | } |
5492 | 0 | return inverse; |
5493 | 0 | } |
5494 | | |
5495 | | // --------------------------------------------------------------------------- |
5496 | | |
5497 | | /** \brief Return an equivalent transformation to the current one, but using |
5498 | | * PROJ alternative grid names. |
5499 | | */ |
5500 | | PointMotionOperationNNPtr |
5501 | | PointMotionOperation::substitutePROJAlternativeGridNames( |
5502 | 0 | io::DatabaseContextNNPtr databaseContext) const { |
5503 | 0 | auto self = NN_NO_CHECK(std::dynamic_pointer_cast<PointMotionOperation>( |
5504 | 0 | shared_from_this().as_nullable())); |
5505 | |
|
5506 | 0 | const auto &l_method = method(); |
5507 | 0 | const int methodEPSGCode = l_method->getEPSGCode(); |
5508 | |
|
5509 | 0 | const char *const paramName = |
5510 | 0 | methodEPSGCode == |
5511 | 0 | EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT |
5512 | 0 | ? EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE |
5513 | 0 | : EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE; |
5514 | 0 | const int paramCode = |
5515 | 0 | methodEPSGCode == |
5516 | 0 | EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT |
5517 | 0 | ? EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE |
5518 | 0 | : EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE; |
5519 | |
|
5520 | 0 | std::string filename; |
5521 | 0 | if (methodEPSGCode == |
5522 | 0 | EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL || |
5523 | 0 | methodEPSGCode == |
5524 | 0 | EPSG_CODE_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL || |
5525 | 0 | methodEPSGCode == |
5526 | 0 | EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT) { |
5527 | 0 | const auto &fileParameter = parameterValue(paramName, paramCode); |
5528 | 0 | if (fileParameter && |
5529 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
5530 | 0 | filename = fileParameter->valueFile(); |
5531 | 0 | } |
5532 | 0 | } |
5533 | |
|
5534 | 0 | std::string projFilename; |
5535 | 0 | std::string projGridFormat; |
5536 | 0 | bool inverseDirection = false; |
5537 | 0 | if (!filename.empty() && |
5538 | 0 | databaseContext->lookForGridAlternative( |
5539 | 0 | filename, projFilename, projGridFormat, inverseDirection)) { |
5540 | |
|
5541 | 0 | if (filename == projFilename) { |
5542 | 0 | return self; |
5543 | 0 | } |
5544 | | |
5545 | 0 | const VectorOfParameters parameters{ |
5546 | 0 | createOpParamNameEPSGCode(paramCode)}; |
5547 | 0 | const VectorOfValues values{ |
5548 | 0 | ParameterValue::createFilename(projFilename)}; |
5549 | 0 | return PointMotionOperation::create( |
5550 | 0 | createSimilarPropertiesOperation(self), sourceCRS(), |
5551 | 0 | createSimilarPropertiesMethod(method()), parameters, values, |
5552 | 0 | coordinateOperationAccuracies()); |
5553 | 0 | } |
5554 | | |
5555 | 0 | return self; |
5556 | 0 | } |
5557 | | |
5558 | | // --------------------------------------------------------------------------- |
5559 | | |
5560 | | /** \brief Return the source crs::CRS of the operation. |
5561 | | * |
5562 | | * @return the source CRS. |
5563 | | */ |
5564 | 0 | const crs::CRSNNPtr &PointMotionOperation::sourceCRS() PROJ_PURE_DEFN { |
5565 | 0 | return CoordinateOperation::getPrivate()->strongRef_->sourceCRS_; |
5566 | 0 | } |
5567 | | |
5568 | | // --------------------------------------------------------------------------- |
5569 | | |
5570 | | //! @cond Doxygen_Suppress |
5571 | | |
5572 | 0 | PointMotionOperationNNPtr PointMotionOperation::shallowClone() const { |
5573 | 0 | auto pmo = |
5574 | 0 | PointMotionOperation::nn_make_shared<PointMotionOperation>(*this); |
5575 | 0 | pmo->assignSelf(pmo); |
5576 | 0 | pmo->setCRSs(this, false); |
5577 | 0 | return pmo; |
5578 | 0 | } |
5579 | | |
5580 | 0 | CoordinateOperationNNPtr PointMotionOperation::_shallowClone() const { |
5581 | 0 | return util::nn_static_pointer_cast<CoordinateOperation>(shallowClone()); |
5582 | 0 | } |
5583 | | |
5584 | | // --------------------------------------------------------------------------- |
5585 | | |
5586 | | PointMotionOperationNNPtr PointMotionOperation::cloneWithEpochs( |
5587 | | const common::DataEpoch &sourceEpoch, |
5588 | 0 | const common::DataEpoch &targetEpoch) const { |
5589 | 0 | auto pmo = |
5590 | 0 | PointMotionOperation::nn_make_shared<PointMotionOperation>(*this); |
5591 | |
|
5592 | 0 | pmo->assignSelf(pmo); |
5593 | 0 | pmo->setCRSs(this, false); |
5594 | |
|
5595 | 0 | pmo->setSourceCoordinateEpoch( |
5596 | 0 | util::optional<common::DataEpoch>(sourceEpoch)); |
5597 | 0 | pmo->setTargetCoordinateEpoch( |
5598 | 0 | util::optional<common::DataEpoch>(targetEpoch)); |
5599 | |
|
5600 | 0 | const double sourceYear = getRoundedEpochInDecimalYear( |
5601 | 0 | sourceEpoch.coordinateEpoch().convertToUnit( |
5602 | 0 | common::UnitOfMeasure::YEAR)); |
5603 | 0 | const double targetYear = getRoundedEpochInDecimalYear( |
5604 | 0 | targetEpoch.coordinateEpoch().convertToUnit( |
5605 | 0 | common::UnitOfMeasure::YEAR)); |
5606 | |
|
5607 | 0 | auto l_name = nameStr(); |
5608 | 0 | l_name += " from epoch "; |
5609 | 0 | l_name += toString(sourceYear); |
5610 | 0 | l_name += " to epoch "; |
5611 | 0 | l_name += toString(targetYear); |
5612 | 0 | util::PropertyMap newProperties; |
5613 | 0 | newProperties.set(IdentifiedObject::NAME_KEY, l_name); |
5614 | 0 | pmo->setProperties(newProperties); |
5615 | |
|
5616 | 0 | return pmo; |
5617 | 0 | } |
5618 | | |
5619 | | // --------------------------------------------------------------------------- |
5620 | | |
5621 | 0 | void PointMotionOperation::_exportToWKT(io::WKTFormatter *formatter) const { |
5622 | 0 | if (formatter->version() != io::WKTFormatter::Version::WKT2 || |
5623 | 0 | !formatter->use2019Keywords()) { |
5624 | 0 | throw io::FormattingException( |
5625 | 0 | "Transformation can only be exported to WKT2:2019"); |
5626 | 0 | } |
5627 | | |
5628 | 0 | formatter->startNode(io::WKTConstants::POINTMOTIONOPERATION, |
5629 | 0 | !identifiers().empty()); |
5630 | |
|
5631 | 0 | formatter->addQuotedString(nameStr()); |
5632 | |
|
5633 | 0 | const auto &version = operationVersion(); |
5634 | 0 | if (version.has_value()) { |
5635 | 0 | formatter->startNode(io::WKTConstants::VERSION, false); |
5636 | 0 | formatter->addQuotedString(*version); |
5637 | 0 | formatter->endNode(); |
5638 | 0 | } |
5639 | |
|
5640 | 0 | auto l_sourceCRS = sourceCRS(); |
5641 | 0 | const bool canExportCRSId = |
5642 | 0 | !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId()); |
5643 | |
|
5644 | 0 | const bool hasDomains = !domains().empty(); |
5645 | 0 | if (hasDomains) { |
5646 | 0 | formatter->pushDisableUsage(); |
5647 | 0 | } |
5648 | |
|
5649 | 0 | formatter->startNode(io::WKTConstants::SOURCECRS, false); |
5650 | 0 | if (canExportCRSId && !l_sourceCRS->identifiers().empty()) { |
5651 | | // fake that top node has no id, so that the sourceCRS id is |
5652 | | // considered |
5653 | 0 | formatter->pushHasId(false); |
5654 | 0 | l_sourceCRS->_exportToWKT(formatter); |
5655 | 0 | formatter->popHasId(); |
5656 | 0 | } else { |
5657 | 0 | l_sourceCRS->_exportToWKT(formatter); |
5658 | 0 | } |
5659 | 0 | formatter->endNode(); |
5660 | |
|
5661 | 0 | if (hasDomains) { |
5662 | 0 | formatter->popDisableUsage(); |
5663 | 0 | } |
5664 | |
|
5665 | 0 | const auto &l_method = method(); |
5666 | 0 | l_method->_exportToWKT(formatter); |
5667 | |
|
5668 | 0 | for (const auto ¶mValue : parameterValues()) { |
5669 | 0 | paramValue->_exportToWKT(formatter, nullptr); |
5670 | 0 | } |
5671 | |
|
5672 | 0 | if (!coordinateOperationAccuracies().empty()) { |
5673 | 0 | formatter->startNode(io::WKTConstants::OPERATIONACCURACY, false); |
5674 | 0 | formatter->add(coordinateOperationAccuracies()[0]->value()); |
5675 | 0 | formatter->endNode(); |
5676 | 0 | } |
5677 | |
|
5678 | 0 | ObjectUsage::baseExportToWKT(formatter); |
5679 | 0 | formatter->endNode(); |
5680 | 0 | } |
5681 | | |
5682 | | // --------------------------------------------------------------------------- |
5683 | | |
5684 | | void PointMotionOperation::_exportToPROJString( |
5685 | | io::PROJStringFormatter *formatter) const // throw(FormattingException) |
5686 | 0 | { |
5687 | 0 | if (formatter->convention() == |
5688 | 0 | io::PROJStringFormatter::Convention::PROJ_4) { |
5689 | 0 | throw io::FormattingException( |
5690 | 0 | "PointMotionOperation cannot be exported as a PROJ.4 string"); |
5691 | 0 | } |
5692 | | |
5693 | 0 | const int methodEPSGCode = method()->getEPSGCode(); |
5694 | 0 | if (methodEPSGCode == |
5695 | 0 | EPSG_CODE_METHOD_POINT_MOTION_BY_GRID_CANADA_NTV2_VEL || |
5696 | 0 | methodEPSGCode == |
5697 | 0 | EPSG_CODE_METHOD_POINT_MOTION_GEOG3D_DOMAIN_USING_NEU_VELOCITY_GRID_NTV2_VEL || |
5698 | 0 | methodEPSGCode == |
5699 | 0 | EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT) { |
5700 | 0 | if (!sourceCoordinateEpoch().has_value()) { |
5701 | 0 | throw io::FormattingException( |
5702 | 0 | "CoordinateOperationNNPtr::_exportToPROJString() unimplemented " |
5703 | 0 | "when source coordinate epoch is missing"); |
5704 | 0 | } |
5705 | 0 | if (!targetCoordinateEpoch().has_value()) { |
5706 | 0 | throw io::FormattingException( |
5707 | 0 | "CoordinateOperationNNPtr::_exportToPROJString() unimplemented " |
5708 | 0 | "when target coordinate epoch is missing"); |
5709 | 0 | } |
5710 | | |
5711 | 0 | auto l_sourceCRS = |
5712 | 0 | dynamic_cast<const crs::GeodeticCRS *>(sourceCRS().get()); |
5713 | 0 | if (!l_sourceCRS) { |
5714 | 0 | throw io::FormattingException("Can apply PointMotionOperation " |
5715 | 0 | "VelocityGrid only to GeodeticCRS"); |
5716 | 0 | } |
5717 | | |
5718 | 0 | if (!l_sourceCRS->isGeocentric()) { |
5719 | 0 | formatter->startInversion(); |
5720 | 0 | l_sourceCRS->_exportToPROJString(formatter); |
5721 | 0 | formatter->stopInversion(); |
5722 | |
|
5723 | 0 | formatter->addStep("cart"); |
5724 | 0 | l_sourceCRS->ellipsoid()->_exportToPROJString(formatter); |
5725 | 0 | } else { |
5726 | 0 | formatter->startInversion(); |
5727 | 0 | l_sourceCRS->addGeocentricUnitConversionIntoPROJString(formatter); |
5728 | 0 | formatter->stopInversion(); |
5729 | 0 | } |
5730 | |
|
5731 | 0 | const double sourceYear = getRoundedEpochInDecimalYear( |
5732 | 0 | sourceCoordinateEpoch()->coordinateEpoch().convertToUnit( |
5733 | 0 | common::UnitOfMeasure::YEAR)); |
5734 | 0 | const double targetYear = getRoundedEpochInDecimalYear( |
5735 | 0 | targetCoordinateEpoch()->coordinateEpoch().convertToUnit( |
5736 | 0 | common::UnitOfMeasure::YEAR)); |
5737 | |
|
5738 | 0 | formatter->addStep("set"); |
5739 | 0 | formatter->addParam("v_4", sourceYear); |
5740 | 0 | formatter->addParam("omit_fwd"); |
5741 | |
|
5742 | 0 | formatter->addStep("deformation"); |
5743 | 0 | formatter->addParam("dt", targetYear - sourceYear); |
5744 | |
|
5745 | 0 | const char *const paramName = |
5746 | 0 | methodEPSGCode == |
5747 | 0 | EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT |
5748 | 0 | ? EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE |
5749 | 0 | : EPSG_NAME_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE; |
5750 | 0 | const int paramCode = |
5751 | 0 | methodEPSGCode == |
5752 | 0 | EPSG_CODE_METHOD_POINT_MOTION_GEOCEN_DOMAIN_USING_NEU_VELOCITY_GRID_GRAVSOFT |
5753 | 0 | ? EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_NORTH_GRID_FILE |
5754 | 0 | : EPSG_CODE_PARAMETER_POINT_MOTION_VELOCITY_GRID_FILE; |
5755 | 0 | const auto &fileParameter = parameterValue(paramName, paramCode); |
5756 | 0 | if (fileParameter && |
5757 | 0 | fileParameter->type() == ParameterValue::Type::FILENAME) { |
5758 | 0 | formatter->addParam("grids", fileParameter->valueFile()); |
5759 | 0 | } else { |
5760 | 0 | throw io::FormattingException( |
5761 | 0 | "CoordinateOperationNNPtr::_exportToPROJString(): missing " |
5762 | 0 | "velocity grid file parameter"); |
5763 | 0 | } |
5764 | 0 | l_sourceCRS->ellipsoid()->_exportToPROJString(formatter); |
5765 | |
|
5766 | 0 | formatter->addStep("set"); |
5767 | 0 | formatter->addParam("v_4", targetYear); |
5768 | 0 | formatter->addParam("omit_inv"); |
5769 | |
|
5770 | 0 | if (!l_sourceCRS->isGeocentric()) { |
5771 | 0 | formatter->startInversion(); |
5772 | 0 | formatter->addStep("cart"); |
5773 | 0 | l_sourceCRS->ellipsoid()->_exportToPROJString(formatter); |
5774 | 0 | formatter->stopInversion(); |
5775 | |
|
5776 | 0 | l_sourceCRS->_exportToPROJString(formatter); |
5777 | 0 | } else { |
5778 | 0 | l_sourceCRS->addGeocentricUnitConversionIntoPROJString(formatter); |
5779 | 0 | } |
5780 | |
|
5781 | 0 | } else { |
5782 | 0 | throw io::FormattingException( |
5783 | 0 | "CoordinateOperationNNPtr::_exportToPROJString() unimplemented for " |
5784 | 0 | "this method"); |
5785 | 0 | } |
5786 | 0 | } |
5787 | | |
5788 | | // --------------------------------------------------------------------------- |
5789 | | |
5790 | | void PointMotionOperation::_exportToJSON( |
5791 | | io::JSONFormatter *formatter) const // throw(FormattingException) |
5792 | 0 | { |
5793 | 0 | auto writer = formatter->writer(); |
5794 | 0 | auto objectContext(formatter->MakeObjectContext("PointMotionOperation", |
5795 | 0 | !identifiers().empty())); |
5796 | |
|
5797 | 0 | writer->AddObjKey("name"); |
5798 | 0 | const auto &l_name = nameStr(); |
5799 | 0 | if (l_name.empty()) { |
5800 | 0 | writer->Add("unnamed"); |
5801 | 0 | } else { |
5802 | 0 | writer->Add(l_name); |
5803 | 0 | } |
5804 | |
|
5805 | 0 | writer->AddObjKey("source_crs"); |
5806 | 0 | formatter->setAllowIDInImmediateChild(); |
5807 | 0 | sourceCRS()->_exportToJSON(formatter); |
5808 | |
|
5809 | 0 | writer->AddObjKey("method"); |
5810 | 0 | formatter->setOmitTypeInImmediateChild(); |
5811 | 0 | formatter->setAllowIDInImmediateChild(); |
5812 | 0 | method()->_exportToJSON(formatter); |
5813 | |
|
5814 | 0 | writer->AddObjKey("parameters"); |
5815 | 0 | { |
5816 | 0 | auto parametersContext(writer->MakeArrayContext(false)); |
5817 | 0 | for (const auto &genOpParamvalue : parameterValues()) { |
5818 | 0 | formatter->setAllowIDInImmediateChild(); |
5819 | 0 | formatter->setOmitTypeInImmediateChild(); |
5820 | 0 | genOpParamvalue->_exportToJSON(formatter); |
5821 | 0 | } |
5822 | 0 | } |
5823 | |
|
5824 | 0 | if (!coordinateOperationAccuracies().empty()) { |
5825 | 0 | writer->AddObjKey("accuracy"); |
5826 | 0 | writer->Add(coordinateOperationAccuracies()[0]->value()); |
5827 | 0 | } |
5828 | |
|
5829 | 0 | ObjectUsage::baseExportToJSON(formatter); |
5830 | 0 | } |
5831 | | |
5832 | | //! @endcond |
5833 | | |
5834 | | // --------------------------------------------------------------------------- |
5835 | | |
5836 | | } // namespace operation |
5837 | | |
5838 | | NS_PROJ_END |