/src/proj/src/iso19111/operation/oputils.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 <string.h> |
34 | | |
35 | | #include "proj/coordinateoperation.hpp" |
36 | | #include "proj/crs.hpp" |
37 | | #include "proj/util.hpp" |
38 | | |
39 | | #include "proj/internal/internal.hpp" |
40 | | #include "proj/internal/io_internal.hpp" |
41 | | |
42 | | #include "oputils.hpp" |
43 | | #include "parammappings.hpp" |
44 | | |
45 | | #include "proj_constants.h" |
46 | | |
47 | | // --------------------------------------------------------------------------- |
48 | | |
49 | | NS_PROJ_START |
50 | | |
51 | | using namespace internal; |
52 | | |
53 | | namespace operation { |
54 | | |
55 | | // --------------------------------------------------------------------------- |
56 | | |
57 | | //! @cond Doxygen_Suppress |
58 | | |
59 | | const char *BALLPARK_GEOCENTRIC_TRANSLATION = "Ballpark geocentric translation"; |
60 | | const char *NULL_GEOGRAPHIC_OFFSET = "Null geographic offset"; |
61 | | const char *NULL_GEOCENTRIC_TRANSLATION = "Null geocentric translation"; |
62 | | const char *BALLPARK_GEOGRAPHIC_OFFSET = "Ballpark geographic offset"; |
63 | | const char *BALLPARK_VERTICAL_TRANSFORMATION = |
64 | | "ballpark vertical transformation"; |
65 | | const char *BALLPARK_VERTICAL_TRANSFORMATION_NO_ELLIPSOID_VERT_HEIGHT = |
66 | | "ballpark vertical transformation, without ellipsoid height to vertical " |
67 | | "height correction"; |
68 | | |
69 | | // --------------------------------------------------------------------------- |
70 | | |
71 | 26.6k | OperationParameterNNPtr createOpParamNameEPSGCode(int code) { |
72 | 26.6k | const char *name = OperationParameter::getNameForEPSGCode(code); |
73 | 26.6k | assert(name); |
74 | 26.6k | return OperationParameter::create(createMapNameEPSGCode(name, code)); |
75 | 26.6k | } |
76 | | |
77 | | // --------------------------------------------------------------------------- |
78 | | |
79 | 6.93k | util::PropertyMap createMethodMapNameEPSGCode(int code) { |
80 | 6.93k | const char *name = nullptr; |
81 | 6.93k | size_t nMethodNameCodes = 0; |
82 | 6.93k | const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes); |
83 | 463k | for (size_t i = 0; i < nMethodNameCodes; ++i) { |
84 | 463k | const auto &tuple = methodNameCodes[i]; |
85 | 463k | if (tuple.epsg_code == code) { |
86 | 6.93k | name = tuple.name; |
87 | 6.93k | break; |
88 | 6.93k | } |
89 | 463k | } |
90 | 6.93k | assert(name); |
91 | 6.93k | return createMapNameEPSGCode(name, code); |
92 | 6.93k | } |
93 | | |
94 | | // --------------------------------------------------------------------------- |
95 | | |
96 | 1.39k | util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) { |
97 | 1.39k | return util::PropertyMap() |
98 | 1.39k | .set(common::IdentifiedObject::NAME_KEY, name) |
99 | 1.39k | .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) |
100 | 1.39k | .set(metadata::Identifier::CODE_KEY, code); |
101 | 1.39k | } |
102 | | |
103 | | // --------------------------------------------------------------------------- |
104 | | |
105 | 33.6k | util::PropertyMap createMapNameEPSGCode(const char *name, int code) { |
106 | 33.6k | return util::PropertyMap() |
107 | 33.6k | .set(common::IdentifiedObject::NAME_KEY, name) |
108 | 33.6k | .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) |
109 | 33.6k | .set(metadata::Identifier::CODE_KEY, code); |
110 | 33.6k | } |
111 | | |
112 | | // --------------------------------------------------------------------------- |
113 | | |
114 | | util::PropertyMap &addDomains(util::PropertyMap &map, |
115 | 479 | const common::ObjectUsage *obj) { |
116 | | |
117 | 479 | auto ar = util::ArrayOfBaseObject::create(); |
118 | 507 | for (const auto &domain : obj->domains()) { |
119 | 507 | ar->add(domain); |
120 | 507 | } |
121 | 479 | if (!ar->empty()) { |
122 | 479 | map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar); |
123 | 479 | } |
124 | 479 | return map; |
125 | 479 | } |
126 | | |
127 | | // --------------------------------------------------------------------------- |
128 | | |
129 | 44 | static const char *getCRSQualifierStr(const crs::CRSPtr &crs) { |
130 | 44 | auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get()); |
131 | 44 | if (geod) { |
132 | 44 | if (geod->isGeocentric()) { |
133 | 22 | return " (geocentric)"; |
134 | 22 | } |
135 | 22 | auto geog = dynamic_cast<crs::GeographicCRS *>(geod); |
136 | 22 | if (geog) { |
137 | 22 | if (geog->coordinateSystem()->axisList().size() == 2) { |
138 | 8 | return " (geog2D)"; |
139 | 14 | } else { |
140 | 14 | return " (geog3D)"; |
141 | 14 | } |
142 | 22 | } |
143 | 22 | } |
144 | 0 | return ""; |
145 | 44 | } |
146 | | |
147 | | // --------------------------------------------------------------------------- |
148 | | |
149 | | std::string buildOpName(const char *opType, const crs::CRSPtr &source, |
150 | 78 | const crs::CRSPtr &target) { |
151 | 78 | std::string res(opType); |
152 | 78 | const auto &srcName = source->nameStr(); |
153 | 78 | const auto &targetName = target->nameStr(); |
154 | 78 | const char *srcQualifier = ""; |
155 | 78 | const char *targetQualifier = ""; |
156 | 78 | if (srcName == targetName) { |
157 | 22 | srcQualifier = getCRSQualifierStr(source); |
158 | 22 | targetQualifier = getCRSQualifierStr(target); |
159 | 22 | if (strcmp(srcQualifier, targetQualifier) == 0) { |
160 | 0 | srcQualifier = ""; |
161 | 0 | targetQualifier = ""; |
162 | 0 | } |
163 | 22 | } |
164 | 78 | res += " from "; |
165 | 78 | res += srcName; |
166 | 78 | res += srcQualifier; |
167 | 78 | res += " to "; |
168 | 78 | res += targetName; |
169 | 78 | res += targetQualifier; |
170 | 78 | return res; |
171 | 78 | } |
172 | | |
173 | | // --------------------------------------------------------------------------- |
174 | | |
175 | | void addModifiedIdentifier(util::PropertyMap &map, |
176 | | const common::IdentifiedObject *obj, bool inverse, |
177 | 494 | bool derivedFrom) { |
178 | | // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE |
179 | | // as identifier. |
180 | | |
181 | 494 | auto ar = util::ArrayOfBaseObject::create(); |
182 | 494 | for (const auto &idSrc : obj->identifiers()) { |
183 | 481 | auto authName = *(idSrc->codeSpace()); |
184 | 481 | const auto &srcCode = idSrc->code(); |
185 | 481 | if (derivedFrom) { |
186 | 434 | authName = concat("DERIVED_FROM(", authName, ")"); |
187 | 434 | } |
188 | 481 | if (inverse) { |
189 | 58 | if (starts_with(authName, "INVERSE(") && authName.back() == ')') { |
190 | 11 | authName = authName.substr(strlen("INVERSE(")); |
191 | 11 | authName.resize(authName.size() - 1); |
192 | 47 | } else { |
193 | 47 | authName = concat("INVERSE(", authName, ")"); |
194 | 47 | } |
195 | 58 | } |
196 | 481 | auto idsProp = util::PropertyMap().set( |
197 | 481 | metadata::Identifier::CODESPACE_KEY, authName); |
198 | 481 | ar->add(metadata::Identifier::create(srcCode, idsProp)); |
199 | 481 | } |
200 | 494 | if (!ar->empty()) { |
201 | 481 | map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); |
202 | 481 | } |
203 | 494 | } |
204 | | |
205 | | // --------------------------------------------------------------------------- |
206 | | |
207 | | util::PropertyMap |
208 | 15 | createPropertiesForInverse(const OperationMethodNNPtr &method) { |
209 | 15 | util::PropertyMap map; |
210 | | |
211 | 15 | const std::string &forwardName = method->nameStr(); |
212 | 15 | if (!forwardName.empty()) { |
213 | 15 | if (starts_with(forwardName, INVERSE_OF)) { |
214 | 0 | map.set(common::IdentifiedObject::NAME_KEY, |
215 | 0 | forwardName.substr(INVERSE_OF.size())); |
216 | 15 | } else { |
217 | 15 | map.set(common::IdentifiedObject::NAME_KEY, |
218 | 15 | INVERSE_OF + forwardName); |
219 | 15 | } |
220 | 15 | } |
221 | | |
222 | 15 | addModifiedIdentifier(map, method.get(), true, false); |
223 | | |
224 | 15 | return map; |
225 | 15 | } |
226 | | |
227 | | // --------------------------------------------------------------------------- |
228 | | |
229 | | util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op, |
230 | | bool derivedFrom, |
231 | 56 | bool approximateInversion) { |
232 | 56 | assert(op); |
233 | 56 | util::PropertyMap map; |
234 | | |
235 | | // The domain(s) are unchanged by the inverse operation |
236 | 56 | addDomains(map, op); |
237 | | |
238 | 56 | const std::string &forwardName = op->nameStr(); |
239 | | |
240 | | // Forge a name for the inverse, either from the forward name, or |
241 | | // from the source and target CRS names |
242 | 56 | const char *opType; |
243 | 56 | if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) { |
244 | 0 | opType = BALLPARK_GEOCENTRIC_TRANSLATION; |
245 | 56 | } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) { |
246 | 0 | opType = BALLPARK_GEOGRAPHIC_OFFSET; |
247 | 56 | } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) { |
248 | 0 | opType = NULL_GEOGRAPHIC_OFFSET; |
249 | 56 | } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) { |
250 | 0 | opType = NULL_GEOCENTRIC_TRANSLATION; |
251 | 56 | } else if (dynamic_cast<const Transformation *>(op) || |
252 | 56 | starts_with(forwardName, "Transformation from ")) { |
253 | 26 | opType = "Transformation"; |
254 | 30 | } else if (dynamic_cast<const Conversion *>(op)) { |
255 | 0 | opType = "Conversion"; |
256 | 30 | } else { |
257 | 30 | opType = "Operation"; |
258 | 30 | } |
259 | | |
260 | 56 | auto sourceCRS = op->sourceCRS(); |
261 | 56 | auto targetCRS = op->targetCRS(); |
262 | 56 | std::string name; |
263 | 56 | if (!forwardName.empty()) { |
264 | 56 | if (dynamic_cast<const Transformation *>(op) == nullptr && |
265 | 56 | dynamic_cast<const ConcatenatedOperation *>(op) == nullptr && |
266 | 56 | (starts_with(forwardName, INVERSE_OF) || |
267 | 30 | forwardName.find(" + ") != std::string::npos)) { |
268 | 0 | std::vector<std::string> tokens; |
269 | 0 | std::string curToken; |
270 | 0 | bool inString = false; |
271 | 0 | for (size_t i = 0; i < forwardName.size(); ++i) { |
272 | 0 | if (inString) { |
273 | 0 | curToken += forwardName[i]; |
274 | 0 | if (forwardName[i] == '\'') { |
275 | 0 | inString = false; |
276 | 0 | } |
277 | 0 | } else if (i + 3 < forwardName.size() && |
278 | 0 | memcmp(&forwardName[i], " + ", 3) == 0) { |
279 | 0 | tokens.push_back(curToken); |
280 | 0 | curToken.clear(); |
281 | 0 | i += 2; |
282 | 0 | } else if (forwardName[i] == '\'') { |
283 | 0 | inString = true; |
284 | 0 | curToken += forwardName[i]; |
285 | 0 | } else { |
286 | 0 | curToken += forwardName[i]; |
287 | 0 | } |
288 | 0 | } |
289 | 0 | if (!curToken.empty()) { |
290 | 0 | tokens.push_back(std::move(curToken)); |
291 | 0 | } |
292 | 0 | for (size_t i = tokens.size(); i > 0;) { |
293 | 0 | i--; |
294 | 0 | if (!name.empty()) { |
295 | 0 | name += " + "; |
296 | 0 | } |
297 | 0 | if (starts_with(tokens[i], INVERSE_OF)) { |
298 | 0 | name += tokens[i].substr(INVERSE_OF.size()); |
299 | 0 | } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME || |
300 | 0 | tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) { |
301 | 0 | name += tokens[i]; |
302 | 0 | } else { |
303 | 0 | name += INVERSE_OF + tokens[i]; |
304 | 0 | } |
305 | 0 | } |
306 | 56 | } else if (!sourceCRS || !targetCRS || |
307 | 56 | forwardName != buildOpName(opType, sourceCRS, targetCRS)) { |
308 | 56 | if (forwardName.find(" + ") != std::string::npos) { |
309 | 1 | name = INVERSE_OF + '\'' + forwardName + '\''; |
310 | 55 | } else { |
311 | 55 | name = INVERSE_OF + forwardName; |
312 | 55 | } |
313 | 56 | } |
314 | 56 | } |
315 | 56 | if (name.empty() && sourceCRS && targetCRS) { |
316 | 0 | name = buildOpName(opType, targetCRS, sourceCRS); |
317 | 0 | } |
318 | 56 | if (approximateInversion) { |
319 | 0 | name += " (approx. inversion)"; |
320 | 0 | } |
321 | | |
322 | 56 | if (!name.empty()) { |
323 | 56 | map.set(common::IdentifiedObject::NAME_KEY, name); |
324 | 56 | } |
325 | | |
326 | 56 | const std::string &remarks = op->remarks(); |
327 | 56 | if (!remarks.empty()) { |
328 | 26 | map.set(common::IdentifiedObject::REMARKS_KEY, remarks); |
329 | 26 | } |
330 | | |
331 | 56 | addModifiedIdentifier(map, op, true, derivedFrom); |
332 | | |
333 | 56 | const auto so = dynamic_cast<const SingleOperation *>(op); |
334 | 56 | if (so) { |
335 | 56 | const int soMethodEPSGCode = so->method()->getEPSGCode(); |
336 | 56 | if (soMethodEPSGCode > 0) { |
337 | 13 | map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode); |
338 | 13 | } |
339 | 56 | } |
340 | | |
341 | 56 | return map; |
342 | 56 | } |
343 | | |
344 | | // --------------------------------------------------------------------------- |
345 | | |
346 | | util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties, |
347 | 19.0k | const std::string &defaultName) { |
348 | 19.0k | if (!properties.get(common::IdentifiedObject::NAME_KEY)) { |
349 | 16.8k | return util::PropertyMap(properties) |
350 | 16.8k | .set(common::IdentifiedObject::NAME_KEY, defaultName); |
351 | 16.8k | } else { |
352 | 2.20k | return properties; |
353 | 2.20k | } |
354 | 19.0k | } |
355 | | |
356 | | // --------------------------------------------------------------------------- |
357 | | |
358 | | static std::string createEntryEqParam(const std::string &a, |
359 | 25.5k | const std::string &b) { |
360 | 25.5k | return a < b ? a + b : b + a; |
361 | 25.5k | } |
362 | | |
363 | 1 | static std::set<std::string> buildSetEquivalentParameters() { |
364 | | |
365 | 1 | std::set<std::string> set; |
366 | | |
367 | 1 | const char *const listOfEquivalentParameterNames[][7] = { |
368 | 1 | {"latitude_of_point_1", "Latitude_Of_1st_Point", nullptr}, |
369 | 1 | {"longitude_of_point_1", "Longitude_Of_1st_Point", nullptr}, |
370 | 1 | {"latitude_of_point_2", "Latitude_Of_2nd_Point", nullptr}, |
371 | 1 | {"longitude_of_point_2", "Longitude_Of_2nd_Point", nullptr}, |
372 | | |
373 | 1 | {"satellite_height", "height", nullptr}, |
374 | | |
375 | 1 | {EPSG_NAME_PARAMETER_FALSE_EASTING, |
376 | 1 | EPSG_NAME_PARAMETER_EASTING_FALSE_ORIGIN, |
377 | 1 | EPSG_NAME_PARAMETER_EASTING_PROJECTION_CENTRE, nullptr}, |
378 | | |
379 | 1 | {EPSG_NAME_PARAMETER_FALSE_NORTHING, |
380 | 1 | EPSG_NAME_PARAMETER_NORTHING_FALSE_ORIGIN, |
381 | 1 | EPSG_NAME_PARAMETER_NORTHING_PROJECTION_CENTRE, nullptr}, |
382 | | |
383 | 1 | {EPSG_NAME_PARAMETER_SCALE_FACTOR_AT_NATURAL_ORIGIN, WKT1_SCALE_FACTOR, |
384 | 1 | EPSG_NAME_PARAMETER_SCALE_FACTOR_INITIAL_LINE, |
385 | 1 | EPSG_NAME_PARAMETER_SCALE_FACTOR_PROJECTION_CENTRE, |
386 | 1 | EPSG_NAME_PARAMETER_SCALE_FACTOR_PSEUDO_STANDARD_PARALLEL, nullptr}, |
387 | | |
388 | 1 | {WKT1_LATITUDE_OF_ORIGIN, WKT1_LATITUDE_OF_CENTER, |
389 | 1 | EPSG_NAME_PARAMETER_LATITUDE_OF_NATURAL_ORIGIN, |
390 | 1 | EPSG_NAME_PARAMETER_LATITUDE_FALSE_ORIGIN, |
391 | 1 | EPSG_NAME_PARAMETER_LATITUDE_PROJECTION_CENTRE, "Central_Parallel", |
392 | 1 | nullptr}, |
393 | | |
394 | 1 | {WKT1_CENTRAL_MERIDIAN, WKT1_LONGITUDE_OF_CENTER, |
395 | 1 | EPSG_NAME_PARAMETER_LONGITUDE_OF_NATURAL_ORIGIN, |
396 | 1 | EPSG_NAME_PARAMETER_LONGITUDE_FALSE_ORIGIN, |
397 | 1 | EPSG_NAME_PARAMETER_LONGITUDE_PROJECTION_CENTRE, |
398 | 1 | EPSG_NAME_PARAMETER_LONGITUDE_OF_ORIGIN, nullptr}, |
399 | | |
400 | 1 | {EPSG_NAME_PARAMETER_AZIMUTH_INITIAL_LINE, |
401 | 1 | EPSG_NAME_PARAMETER_AZIMUTH_PROJECTION_CENTRE, nullptr}, |
402 | | |
403 | 1 | {"pseudo_standard_parallel_1", WKT1_STANDARD_PARALLEL_1, nullptr}, |
404 | 1 | }; |
405 | | |
406 | 12 | for (const auto ¶mList : listOfEquivalentParameterNames) { |
407 | 49 | for (size_t i = 0; paramList[i]; i++) { |
408 | 37 | auto a = metadata::Identifier::canonicalizeName(paramList[i]); |
409 | 90 | for (size_t j = i + 1; paramList[j]; j++) { |
410 | 53 | auto b = metadata::Identifier::canonicalizeName(paramList[j]); |
411 | 53 | set.insert(createEntryEqParam(a, b)); |
412 | 53 | } |
413 | 37 | } |
414 | 12 | } |
415 | 1 | return set; |
416 | 1 | } |
417 | | |
418 | 25.4k | bool areEquivalentParameters(const std::string &a, const std::string &b) { |
419 | | |
420 | 25.4k | static const std::set<std::string> setEquivalentParameters = |
421 | 25.4k | buildSetEquivalentParameters(); |
422 | | |
423 | 25.4k | auto a_can = metadata::Identifier::canonicalizeName(a); |
424 | 25.4k | auto b_can = metadata::Identifier::canonicalizeName(b); |
425 | 25.4k | return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) != |
426 | 25.4k | setEquivalentParameters.end(); |
427 | 25.4k | } |
428 | | |
429 | | // --------------------------------------------------------------------------- |
430 | | |
431 | 84.3k | bool isTimeDependent(const std::string &methodName) { |
432 | 84.3k | return ci_find(methodName, "Time dependent") != std::string::npos || |
433 | 84.3k | ci_find(methodName, "Time-dependent") != std::string::npos; |
434 | 84.3k | } |
435 | | |
436 | | // --------------------------------------------------------------------------- |
437 | | |
438 | | std::string computeConcatenatedName( |
439 | 0 | const std::vector<CoordinateOperationNNPtr> &flattenOps) { |
440 | 0 | std::string name; |
441 | 0 | for (const auto &subOp : flattenOps) { |
442 | 0 | if (!name.empty()) { |
443 | 0 | name += " + "; |
444 | 0 | } |
445 | 0 | const auto &l_name = subOp->nameStr(); |
446 | 0 | if (l_name.empty()) { |
447 | 0 | name += "unnamed"; |
448 | 0 | } else { |
449 | 0 | name += l_name; |
450 | 0 | } |
451 | 0 | } |
452 | 0 | return name; |
453 | 0 | } |
454 | | |
455 | | // --------------------------------------------------------------------------- |
456 | | |
457 | | metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, |
458 | | bool conversionExtentIsWorld, |
459 | 0 | bool &emptyIntersection) { |
460 | 0 | auto conv = dynamic_cast<const Conversion *>(op.get()); |
461 | 0 | if (conv) { |
462 | 0 | emptyIntersection = false; |
463 | 0 | return metadata::Extent::WORLD; |
464 | 0 | } |
465 | 0 | const auto &domains = op->domains(); |
466 | 0 | if (!domains.empty()) { |
467 | 0 | emptyIntersection = false; |
468 | 0 | return domains[0]->domainOfValidity(); |
469 | 0 | } |
470 | 0 | auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); |
471 | 0 | if (!concatenated) { |
472 | 0 | emptyIntersection = false; |
473 | 0 | return nullptr; |
474 | 0 | } |
475 | 0 | return getExtent(concatenated->operations(), conversionExtentIsWorld, |
476 | 0 | emptyIntersection); |
477 | 0 | } |
478 | | |
479 | | // --------------------------------------------------------------------------- |
480 | | |
481 | | static const metadata::ExtentPtr nullExtent{}; |
482 | | |
483 | 0 | const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) { |
484 | 0 | const auto &domains = crs->domains(); |
485 | 0 | if (!domains.empty()) { |
486 | 0 | return domains[0]->domainOfValidity(); |
487 | 0 | } |
488 | 0 | const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get()); |
489 | 0 | if (boundCRS) { |
490 | 0 | return getExtent(boundCRS->baseCRS()); |
491 | 0 | } |
492 | 0 | return nullExtent; |
493 | 0 | } |
494 | | |
495 | | const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, |
496 | 0 | bool &approxOut) { |
497 | 0 | const auto &rawExtent(getExtent(crs)); |
498 | 0 | approxOut = false; |
499 | 0 | if (rawExtent) |
500 | 0 | return rawExtent; |
501 | 0 | const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get()); |
502 | 0 | if (compoundCRS) { |
503 | | // For a compoundCRS, take the intersection of the extent of its |
504 | | // components. |
505 | 0 | const auto &components = compoundCRS->componentReferenceSystems(); |
506 | 0 | metadata::ExtentPtr extent; |
507 | 0 | approxOut = true; |
508 | 0 | for (const auto &component : components) { |
509 | 0 | const auto &componentExtent(getExtent(component)); |
510 | 0 | if (extent && componentExtent) |
511 | 0 | extent = extent->intersection(NN_NO_CHECK(componentExtent)); |
512 | 0 | else if (componentExtent) |
513 | 0 | extent = componentExtent; |
514 | 0 | } |
515 | 0 | return extent; |
516 | 0 | } |
517 | 0 | return rawExtent; |
518 | 0 | } |
519 | | |
520 | | // --------------------------------------------------------------------------- |
521 | | |
522 | | metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops, |
523 | | bool conversionExtentIsWorld, |
524 | 0 | bool &emptyIntersection) { |
525 | 0 | metadata::ExtentPtr res = nullptr; |
526 | 0 | for (const auto &subop : ops) { |
527 | |
|
528 | 0 | const auto &subExtent = |
529 | 0 | getExtent(subop, conversionExtentIsWorld, emptyIntersection); |
530 | 0 | if (!subExtent) { |
531 | 0 | if (emptyIntersection) { |
532 | 0 | return nullptr; |
533 | 0 | } |
534 | 0 | continue; |
535 | 0 | } |
536 | 0 | if (res == nullptr) { |
537 | 0 | res = subExtent; |
538 | 0 | } else { |
539 | 0 | res = res->intersection(NN_NO_CHECK(subExtent)); |
540 | 0 | if (!res) { |
541 | 0 | emptyIntersection = true; |
542 | 0 | return nullptr; |
543 | 0 | } |
544 | 0 | } |
545 | 0 | } |
546 | 0 | emptyIntersection = false; |
547 | 0 | return res; |
548 | 0 | } |
549 | | |
550 | | // --------------------------------------------------------------------------- |
551 | | |
552 | | // Returns the accuracy of an operation, or -1 if unknown |
553 | 0 | double getAccuracy(const CoordinateOperationNNPtr &op) { |
554 | |
|
555 | 0 | if (dynamic_cast<const Conversion *>(op.get())) { |
556 | | // A conversion is perfectly accurate. |
557 | 0 | return 0.0; |
558 | 0 | } |
559 | | |
560 | 0 | double accuracy = -1.0; |
561 | 0 | const auto &accuracies = op->coordinateOperationAccuracies(); |
562 | 0 | if (!accuracies.empty()) { |
563 | 0 | try { |
564 | 0 | accuracy = c_locale_stod(accuracies[0]->value()); |
565 | 0 | } catch (const std::exception &) { |
566 | 0 | } |
567 | 0 | } else { |
568 | 0 | auto concatenated = |
569 | 0 | dynamic_cast<const ConcatenatedOperation *>(op.get()); |
570 | 0 | if (concatenated) { |
571 | 0 | accuracy = getAccuracy(concatenated->operations()); |
572 | 0 | } |
573 | 0 | } |
574 | 0 | return accuracy; |
575 | 0 | } |
576 | | |
577 | | // --------------------------------------------------------------------------- |
578 | | |
579 | | // Returns the accuracy of a set of concatenated operations, or -1 if unknown |
580 | 0 | double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) { |
581 | 0 | double accuracy = -1.0; |
582 | 0 | for (const auto &subop : ops) { |
583 | 0 | const double subops_accuracy = getAccuracy(subop); |
584 | 0 | if (subops_accuracy < 0.0) { |
585 | 0 | return -1.0; |
586 | 0 | } |
587 | 0 | if (accuracy < 0.0) { |
588 | 0 | accuracy = 0.0; |
589 | 0 | } |
590 | 0 | accuracy += subops_accuracy; |
591 | 0 | } |
592 | 0 | return accuracy; |
593 | 0 | } |
594 | | |
595 | | // --------------------------------------------------------------------------- |
596 | | |
597 | | void exportSourceCRSAndTargetCRSToWKT(const CoordinateOperation *co, |
598 | 0 | io::WKTFormatter *formatter) { |
599 | 0 | auto l_sourceCRS = co->sourceCRS(); |
600 | 0 | assert(l_sourceCRS); |
601 | 0 | auto l_targetCRS = co->targetCRS(); |
602 | 0 | assert(l_targetCRS); |
603 | 0 | const bool isWKT2 = formatter->version() == io::WKTFormatter::Version::WKT2; |
604 | 0 | const bool canExportCRSId = |
605 | 0 | (isWKT2 && formatter->use2019Keywords() && |
606 | 0 | !(formatter->idOnTopLevelOnly() && formatter->topLevelHasId())); |
607 | |
|
608 | 0 | const bool hasDomains = !co->domains().empty(); |
609 | 0 | if (hasDomains) { |
610 | 0 | formatter->pushDisableUsage(); |
611 | 0 | } |
612 | |
|
613 | 0 | formatter->startNode(io::WKTConstants::SOURCECRS, false); |
614 | 0 | if (canExportCRSId && !l_sourceCRS->identifiers().empty()) { |
615 | | // fake that top node has no id, so that the sourceCRS id is |
616 | | // considered |
617 | 0 | formatter->pushHasId(false); |
618 | 0 | l_sourceCRS->_exportToWKT(formatter); |
619 | 0 | formatter->popHasId(); |
620 | 0 | } else { |
621 | 0 | l_sourceCRS->_exportToWKT(formatter); |
622 | 0 | } |
623 | 0 | formatter->endNode(); |
624 | |
|
625 | 0 | formatter->startNode(io::WKTConstants::TARGETCRS, false); |
626 | 0 | if (canExportCRSId && !l_targetCRS->identifiers().empty()) { |
627 | | // fake that top node has no id, so that the targetCRS id is |
628 | | // considered |
629 | 0 | formatter->pushHasId(false); |
630 | 0 | l_targetCRS->_exportToWKT(formatter); |
631 | 0 | formatter->popHasId(); |
632 | 0 | } else { |
633 | 0 | l_targetCRS->_exportToWKT(formatter); |
634 | 0 | } |
635 | 0 | formatter->endNode(); |
636 | |
|
637 | 0 | if (hasDomains) { |
638 | 0 | formatter->popDisableUsage(); |
639 | 0 | } |
640 | 0 | } |
641 | | |
642 | | //! @endcond |
643 | | |
644 | | // --------------------------------------------------------------------------- |
645 | | |
646 | | } // namespace operation |
647 | | NS_PROJ_END |