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