/src/PROJ/src/iso19111/operation/oputils.cpp
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: PROJ |
4 | | * Purpose: ISO19111:2019 implementation |
5 | | * Author: Even Rouault <even dot rouault at spatialys dot com> |
6 | | * |
7 | | ****************************************************************************** |
8 | | * Copyright (c) 2018, Even Rouault <even dot rouault at spatialys dot com> |
9 | | * |
10 | | * Permission is hereby granted, free of charge, to any person obtaining a |
11 | | * copy of this software and associated documentation files (the "Software"), |
12 | | * to deal in the Software without restriction, including without limitation |
13 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
14 | | * and/or sell copies of the Software, and to permit persons to whom the |
15 | | * Software is furnished to do so, subject to the following conditions: |
16 | | * |
17 | | * The above copyright notice and this permission notice shall be included |
18 | | * in all copies or substantial portions of the Software. |
19 | | * |
20 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
21 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
22 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
23 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
24 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
25 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
26 | | * DEALINGS IN THE SOFTWARE. |
27 | | ****************************************************************************/ |
28 | | |
29 | | #ifndef FROM_PROJ_CPP |
30 | | #define FROM_PROJ_CPP |
31 | | #endif |
32 | | |
33 | | #include <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 | 391k | OperationParameterNNPtr createOpParamNameEPSGCode(int code) { |
72 | 391k | const char *name = OperationParameter::getNameForEPSGCode(code); |
73 | 391k | assert(name); |
74 | 391k | return OperationParameter::create(createMapNameEPSGCode(name, code)); |
75 | 391k | } |
76 | | |
77 | | // --------------------------------------------------------------------------- |
78 | | |
79 | 259k | util::PropertyMap createMethodMapNameEPSGCode(int code) { |
80 | 259k | const char *name = nullptr; |
81 | 259k | size_t nMethodNameCodes = 0; |
82 | 259k | const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes); |
83 | 13.2M | for (size_t i = 0; i < nMethodNameCodes; ++i) { |
84 | 13.2M | const auto &tuple = methodNameCodes[i]; |
85 | 13.2M | if (tuple.epsg_code == code) { |
86 | 259k | name = tuple.name; |
87 | 259k | break; |
88 | 259k | } |
89 | 13.2M | } |
90 | 259k | assert(name); |
91 | 259k | return createMapNameEPSGCode(name, code); |
92 | 259k | } |
93 | | |
94 | | // --------------------------------------------------------------------------- |
95 | | |
96 | 73.9k | util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) { |
97 | 73.9k | return util::PropertyMap() |
98 | 73.9k | .set(common::IdentifiedObject::NAME_KEY, name) |
99 | 73.9k | .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) |
100 | 73.9k | .set(metadata::Identifier::CODE_KEY, code); |
101 | 73.9k | } |
102 | | |
103 | | // --------------------------------------------------------------------------- |
104 | | |
105 | 651k | util::PropertyMap createMapNameEPSGCode(const char *name, int code) { |
106 | 651k | return util::PropertyMap() |
107 | 651k | .set(common::IdentifiedObject::NAME_KEY, name) |
108 | 651k | .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) |
109 | 651k | .set(metadata::Identifier::CODE_KEY, code); |
110 | 651k | } |
111 | | |
112 | | // --------------------------------------------------------------------------- |
113 | | |
114 | | util::PropertyMap &addDomains(util::PropertyMap &map, |
115 | 366k | const common::ObjectUsage *obj) { |
116 | | |
117 | 366k | auto ar = util::ArrayOfBaseObject::create(); |
118 | 366k | for (const auto &domain : obj->domains()) { |
119 | 259k | ar->add(domain); |
120 | 259k | } |
121 | 366k | if (!ar->empty()) { |
122 | 259k | map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar); |
123 | 259k | } |
124 | 366k | return map; |
125 | 366k | } |
126 | | |
127 | | // --------------------------------------------------------------------------- |
128 | | |
129 | 254k | static const char *getCRSQualifierStr(const crs::CRSPtr &crs) { |
130 | 254k | auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get()); |
131 | 254k | if (geod) { |
132 | 236k | if (geod->isGeocentric()) { |
133 | 77.7k | return " (geocentric)"; |
134 | 77.7k | } |
135 | 158k | auto geog = dynamic_cast<crs::GeographicCRS *>(geod); |
136 | 158k | if (geog) { |
137 | 158k | if (geog->coordinateSystem()->axisList().size() == 2) { |
138 | 81.8k | return " (geog2D)"; |
139 | 81.8k | } else { |
140 | 76.8k | return " (geog3D)"; |
141 | 76.8k | } |
142 | 158k | } |
143 | 158k | } |
144 | 18.5k | return ""; |
145 | 254k | } |
146 | | |
147 | | // --------------------------------------------------------------------------- |
148 | | |
149 | | std::string buildOpName(const char *opType, const crs::CRSPtr &source, |
150 | 399k | const crs::CRSPtr &target) { |
151 | 399k | std::string res(opType); |
152 | 399k | const auto &srcName = source->nameStr(); |
153 | 399k | const auto &targetName = target->nameStr(); |
154 | 399k | const char *srcQualifier = ""; |
155 | 399k | const char *targetQualifier = ""; |
156 | 399k | if (srcName == targetName) { |
157 | 127k | srcQualifier = getCRSQualifierStr(source); |
158 | 127k | targetQualifier = getCRSQualifierStr(target); |
159 | 127k | if (strcmp(srcQualifier, targetQualifier) == 0) { |
160 | 15.6k | srcQualifier = ""; |
161 | 15.6k | targetQualifier = ""; |
162 | 15.6k | } |
163 | 127k | } |
164 | 399k | res += " from "; |
165 | 399k | res += srcName; |
166 | 399k | res += srcQualifier; |
167 | 399k | res += " to "; |
168 | 399k | res += targetName; |
169 | 399k | res += targetQualifier; |
170 | 399k | return res; |
171 | 399k | } |
172 | | |
173 | | // --------------------------------------------------------------------------- |
174 | | |
175 | | void addModifiedIdentifier(util::PropertyMap &map, |
176 | | const common::IdentifiedObject *obj, bool inverse, |
177 | 461k | bool derivedFrom) { |
178 | | // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE |
179 | | // as identifier. |
180 | | |
181 | 461k | auto ar = util::ArrayOfBaseObject::create(); |
182 | 461k | for (const auto &idSrc : obj->identifiers()) { |
183 | 220k | auto authName = *(idSrc->codeSpace()); |
184 | 220k | const auto &srcCode = idSrc->code(); |
185 | 220k | if (derivedFrom) { |
186 | 77.7k | authName = concat("DERIVED_FROM(", authName, ")"); |
187 | 77.7k | } |
188 | 220k | if (inverse) { |
189 | 142k | if (starts_with(authName, "INVERSE(") && authName.back() == ')') { |
190 | 5.09k | authName = authName.substr(strlen("INVERSE(")); |
191 | 5.09k | authName.resize(authName.size() - 1); |
192 | 137k | } else { |
193 | 137k | authName = concat("INVERSE(", authName, ")"); |
194 | 137k | } |
195 | 142k | } |
196 | 220k | auto idsProp = util::PropertyMap().set( |
197 | 220k | metadata::Identifier::CODESPACE_KEY, authName); |
198 | 220k | ar->add(metadata::Identifier::create(srcCode, idsProp)); |
199 | 220k | } |
200 | 461k | if (!ar->empty()) { |
201 | 219k | map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); |
202 | 219k | } |
203 | 461k | } |
204 | | |
205 | | // --------------------------------------------------------------------------- |
206 | | |
207 | | util::PropertyMap |
208 | 95.4k | createPropertiesForInverse(const OperationMethodNNPtr &method) { |
209 | 95.4k | util::PropertyMap map; |
210 | | |
211 | 95.4k | const std::string &forwardName = method->nameStr(); |
212 | 95.4k | if (!forwardName.empty()) { |
213 | 95.4k | if (starts_with(forwardName, INVERSE_OF)) { |
214 | 1.65k | map.set(common::IdentifiedObject::NAME_KEY, |
215 | 1.65k | forwardName.substr(INVERSE_OF.size())); |
216 | 93.7k | } else { |
217 | 93.7k | map.set(common::IdentifiedObject::NAME_KEY, |
218 | 93.7k | INVERSE_OF + forwardName); |
219 | 93.7k | } |
220 | 95.4k | } |
221 | | |
222 | 95.4k | addModifiedIdentifier(map, method.get(), true, false); |
223 | | |
224 | 95.4k | return map; |
225 | 95.4k | } |
226 | | |
227 | | // --------------------------------------------------------------------------- |
228 | | |
229 | | util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op, |
230 | | bool derivedFrom, |
231 | 288k | bool approximateInversion) { |
232 | 288k | assert(op); |
233 | 288k | util::PropertyMap map; |
234 | | |
235 | | // The domain(s) are unchanged by the inverse operation |
236 | 288k | addDomains(map, op); |
237 | | |
238 | 288k | 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 | 288k | const char *opType; |
243 | 288k | if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) { |
244 | 0 | opType = BALLPARK_GEOCENTRIC_TRANSLATION; |
245 | 288k | } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) { |
246 | 7.74k | opType = BALLPARK_GEOGRAPHIC_OFFSET; |
247 | 280k | } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) { |
248 | 6.97k | opType = NULL_GEOGRAPHIC_OFFSET; |
249 | 273k | } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) { |
250 | 30 | opType = NULL_GEOCENTRIC_TRANSLATION; |
251 | 273k | } else if (dynamic_cast<const Transformation *>(op) || |
252 | 164k | starts_with(forwardName, "Transformation from ")) { |
253 | 133k | opType = "Transformation"; |
254 | 140k | } else if (dynamic_cast<const Conversion *>(op)) { |
255 | 65.0k | opType = "Conversion"; |
256 | 75.2k | } else { |
257 | 75.2k | opType = "Operation"; |
258 | 75.2k | } |
259 | | |
260 | 288k | auto sourceCRS = op->sourceCRS(); |
261 | 288k | auto targetCRS = op->targetCRS(); |
262 | 288k | std::string name; |
263 | 288k | if (!forwardName.empty()) { |
264 | 288k | if (dynamic_cast<const Transformation *>(op) == nullptr && |
265 | 167k | dynamic_cast<const ConcatenatedOperation *>(op) == nullptr && |
266 | 124k | (starts_with(forwardName, INVERSE_OF) || |
267 | 105k | forwardName.find(" + ") != std::string::npos)) { |
268 | 38.3k | std::vector<std::string> tokens; |
269 | 38.3k | std::string curToken; |
270 | 38.3k | bool inString = false; |
271 | 5.04M | for (size_t i = 0; i < forwardName.size(); ++i) { |
272 | 5.00M | if (inString) { |
273 | 34.4k | curToken += forwardName[i]; |
274 | 34.4k | if (forwardName[i] == '\'') { |
275 | 5.67k | inString = false; |
276 | 5.67k | } |
277 | 4.97M | } else if (i + 3 < forwardName.size() && |
278 | 4.86M | memcmp(&forwardName[i], " + ", 3) == 0) { |
279 | 131k | tokens.push_back(curToken); |
280 | 131k | curToken.clear(); |
281 | 131k | i += 2; |
282 | 4.84M | } else if (forwardName[i] == '\'') { |
283 | 5.91k | inString = true; |
284 | 5.91k | curToken += forwardName[i]; |
285 | 4.83M | } else { |
286 | 4.83M | curToken += forwardName[i]; |
287 | 4.83M | } |
288 | 5.00M | } |
289 | 38.3k | if (!curToken.empty()) { |
290 | 38.3k | tokens.push_back(std::move(curToken)); |
291 | 38.3k | } |
292 | 208k | for (size_t i = tokens.size(); i > 0;) { |
293 | 170k | i--; |
294 | 170k | if (!name.empty()) { |
295 | 131k | name += " + "; |
296 | 131k | } |
297 | 170k | if (starts_with(tokens[i], INVERSE_OF)) { |
298 | 48.8k | name += tokens[i].substr(INVERSE_OF.size()); |
299 | 121k | } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME || |
300 | 106k | tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) { |
301 | 28.2k | name += tokens[i]; |
302 | 93.2k | } else { |
303 | 93.2k | name += INVERSE_OF + tokens[i]; |
304 | 93.2k | } |
305 | 170k | } |
306 | 250k | } else if (!sourceCRS || !targetCRS || |
307 | 250k | forwardName != buildOpName(opType, sourceCRS, targetCRS)) { |
308 | 188k | if (forwardName.find(" + ") != std::string::npos) { |
309 | 32.8k | name = INVERSE_OF + '\'' + forwardName + '\''; |
310 | 156k | } else { |
311 | 156k | name = INVERSE_OF + forwardName; |
312 | 156k | } |
313 | 188k | } |
314 | 288k | } |
315 | 288k | if (name.empty() && sourceCRS && targetCRS) { |
316 | 61.2k | name = buildOpName(opType, targetCRS, sourceCRS); |
317 | 61.2k | } |
318 | 288k | if (approximateInversion) { |
319 | 0 | name += " (approx. inversion)"; |
320 | 0 | } |
321 | | |
322 | 288k | if (!name.empty()) { |
323 | 288k | map.set(common::IdentifiedObject::NAME_KEY, name); |
324 | 288k | } |
325 | | |
326 | 288k | const std::string &remarks = op->remarks(); |
327 | 288k | if (!remarks.empty()) { |
328 | 107k | map.set(common::IdentifiedObject::REMARKS_KEY, remarks); |
329 | 107k | } |
330 | | |
331 | 288k | addModifiedIdentifier(map, op, true, derivedFrom); |
332 | | |
333 | 288k | const auto so = dynamic_cast<const SingleOperation *>(op); |
334 | 288k | if (so) { |
335 | 245k | const int soMethodEPSGCode = so->method()->getEPSGCode(); |
336 | 245k | if (soMethodEPSGCode > 0) { |
337 | 114k | map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode); |
338 | 114k | } |
339 | 245k | } |
340 | | |
341 | 288k | return map; |
342 | 288k | } |
343 | | |
344 | | // --------------------------------------------------------------------------- |
345 | | |
346 | | util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties, |
347 | 87.4k | const std::string &defaultName) { |
348 | 87.4k | if (!properties.get(common::IdentifiedObject::NAME_KEY)) { |
349 | 1.49k | return util::PropertyMap(properties) |
350 | 1.49k | .set(common::IdentifiedObject::NAME_KEY, defaultName); |
351 | 85.9k | } else { |
352 | 85.9k | return properties; |
353 | 85.9k | } |
354 | 87.4k | } |
355 | | |
356 | | // --------------------------------------------------------------------------- |
357 | | |
358 | | static std::string createEntryEqParam(const std::string &a, |
359 | 2.89k | const std::string &b) { |
360 | 2.89k | return a < b ? a + b : b + a; |
361 | 2.89k | } |
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 | 2.84k | bool areEquivalentParameters(const std::string &a, const std::string &b) { |
419 | | |
420 | 2.84k | static const std::set<std::string> setEquivalentParameters = |
421 | 2.84k | buildSetEquivalentParameters(); |
422 | | |
423 | 2.84k | auto a_can = metadata::Identifier::canonicalizeName(a); |
424 | 2.84k | auto b_can = metadata::Identifier::canonicalizeName(b); |
425 | 2.84k | return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) != |
426 | 2.84k | setEquivalentParameters.end(); |
427 | 2.84k | } |
428 | | |
429 | | // --------------------------------------------------------------------------- |
430 | | |
431 | 979k | bool isTimeDependent(const std::string &methodName) { |
432 | 979k | return ci_find(methodName, "Time dependent") != std::string::npos || |
433 | 979k | ci_find(methodName, "Time-dependent") != std::string::npos; |
434 | 979k | } |
435 | | |
436 | | // --------------------------------------------------------------------------- |
437 | | |
438 | | std::string computeConcatenatedName( |
439 | 133k | const std::vector<CoordinateOperationNNPtr> &flattenOps) { |
440 | 133k | std::string name; |
441 | 456k | for (const auto &subOp : flattenOps) { |
442 | 456k | if (!name.empty()) { |
443 | 322k | name += " + "; |
444 | 322k | } |
445 | 456k | const auto &l_name = subOp->nameStr(); |
446 | 456k | if (l_name.empty()) { |
447 | 5 | name += "unnamed"; |
448 | 456k | } else { |
449 | 456k | name += l_name; |
450 | 456k | } |
451 | 456k | } |
452 | 133k | return name; |
453 | 133k | } |
454 | | |
455 | | // --------------------------------------------------------------------------- |
456 | | |
457 | | metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, |
458 | | bool conversionExtentIsWorld, |
459 | 466k | bool &emptyIntersection) { |
460 | 466k | auto conv = dynamic_cast<const Conversion *>(op.get()); |
461 | 466k | if (conv) { |
462 | 153k | emptyIntersection = false; |
463 | 153k | return metadata::Extent::WORLD; |
464 | 153k | } |
465 | 313k | const auto &domains = op->domains(); |
466 | 313k | if (!domains.empty()) { |
467 | 246k | emptyIntersection = false; |
468 | 246k | return domains[0]->domainOfValidity(); |
469 | 246k | } |
470 | 67.5k | auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); |
471 | 67.5k | if (!concatenated) { |
472 | 61.5k | emptyIntersection = false; |
473 | 61.5k | return nullptr; |
474 | 61.5k | } |
475 | 5.98k | return getExtent(concatenated->operations(), conversionExtentIsWorld, |
476 | 5.98k | emptyIntersection); |
477 | 67.5k | } |
478 | | |
479 | | // --------------------------------------------------------------------------- |
480 | | |
481 | | static const metadata::ExtentPtr nullExtent{}; |
482 | | |
483 | 163k | const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) { |
484 | 163k | const auto &domains = crs->domains(); |
485 | 163k | if (!domains.empty()) { |
486 | 109k | return domains[0]->domainOfValidity(); |
487 | 109k | } |
488 | 53.9k | const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get()); |
489 | 53.9k | if (boundCRS) { |
490 | 7.49k | return getExtent(boundCRS->baseCRS()); |
491 | 7.49k | } |
492 | 46.4k | return nullExtent; |
493 | 53.9k | } |
494 | | |
495 | | const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, |
496 | 45.3k | bool &approxOut) { |
497 | 45.3k | const auto &rawExtent(getExtent(crs)); |
498 | 45.3k | approxOut = false; |
499 | 45.3k | if (rawExtent) |
500 | 29.4k | return rawExtent; |
501 | 15.8k | const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get()); |
502 | 15.8k | if (compoundCRS) { |
503 | | // For a compoundCRS, take the intersection of the extent of its |
504 | | // components. |
505 | 3.13k | const auto &components = compoundCRS->componentReferenceSystems(); |
506 | 3.13k | metadata::ExtentPtr extent; |
507 | 3.13k | approxOut = true; |
508 | 6.33k | for (const auto &component : components) { |
509 | 6.33k | const auto &componentExtent(getExtent(component)); |
510 | 6.33k | if (extent && componentExtent) |
511 | 44 | extent = extent->intersection(NN_NO_CHECK(componentExtent)); |
512 | 6.28k | else if (componentExtent) |
513 | 78 | extent = componentExtent; |
514 | 6.33k | } |
515 | 3.13k | return extent; |
516 | 3.13k | } |
517 | 12.7k | return rawExtent; |
518 | 15.8k | } |
519 | | |
520 | | // --------------------------------------------------------------------------- |
521 | | |
522 | | metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops, |
523 | | bool conversionExtentIsWorld, |
524 | 107k | bool &emptyIntersection) { |
525 | 107k | metadata::ExtentPtr res = nullptr; |
526 | 351k | for (const auto &subop : ops) { |
527 | | |
528 | 351k | const auto &subExtent = |
529 | 351k | getExtent(subop, conversionExtentIsWorld, emptyIntersection); |
530 | 351k | if (!subExtent) { |
531 | 66.5k | if (emptyIntersection) { |
532 | 0 | return nullptr; |
533 | 0 | } |
534 | 66.5k | continue; |
535 | 66.5k | } |
536 | 285k | if (res == nullptr) { |
537 | 99.7k | res = subExtent; |
538 | 185k | } else { |
539 | 185k | res = res->intersection(NN_NO_CHECK(subExtent)); |
540 | 185k | if (!res) { |
541 | 14.7k | emptyIntersection = true; |
542 | 14.7k | return nullptr; |
543 | 14.7k | } |
544 | 185k | } |
545 | 285k | } |
546 | 92.2k | emptyIntersection = false; |
547 | 92.2k | return res; |
548 | 107k | } |
549 | | |
550 | | // --------------------------------------------------------------------------- |
551 | | |
552 | | // Returns the accuracy of an operation, or -1 if unknown |
553 | 395k | double getAccuracy(const CoordinateOperationNNPtr &op) { |
554 | | |
555 | 395k | if (dynamic_cast<const Conversion *>(op.get())) { |
556 | | // A conversion is perfectly accurate. |
557 | 140k | return 0.0; |
558 | 140k | } |
559 | | |
560 | 254k | double accuracy = -1.0; |
561 | 254k | const auto &accuracies = op->coordinateOperationAccuracies(); |
562 | 254k | if (!accuracies.empty()) { |
563 | 127k | try { |
564 | 127k | accuracy = c_locale_stod(accuracies[0]->value()); |
565 | 127k | } catch (const std::exception &) { |
566 | 0 | } |
567 | 127k | } else { |
568 | 127k | auto concatenated = |
569 | 127k | dynamic_cast<const ConcatenatedOperation *>(op.get()); |
570 | 127k | if (concatenated) { |
571 | 36.4k | accuracy = getAccuracy(concatenated->operations()); |
572 | 36.4k | } |
573 | 127k | } |
574 | 254k | return accuracy; |
575 | 254k | } |
576 | | |
577 | | // --------------------------------------------------------------------------- |
578 | | |
579 | | // Returns the accuracy of a set of concatenated operations, or -1 if unknown |
580 | 122k | double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) { |
581 | 122k | double accuracy = -1.0; |
582 | 306k | for (const auto &subop : ops) { |
583 | 306k | const double subops_accuracy = getAccuracy(subop); |
584 | 306k | if (subops_accuracy < 0.0) { |
585 | 86.5k | return -1.0; |
586 | 86.5k | } |
587 | 220k | if (accuracy < 0.0) { |
588 | 99.6k | accuracy = 0.0; |
589 | 99.6k | } |
590 | 220k | accuracy += subops_accuracy; |
591 | 220k | } |
592 | 36.2k | return accuracy; |
593 | 122k | } |
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 |