/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 | 364k | OperationParameterNNPtr createOpParamNameEPSGCode(int code) { |
72 | 364k | const char *name = OperationParameter::getNameForEPSGCode(code); |
73 | 364k | assert(name); |
74 | 364k | return OperationParameter::create(createMapNameEPSGCode(name, code)); |
75 | 364k | } |
76 | | |
77 | | // --------------------------------------------------------------------------- |
78 | | |
79 | 240k | util::PropertyMap createMethodMapNameEPSGCode(int code) { |
80 | 240k | const char *name = nullptr; |
81 | 240k | size_t nMethodNameCodes = 0; |
82 | 240k | const auto methodNameCodes = getMethodNameCodes(nMethodNameCodes); |
83 | 12.1M | for (size_t i = 0; i < nMethodNameCodes; ++i) { |
84 | 12.1M | const auto &tuple = methodNameCodes[i]; |
85 | 12.1M | if (tuple.epsg_code == code) { |
86 | 240k | name = tuple.name; |
87 | 240k | break; |
88 | 240k | } |
89 | 12.1M | } |
90 | 240k | assert(name); |
91 | 240k | return createMapNameEPSGCode(name, code); |
92 | 240k | } |
93 | | |
94 | | // --------------------------------------------------------------------------- |
95 | | |
96 | 72.0k | util::PropertyMap createMapNameEPSGCode(const std::string &name, int code) { |
97 | 72.0k | return util::PropertyMap() |
98 | 72.0k | .set(common::IdentifiedObject::NAME_KEY, name) |
99 | 72.0k | .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) |
100 | 72.0k | .set(metadata::Identifier::CODE_KEY, code); |
101 | 72.0k | } |
102 | | |
103 | | // --------------------------------------------------------------------------- |
104 | | |
105 | 604k | util::PropertyMap createMapNameEPSGCode(const char *name, int code) { |
106 | 604k | return util::PropertyMap() |
107 | 604k | .set(common::IdentifiedObject::NAME_KEY, name) |
108 | 604k | .set(metadata::Identifier::CODESPACE_KEY, metadata::Identifier::EPSG) |
109 | 604k | .set(metadata::Identifier::CODE_KEY, code); |
110 | 604k | } |
111 | | |
112 | | // --------------------------------------------------------------------------- |
113 | | |
114 | | util::PropertyMap &addDomains(util::PropertyMap &map, |
115 | 364k | const common::ObjectUsage *obj) { |
116 | | |
117 | 364k | auto ar = util::ArrayOfBaseObject::create(); |
118 | 364k | for (const auto &domain : obj->domains()) { |
119 | 261k | ar->add(domain); |
120 | 261k | } |
121 | 364k | if (!ar->empty()) { |
122 | 261k | map.set(common::ObjectUsage::OBJECT_DOMAIN_KEY, ar); |
123 | 261k | } |
124 | 364k | return map; |
125 | 364k | } |
126 | | |
127 | | // --------------------------------------------------------------------------- |
128 | | |
129 | 230k | static const char *getCRSQualifierStr(const crs::CRSPtr &crs) { |
130 | 230k | auto geod = dynamic_cast<crs::GeodeticCRS *>(crs.get()); |
131 | 230k | if (geod) { |
132 | 213k | if (geod->isGeocentric()) { |
133 | 70.6k | return " (geocentric)"; |
134 | 70.6k | } |
135 | 142k | auto geog = dynamic_cast<crs::GeographicCRS *>(geod); |
136 | 142k | if (geog) { |
137 | 142k | if (geog->coordinateSystem()->axisList().size() == 2) { |
138 | 74.8k | return " (geog2D)"; |
139 | 74.8k | } else { |
140 | 67.2k | return " (geog3D)"; |
141 | 67.2k | } |
142 | 142k | } |
143 | 142k | } |
144 | 17.4k | return ""; |
145 | 230k | } |
146 | | |
147 | | // --------------------------------------------------------------------------- |
148 | | |
149 | | std::string buildOpName(const char *opType, const crs::CRSPtr &source, |
150 | 376k | const crs::CRSPtr &target) { |
151 | 376k | std::string res(opType); |
152 | 376k | const auto &srcName = source->nameStr(); |
153 | 376k | const auto &targetName = target->nameStr(); |
154 | 376k | const char *srcQualifier = ""; |
155 | 376k | const char *targetQualifier = ""; |
156 | 376k | if (srcName == targetName) { |
157 | 115k | srcQualifier = getCRSQualifierStr(source); |
158 | 115k | targetQualifier = getCRSQualifierStr(target); |
159 | 115k | if (strcmp(srcQualifier, targetQualifier) == 0) { |
160 | 14.1k | srcQualifier = ""; |
161 | 14.1k | targetQualifier = ""; |
162 | 14.1k | } |
163 | 115k | } |
164 | 376k | res += " from "; |
165 | 376k | res += srcName; |
166 | 376k | res += srcQualifier; |
167 | 376k | res += " to "; |
168 | 376k | res += targetName; |
169 | 376k | res += targetQualifier; |
170 | 376k | return res; |
171 | 376k | } |
172 | | |
173 | | // --------------------------------------------------------------------------- |
174 | | |
175 | | void addModifiedIdentifier(util::PropertyMap &map, |
176 | | const common::IdentifiedObject *obj, bool inverse, |
177 | 463k | bool derivedFrom) { |
178 | | // If original operation is AUTH:CODE, then assign INVERSE(AUTH):CODE |
179 | | // as identifier. |
180 | | |
181 | 463k | auto ar = util::ArrayOfBaseObject::create(); |
182 | 463k | for (const auto &idSrc : obj->identifiers()) { |
183 | 225k | auto authName = *(idSrc->codeSpace()); |
184 | 225k | const auto &srcCode = idSrc->code(); |
185 | 225k | if (derivedFrom) { |
186 | 78.0k | authName = concat("DERIVED_FROM(", authName, ")"); |
187 | 78.0k | } |
188 | 225k | if (inverse) { |
189 | 147k | if (starts_with(authName, "INVERSE(") && authName.back() == ')') { |
190 | 4.52k | authName = authName.substr(strlen("INVERSE(")); |
191 | 4.52k | authName.resize(authName.size() - 1); |
192 | 142k | } else { |
193 | 142k | authName = concat("INVERSE(", authName, ")"); |
194 | 142k | } |
195 | 147k | } |
196 | 225k | auto idsProp = util::PropertyMap().set( |
197 | 225k | metadata::Identifier::CODESPACE_KEY, authName); |
198 | 225k | ar->add(metadata::Identifier::create(srcCode, idsProp)); |
199 | 225k | } |
200 | 463k | if (!ar->empty()) { |
201 | 225k | map.set(common::IdentifiedObject::IDENTIFIERS_KEY, ar); |
202 | 225k | } |
203 | 463k | } |
204 | | |
205 | | // --------------------------------------------------------------------------- |
206 | | |
207 | | util::PropertyMap |
208 | 97.9k | createPropertiesForInverse(const OperationMethodNNPtr &method) { |
209 | 97.9k | util::PropertyMap map; |
210 | | |
211 | 97.9k | const std::string &forwardName = method->nameStr(); |
212 | 97.9k | if (!forwardName.empty()) { |
213 | 97.9k | if (starts_with(forwardName, INVERSE_OF)) { |
214 | 1.43k | map.set(common::IdentifiedObject::NAME_KEY, |
215 | 1.43k | forwardName.substr(INVERSE_OF.size())); |
216 | 96.4k | } else { |
217 | 96.4k | map.set(common::IdentifiedObject::NAME_KEY, |
218 | 96.4k | INVERSE_OF + forwardName); |
219 | 96.4k | } |
220 | 97.9k | } |
221 | | |
222 | 97.9k | addModifiedIdentifier(map, method.get(), true, false); |
223 | | |
224 | 97.9k | return map; |
225 | 97.9k | } |
226 | | |
227 | | // --------------------------------------------------------------------------- |
228 | | |
229 | | util::PropertyMap createPropertiesForInverse(const CoordinateOperation *op, |
230 | | bool derivedFrom, |
231 | 287k | bool approximateInversion) { |
232 | 287k | assert(op); |
233 | 287k | util::PropertyMap map; |
234 | | |
235 | | // The domain(s) are unchanged by the inverse operation |
236 | 287k | addDomains(map, op); |
237 | | |
238 | 287k | 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 | 287k | const char *opType; |
243 | 287k | if (starts_with(forwardName, BALLPARK_GEOCENTRIC_TRANSLATION)) { |
244 | 0 | opType = BALLPARK_GEOCENTRIC_TRANSLATION; |
245 | 287k | } else if (starts_with(forwardName, BALLPARK_GEOGRAPHIC_OFFSET)) { |
246 | 6.85k | opType = BALLPARK_GEOGRAPHIC_OFFSET; |
247 | 280k | } else if (starts_with(forwardName, NULL_GEOGRAPHIC_OFFSET)) { |
248 | 6.13k | opType = NULL_GEOGRAPHIC_OFFSET; |
249 | 274k | } else if (starts_with(forwardName, NULL_GEOCENTRIC_TRANSLATION)) { |
250 | 30 | opType = NULL_GEOCENTRIC_TRANSLATION; |
251 | 274k | } else if (dynamic_cast<const Transformation *>(op) || |
252 | 159k | starts_with(forwardName, "Transformation from ")) { |
253 | 137k | opType = "Transformation"; |
254 | 137k | } else if (dynamic_cast<const Conversion *>(op)) { |
255 | 59.9k | opType = "Conversion"; |
256 | 76.9k | } else { |
257 | 76.9k | opType = "Operation"; |
258 | 76.9k | } |
259 | | |
260 | 287k | auto sourceCRS = op->sourceCRS(); |
261 | 287k | auto targetCRS = op->targetCRS(); |
262 | 287k | std::string name; |
263 | 287k | if (!forwardName.empty()) { |
264 | 287k | if (dynamic_cast<const Transformation *>(op) == nullptr && |
265 | 162k | dynamic_cast<const ConcatenatedOperation *>(op) == nullptr && |
266 | 121k | (starts_with(forwardName, INVERSE_OF) || |
267 | 99.8k | forwardName.find(" + ") != std::string::npos)) { |
268 | 41.3k | std::vector<std::string> tokens; |
269 | 41.3k | std::string curToken; |
270 | 41.3k | bool inString = false; |
271 | 5.10M | for (size_t i = 0; i < forwardName.size(); ++i) { |
272 | 5.06M | if (inString) { |
273 | 35.7k | curToken += forwardName[i]; |
274 | 35.7k | if (forwardName[i] == '\'') { |
275 | 4.85k | inString = false; |
276 | 4.85k | } |
277 | 5.02M | } else if (i + 3 < forwardName.size() && |
278 | 4.90M | memcmp(&forwardName[i], " + ", 3) == 0) { |
279 | 128k | tokens.push_back(curToken); |
280 | 128k | curToken.clear(); |
281 | 128k | i += 2; |
282 | 4.90M | } else if (forwardName[i] == '\'') { |
283 | 5.09k | inString = true; |
284 | 5.09k | curToken += forwardName[i]; |
285 | 4.89M | } else { |
286 | 4.89M | curToken += forwardName[i]; |
287 | 4.89M | } |
288 | 5.06M | } |
289 | 41.3k | if (!curToken.empty()) { |
290 | 41.3k | tokens.push_back(std::move(curToken)); |
291 | 41.3k | } |
292 | 211k | for (size_t i = tokens.size(); i > 0;) { |
293 | 169k | i--; |
294 | 169k | if (!name.empty()) { |
295 | 128k | name += " + "; |
296 | 128k | } |
297 | 169k | if (starts_with(tokens[i], INVERSE_OF)) { |
298 | 49.8k | name += tokens[i].substr(INVERSE_OF.size()); |
299 | 119k | } else if (tokens[i] == AXIS_ORDER_CHANGE_2D_NAME || |
300 | 103k | tokens[i] == AXIS_ORDER_CHANGE_3D_NAME) { |
301 | 30.8k | name += tokens[i]; |
302 | 89.0k | } else { |
303 | 89.0k | name += INVERSE_OF + tokens[i]; |
304 | 89.0k | } |
305 | 169k | } |
306 | 245k | } else if (!sourceCRS || !targetCRS || |
307 | 245k | forwardName != buildOpName(opType, sourceCRS, targetCRS)) { |
308 | 189k | if (forwardName.find(" + ") != std::string::npos) { |
309 | 29.5k | name = INVERSE_OF + '\'' + forwardName + '\''; |
310 | 159k | } else { |
311 | 159k | name = INVERSE_OF + forwardName; |
312 | 159k | } |
313 | 189k | } |
314 | 287k | } |
315 | 287k | if (name.empty() && sourceCRS && targetCRS) { |
316 | 56.3k | name = buildOpName(opType, targetCRS, sourceCRS); |
317 | 56.3k | } |
318 | 287k | if (approximateInversion) { |
319 | 0 | name += " (approx. inversion)"; |
320 | 0 | } |
321 | | |
322 | 287k | if (!name.empty()) { |
323 | 287k | map.set(common::IdentifiedObject::NAME_KEY, name); |
324 | 287k | } |
325 | | |
326 | 287k | const std::string &remarks = op->remarks(); |
327 | 287k | if (!remarks.empty()) { |
328 | 113k | map.set(common::IdentifiedObject::REMARKS_KEY, remarks); |
329 | 113k | } |
330 | | |
331 | 287k | addModifiedIdentifier(map, op, true, derivedFrom); |
332 | | |
333 | 287k | const auto so = dynamic_cast<const SingleOperation *>(op); |
334 | 287k | if (so) { |
335 | 246k | const int soMethodEPSGCode = so->method()->getEPSGCode(); |
336 | 246k | if (soMethodEPSGCode > 0) { |
337 | 110k | map.set("OPERATION_METHOD_EPSG_CODE", soMethodEPSGCode); |
338 | 110k | } |
339 | 246k | } |
340 | | |
341 | 287k | return map; |
342 | 287k | } |
343 | | |
344 | | // --------------------------------------------------------------------------- |
345 | | |
346 | | util::PropertyMap addDefaultNameIfNeeded(const util::PropertyMap &properties, |
347 | 86.6k | const std::string &defaultName) { |
348 | 86.6k | if (!properties.get(common::IdentifiedObject::NAME_KEY)) { |
349 | 1.42k | return util::PropertyMap(properties) |
350 | 1.42k | .set(common::IdentifiedObject::NAME_KEY, defaultName); |
351 | 85.2k | } else { |
352 | 85.2k | return properties; |
353 | 85.2k | } |
354 | 86.6k | } |
355 | | |
356 | | // --------------------------------------------------------------------------- |
357 | | |
358 | | static std::string createEntryEqParam(const std::string &a, |
359 | 2.72k | const std::string &b) { |
360 | 2.72k | return a < b ? a + b : b + a; |
361 | 2.72k | } |
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.66k | bool areEquivalentParameters(const std::string &a, const std::string &b) { |
419 | | |
420 | 2.66k | static const std::set<std::string> setEquivalentParameters = |
421 | 2.66k | buildSetEquivalentParameters(); |
422 | | |
423 | 2.66k | auto a_can = metadata::Identifier::canonicalizeName(a); |
424 | 2.66k | auto b_can = metadata::Identifier::canonicalizeName(b); |
425 | 2.66k | return setEquivalentParameters.find(createEntryEqParam(a_can, b_can)) != |
426 | 2.66k | setEquivalentParameters.end(); |
427 | 2.66k | } |
428 | | |
429 | | // --------------------------------------------------------------------------- |
430 | | |
431 | 961k | bool isTimeDependent(const std::string &methodName) { |
432 | 961k | return ci_find(methodName, "Time dependent") != std::string::npos || |
433 | 961k | ci_find(methodName, "Time-dependent") != std::string::npos; |
434 | 961k | } |
435 | | |
436 | | // --------------------------------------------------------------------------- |
437 | | |
438 | | std::string computeConcatenatedName( |
439 | 125k | const std::vector<CoordinateOperationNNPtr> &flattenOps) { |
440 | 125k | std::string name; |
441 | 444k | for (const auto &subOp : flattenOps) { |
442 | 444k | if (!name.empty()) { |
443 | 319k | name += " + "; |
444 | 319k | } |
445 | 444k | const auto &l_name = subOp->nameStr(); |
446 | 444k | if (l_name.empty()) { |
447 | 7 | name += "unnamed"; |
448 | 444k | } else { |
449 | 444k | name += l_name; |
450 | 444k | } |
451 | 444k | } |
452 | 125k | return name; |
453 | 125k | } |
454 | | |
455 | | // --------------------------------------------------------------------------- |
456 | | |
457 | | metadata::ExtentPtr getExtent(const CoordinateOperationNNPtr &op, |
458 | | bool conversionExtentIsWorld, |
459 | 448k | bool &emptyIntersection) { |
460 | 448k | auto conv = dynamic_cast<const Conversion *>(op.get()); |
461 | 448k | if (conv) { |
462 | 145k | emptyIntersection = false; |
463 | 145k | return metadata::Extent::WORLD; |
464 | 145k | } |
465 | 303k | const auto &domains = op->domains(); |
466 | 303k | if (!domains.empty()) { |
467 | 239k | emptyIntersection = false; |
468 | 239k | return domains[0]->domainOfValidity(); |
469 | 239k | } |
470 | 63.5k | auto concatenated = dynamic_cast<const ConcatenatedOperation *>(op.get()); |
471 | 63.5k | if (!concatenated) { |
472 | 57.8k | emptyIntersection = false; |
473 | 57.8k | return nullptr; |
474 | 57.8k | } |
475 | 5.77k | return getExtent(concatenated->operations(), conversionExtentIsWorld, |
476 | 5.77k | emptyIntersection); |
477 | 63.5k | } |
478 | | |
479 | | // --------------------------------------------------------------------------- |
480 | | |
481 | | static const metadata::ExtentPtr nullExtent{}; |
482 | | |
483 | 141k | const metadata::ExtentPtr &getExtent(const crs::CRSNNPtr &crs) { |
484 | 141k | const auto &domains = crs->domains(); |
485 | 141k | if (!domains.empty()) { |
486 | 92.1k | return domains[0]->domainOfValidity(); |
487 | 92.1k | } |
488 | 49.3k | const auto *boundCRS = dynamic_cast<const crs::BoundCRS *>(crs.get()); |
489 | 49.3k | if (boundCRS) { |
490 | 7.00k | return getExtent(boundCRS->baseCRS()); |
491 | 7.00k | } |
492 | 42.3k | return nullExtent; |
493 | 49.3k | } |
494 | | |
495 | | const metadata::ExtentPtr getExtentPossiblySynthetized(const crs::CRSNNPtr &crs, |
496 | 40.8k | bool &approxOut) { |
497 | 40.8k | const auto &rawExtent(getExtent(crs)); |
498 | 40.8k | approxOut = false; |
499 | 40.8k | if (rawExtent) |
500 | 26.1k | return rawExtent; |
501 | 14.6k | const auto compoundCRS = dynamic_cast<const crs::CompoundCRS *>(crs.get()); |
502 | 14.6k | if (compoundCRS) { |
503 | | // For a compoundCRS, take the intersection of the extent of its |
504 | | // components. |
505 | 2.93k | const auto &components = compoundCRS->componentReferenceSystems(); |
506 | 2.93k | metadata::ExtentPtr extent; |
507 | 2.93k | approxOut = true; |
508 | 5.91k | for (const auto &component : components) { |
509 | 5.91k | const auto &componentExtent(getExtent(component)); |
510 | 5.91k | if (extent && componentExtent) |
511 | 29 | extent = extent->intersection(NN_NO_CHECK(componentExtent)); |
512 | 5.88k | else if (componentExtent) |
513 | 63 | extent = componentExtent; |
514 | 5.91k | } |
515 | 2.93k | return extent; |
516 | 2.93k | } |
517 | 11.7k | return rawExtent; |
518 | 14.6k | } |
519 | | |
520 | | // --------------------------------------------------------------------------- |
521 | | |
522 | | metadata::ExtentPtr getExtent(const std::vector<CoordinateOperationNNPtr> &ops, |
523 | | bool conversionExtentIsWorld, |
524 | 101k | bool &emptyIntersection) { |
525 | 101k | metadata::ExtentPtr res = nullptr; |
526 | 343k | for (const auto &subop : ops) { |
527 | | |
528 | 343k | const auto &subExtent = |
529 | 343k | getExtent(subop, conversionExtentIsWorld, emptyIntersection); |
530 | 343k | if (!subExtent) { |
531 | 62.6k | if (emptyIntersection) { |
532 | 0 | return nullptr; |
533 | 0 | } |
534 | 62.6k | continue; |
535 | 62.6k | } |
536 | 280k | if (res == nullptr) { |
537 | 94.8k | res = subExtent; |
538 | 185k | } else { |
539 | 185k | res = res->intersection(NN_NO_CHECK(subExtent)); |
540 | 185k | if (!res) { |
541 | 15.6k | emptyIntersection = true; |
542 | 15.6k | return nullptr; |
543 | 15.6k | } |
544 | 185k | } |
545 | 280k | } |
546 | 86.3k | emptyIntersection = false; |
547 | 86.3k | return res; |
548 | 101k | } |
549 | | |
550 | | // --------------------------------------------------------------------------- |
551 | | |
552 | | // Returns the accuracy of an operation, or -1 if unknown |
553 | 378k | double getAccuracy(const CoordinateOperationNNPtr &op) { |
554 | | |
555 | 378k | if (dynamic_cast<const Conversion *>(op.get())) { |
556 | | // A conversion is perfectly accurate. |
557 | 132k | return 0.0; |
558 | 132k | } |
559 | | |
560 | 245k | double accuracy = -1.0; |
561 | 245k | const auto &accuracies = op->coordinateOperationAccuracies(); |
562 | 245k | if (!accuracies.empty()) { |
563 | 125k | try { |
564 | 125k | accuracy = c_locale_stod(accuracies[0]->value()); |
565 | 125k | } catch (const std::exception &) { |
566 | 0 | } |
567 | 125k | } else { |
568 | 120k | auto concatenated = |
569 | 120k | dynamic_cast<const ConcatenatedOperation *>(op.get()); |
570 | 120k | if (concatenated) { |
571 | 35.1k | accuracy = getAccuracy(concatenated->operations()); |
572 | 35.1k | } |
573 | 120k | } |
574 | 245k | return accuracy; |
575 | 245k | } |
576 | | |
577 | | // --------------------------------------------------------------------------- |
578 | | |
579 | | // Returns the accuracy of a set of concatenated operations, or -1 if unknown |
580 | 115k | double getAccuracy(const std::vector<CoordinateOperationNNPtr> &ops) { |
581 | 115k | double accuracy = -1.0; |
582 | 296k | for (const auto &subop : ops) { |
583 | 296k | const double subops_accuracy = getAccuracy(subop); |
584 | 296k | if (subops_accuracy < 0.0) { |
585 | 80.7k | return -1.0; |
586 | 80.7k | } |
587 | 215k | if (accuracy < 0.0) { |
588 | 96.2k | accuracy = 0.0; |
589 | 96.2k | } |
590 | 215k | accuracy += subops_accuracy; |
591 | 215k | } |
592 | 34.9k | return accuracy; |
593 | 115k | } |
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 |