Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * Project: PROJ |
3 | | * Purpose: proj_trans(), proj_trans_array(), proj_trans_generic(), |
4 | | *proj_roundtrip() |
5 | | * |
6 | | * Author: Thomas Knudsen, thokn@sdfe.dk, 2016-06-09/2016-11-06 |
7 | | * |
8 | | ****************************************************************************** |
9 | | * Copyright (c) 2016, 2017 Thomas Knudsen/SDFE |
10 | | * |
11 | | * Permission is hereby granted, free of charge, to any person obtaining a |
12 | | * copy of this software and associated documentation files (the "Software"), |
13 | | * to deal in the Software without restriction, including without limitation |
14 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
15 | | * and/or sell copies of the Software, and to permit persons to whom the |
16 | | * Software is furnished to do so, subject to the following conditions: |
17 | | * |
18 | | * The above copyright notice and this permission notice shall be included |
19 | | * in all copies or substantial portions of the Software. |
20 | | * |
21 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
22 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
23 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
24 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
25 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
26 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
27 | | * DEALINGS IN THE SOFTWARE. |
28 | | *****************************************************************************/ |
29 | | |
30 | | #define FROM_PROJ_CPP |
31 | | |
32 | | #include "proj.h" |
33 | | #include "proj_internal.h" |
34 | | #include <math.h> |
35 | | |
36 | | #include <cmath> |
37 | | #include <limits> |
38 | | |
39 | | #include "proj/internal/io_internal.hpp" |
40 | | |
41 | 5.17M | inline bool pj_coord_has_nans(PJ_COORD coo) { |
42 | 5.17M | return std::isnan(coo.v[0]) || std::isnan(coo.v[1]) || |
43 | 5.17M | std::isnan(coo.v[2]) || std::isnan(coo.v[3]); |
44 | 5.17M | } |
45 | | |
46 | | /**************************************************************************************/ |
47 | | int pj_get_suggested_operation(PJ_CONTEXT *, |
48 | | const std::vector<PJCoordOperation> &opList, |
49 | | const int iExcluded[2], bool skipNonInstantiable, |
50 | | PJ_DIRECTION direction, PJ_COORD coord) |
51 | | /**************************************************************************************/ |
52 | 0 | { |
53 | 0 | const auto normalizeLongitude = [](double x) { |
54 | 0 | if (x > 180.0) { |
55 | 0 | x -= 360.0; |
56 | 0 | if (x > 180.0) |
57 | 0 | x = fmod(x + 180.0, 360.0) - 180.0; |
58 | 0 | } else if (x < -180.0) { |
59 | 0 | x += 360.0; |
60 | 0 | if (x < -180.0) |
61 | 0 | x = fmod(x + 180.0, 360.0) - 180.0; |
62 | 0 | } |
63 | 0 | return x; |
64 | 0 | }; |
65 | | |
66 | | // Select the operations that match the area of use |
67 | | // and has the best accuracy. |
68 | 0 | int iBest = -1; |
69 | 0 | double bestAccuracy = std::numeric_limits<double>::max(); |
70 | 0 | const int nOperations = static_cast<int>(opList.size()); |
71 | 0 | for (int i = 0; i < nOperations; i++) { |
72 | 0 | if (i == iExcluded[0] || i == iExcluded[1]) { |
73 | 0 | continue; |
74 | 0 | } |
75 | 0 | const auto &alt = opList[i]; |
76 | 0 | bool spatialCriterionOK = false; |
77 | 0 | if (direction == PJ_FWD) { |
78 | 0 | if (alt.pjSrcGeocentricToLonLat) { |
79 | 0 | if (alt.minxSrc == -180 && alt.minySrc == -90 && |
80 | 0 | alt.maxxSrc == 180 && alt.maxySrc == 90) { |
81 | 0 | spatialCriterionOK = true; |
82 | 0 | } else { |
83 | 0 | PJ_COORD tmp = coord; |
84 | 0 | pj_fwd4d(tmp, alt.pjSrcGeocentricToLonLat); |
85 | 0 | if (tmp.xyzt.x >= alt.minxSrc && |
86 | 0 | tmp.xyzt.y >= alt.minySrc && |
87 | 0 | tmp.xyzt.x <= alt.maxxSrc && |
88 | 0 | tmp.xyzt.y <= alt.maxySrc) { |
89 | 0 | spatialCriterionOK = true; |
90 | 0 | } |
91 | 0 | } |
92 | 0 | } else if (coord.xyzt.x >= alt.minxSrc && |
93 | 0 | coord.xyzt.y >= alt.minySrc && |
94 | 0 | coord.xyzt.x <= alt.maxxSrc && |
95 | 0 | coord.xyzt.y <= alt.maxySrc) { |
96 | 0 | spatialCriterionOK = true; |
97 | 0 | } else if (alt.srcIsLonLatDegree && coord.xyzt.y >= alt.minySrc && |
98 | 0 | coord.xyzt.y <= alt.maxySrc) { |
99 | 0 | const double normalizedLon = normalizeLongitude(coord.xyzt.x); |
100 | 0 | if (normalizedLon >= alt.minxSrc && |
101 | 0 | normalizedLon <= alt.maxxSrc) { |
102 | 0 | spatialCriterionOK = true; |
103 | 0 | } |
104 | 0 | } else if (alt.srcIsLatLonDegree && coord.xyzt.x >= alt.minxSrc && |
105 | 0 | coord.xyzt.x <= alt.maxxSrc) { |
106 | 0 | const double normalizedLon = normalizeLongitude(coord.xyzt.y); |
107 | 0 | if (normalizedLon >= alt.minySrc && |
108 | 0 | normalizedLon <= alt.maxySrc) { |
109 | 0 | spatialCriterionOK = true; |
110 | 0 | } |
111 | 0 | } |
112 | 0 | } else { |
113 | 0 | if (alt.pjDstGeocentricToLonLat) { |
114 | 0 | if (alt.minxDst == -180 && alt.minyDst == -90 && |
115 | 0 | alt.maxxDst == 180 && alt.maxyDst == 90) { |
116 | 0 | spatialCriterionOK = true; |
117 | 0 | } else { |
118 | 0 | PJ_COORD tmp = coord; |
119 | 0 | pj_fwd4d(tmp, alt.pjDstGeocentricToLonLat); |
120 | 0 | if (tmp.xyzt.x >= alt.minxDst && |
121 | 0 | tmp.xyzt.y >= alt.minyDst && |
122 | 0 | tmp.xyzt.x <= alt.maxxDst && |
123 | 0 | tmp.xyzt.y <= alt.maxyDst) { |
124 | 0 | spatialCriterionOK = true; |
125 | 0 | } |
126 | 0 | } |
127 | 0 | } else if (coord.xyzt.x >= alt.minxDst && |
128 | 0 | coord.xyzt.y >= alt.minyDst && |
129 | 0 | coord.xyzt.x <= alt.maxxDst && |
130 | 0 | coord.xyzt.y <= alt.maxyDst) { |
131 | 0 | spatialCriterionOK = true; |
132 | 0 | } else if (alt.dstIsLonLatDegree && coord.xyzt.y >= alt.minyDst && |
133 | 0 | coord.xyzt.y <= alt.maxyDst) { |
134 | 0 | const double normalizedLon = normalizeLongitude(coord.xyzt.x); |
135 | 0 | if (normalizedLon >= alt.minxDst && |
136 | 0 | normalizedLon <= alt.maxxDst) { |
137 | 0 | spatialCriterionOK = true; |
138 | 0 | } |
139 | 0 | } else if (alt.dstIsLatLonDegree && coord.xyzt.x >= alt.minxDst && |
140 | 0 | coord.xyzt.x <= alt.maxxDst) { |
141 | 0 | const double normalizedLon = normalizeLongitude(coord.xyzt.y); |
142 | 0 | if (normalizedLon >= alt.minyDst && |
143 | 0 | normalizedLon <= alt.maxyDst) { |
144 | 0 | spatialCriterionOK = true; |
145 | 0 | } |
146 | 0 | } |
147 | 0 | } |
148 | |
|
149 | 0 | if (spatialCriterionOK) { |
150 | | // The offshore test is for the "Test bug 245 (use +datum=carthage)" |
151 | | // of test_cs2cs_various.yaml. The long=10 lat=34 point belongs |
152 | | // both to the onshore and offshore Tunisia area of uses, but is |
153 | | // slightly onshore. So in a general way, prefer a onshore area |
154 | | // to a offshore one. |
155 | 0 | if (iBest < 0 || |
156 | 0 | (((alt.accuracy >= 0 && alt.accuracy < bestAccuracy) || |
157 | | // If two operations have the same accuracy, use |
158 | | // the one that has the smallest area |
159 | 0 | (alt.accuracy == bestAccuracy && |
160 | 0 | alt.pseudoArea < opList[iBest].pseudoArea && |
161 | 0 | !(alt.isUnknownAreaName && |
162 | 0 | !opList[iBest].isUnknownAreaName) && |
163 | 0 | !opList[iBest].isPriorityOp)) && |
164 | 0 | !alt.isOffshore)) { |
165 | |
|
166 | 0 | if (skipNonInstantiable && !alt.isInstantiable()) { |
167 | 0 | continue; |
168 | 0 | } |
169 | 0 | iBest = i; |
170 | 0 | bestAccuracy = alt.accuracy; |
171 | 0 | } |
172 | 0 | } |
173 | 0 | } |
174 | |
|
175 | 0 | return iBest; |
176 | 0 | } |
177 | | |
178 | | /**************************************************************************************/ |
179 | | void pj_warn_about_missing_grid(PJ *P) |
180 | | /**************************************************************************************/ |
181 | 0 | { |
182 | 0 | std::string msg("Attempt to use coordinate operation "); |
183 | 0 | msg += proj_get_name(P); |
184 | 0 | msg += " failed."; |
185 | 0 | int gridUsed = proj_coordoperation_get_grid_used_count(P->ctx, P); |
186 | 0 | for (int i = 0; i < gridUsed; ++i) { |
187 | 0 | const char *gridName = ""; |
188 | 0 | int available = FALSE; |
189 | 0 | if (proj_coordoperation_get_grid_used(P->ctx, P, i, &gridName, nullptr, |
190 | 0 | nullptr, nullptr, nullptr, |
191 | 0 | nullptr, &available) && |
192 | 0 | !available) { |
193 | 0 | msg += " Grid "; |
194 | 0 | msg += gridName; |
195 | 0 | msg += " is not available. " |
196 | 0 | "Consult https://proj.org/resource_files.html for guidance."; |
197 | 0 | } |
198 | 0 | } |
199 | 0 | if (!P->errorIfBestTransformationNotAvailable && |
200 | 0 | P->warnIfBestTransformationNotAvailable) { |
201 | 0 | msg += " This might become an error in a future PROJ major release. " |
202 | 0 | "Set the ONLY_BEST option to YES or NO. " |
203 | 0 | "This warning will no longer be emitted (for the current " |
204 | 0 | "transformation instance)."; |
205 | 0 | P->warnIfBestTransformationNotAvailable = false; |
206 | 0 | } |
207 | 0 | pj_log(P->ctx, |
208 | 0 | P->errorIfBestTransformationNotAvailable ? PJ_LOG_ERROR |
209 | 0 | : PJ_LOG_DEBUG, |
210 | 0 | msg.c_str()); |
211 | 0 | } |
212 | | |
213 | | /**************************************************************************************/ |
214 | 5.17M | PJ_COORD proj_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { |
215 | | /*************************************************************************************** |
216 | | Apply the transformation P to the coordinate coord, preferring the 4D |
217 | | interfaces if available. |
218 | | |
219 | | See also pj_approx_2D_trans and pj_approx_3D_trans in pj_internal.c, which |
220 | | work similarly, but prefers the 2D resp. 3D interfaces if available. |
221 | | ***************************************************************************************/ |
222 | 5.17M | if (nullptr == P || direction == PJ_IDENT) |
223 | 0 | return coord; |
224 | 5.17M | if (P->inverted) |
225 | 0 | direction = pj_opposite_direction(direction); |
226 | | |
227 | 5.17M | if (P->iso_obj != nullptr && !P->iso_obj_is_coordinate_operation) { |
228 | 0 | pj_log(P->ctx, PJ_LOG_ERROR, "Object is not a coordinate operation"); |
229 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
230 | 0 | return proj_coord_error(); |
231 | 0 | } |
232 | | |
233 | 5.17M | if (!P->alternativeCoordinateOperations.empty()) { |
234 | 0 | constexpr int N_MAX_RETRY = 2; |
235 | 0 | int iExcluded[N_MAX_RETRY] = {-1, -1}; |
236 | |
|
237 | 0 | bool skipNonInstantiable = P->skipNonInstantiable && |
238 | 0 | !P->warnIfBestTransformationNotAvailable && |
239 | 0 | !P->errorIfBestTransformationNotAvailable; |
240 | 0 | const int nOperations = |
241 | 0 | static_cast<int>(P->alternativeCoordinateOperations.size()); |
242 | | |
243 | | // We may need several attempts. For example the point at |
244 | | // long=-111.5 lat=45.26 falls into the bounding box of the Canadian |
245 | | // ntv2_0.gsb grid, except that it is not in any of the subgrids, being |
246 | | // in the US. We thus need another retry that will select the conus |
247 | | // grid. |
248 | 0 | for (int iRetry = 0; iRetry <= N_MAX_RETRY; iRetry++) { |
249 | | // Do a first pass and select the operations that match the area of |
250 | | // use and has the best accuracy. |
251 | 0 | int iBest = pj_get_suggested_operation( |
252 | 0 | P->ctx, P->alternativeCoordinateOperations, iExcluded, |
253 | 0 | skipNonInstantiable, direction, coord); |
254 | 0 | if (iBest < 0) { |
255 | 0 | break; |
256 | 0 | } |
257 | 0 | if (iRetry > 0) { |
258 | 0 | const int oldErrno = proj_errno_reset(P); |
259 | 0 | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { |
260 | 0 | pj_log(P->ctx, PJ_LOG_DEBUG, |
261 | 0 | proj_context_errno_string(P->ctx, oldErrno)); |
262 | 0 | } |
263 | 0 | pj_log(P->ctx, PJ_LOG_DEBUG, |
264 | 0 | "Did not result in valid result. " |
265 | 0 | "Attempting a retry with another operation."); |
266 | 0 | } |
267 | |
|
268 | 0 | const auto &alt = P->alternativeCoordinateOperations[iBest]; |
269 | 0 | if (P->iCurCoordOp != iBest) { |
270 | 0 | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { |
271 | 0 | std::string msg("Using coordinate operation "); |
272 | 0 | msg += alt.name; |
273 | 0 | pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str()); |
274 | 0 | } |
275 | 0 | P->iCurCoordOp = iBest; |
276 | 0 | } |
277 | 0 | PJ_COORD res = coord; |
278 | 0 | if (alt.pj->hasCoordinateEpoch) |
279 | 0 | coord.xyzt.t = alt.pj->coordinateEpoch; |
280 | 0 | if (direction == PJ_FWD) |
281 | 0 | pj_fwd4d(res, alt.pj); |
282 | 0 | else |
283 | 0 | pj_inv4d(res, alt.pj); |
284 | 0 | if (proj_errno(alt.pj) == PROJ_ERR_OTHER_NETWORK_ERROR) { |
285 | 0 | return proj_coord_error(); |
286 | 0 | } |
287 | 0 | if (res.xyzt.x != HUGE_VAL) { |
288 | 0 | return res; |
289 | 0 | } else if (P->errorIfBestTransformationNotAvailable || |
290 | 0 | P->warnIfBestTransformationNotAvailable) { |
291 | 0 | pj_warn_about_missing_grid(alt.pj); |
292 | 0 | if (P->errorIfBestTransformationNotAvailable) { |
293 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION); |
294 | 0 | return res; |
295 | 0 | } |
296 | 0 | P->warnIfBestTransformationNotAvailable = false; |
297 | 0 | skipNonInstantiable = true; |
298 | 0 | } |
299 | 0 | if (iRetry == N_MAX_RETRY) { |
300 | 0 | break; |
301 | 0 | } |
302 | 0 | iExcluded[iRetry] = iBest; |
303 | 0 | } |
304 | | |
305 | | // In case we did not find an operation whose area of use is compatible |
306 | | // with the input coordinate, then goes through again the list, and |
307 | | // use the first operation that does not require grids. |
308 | 0 | NS_PROJ::io::DatabaseContextPtr dbContext; |
309 | 0 | try { |
310 | 0 | if (P->ctx->cpp_context) { |
311 | 0 | dbContext = |
312 | 0 | P->ctx->cpp_context->getDatabaseContext().as_nullable(); |
313 | 0 | } |
314 | 0 | } catch (const std::exception &) { |
315 | 0 | } |
316 | 0 | for (int i = 0; i < nOperations; i++) { |
317 | 0 | const auto &alt = P->alternativeCoordinateOperations[i]; |
318 | 0 | auto coordOperation = |
319 | 0 | dynamic_cast<NS_PROJ::operation::CoordinateOperation *>( |
320 | 0 | alt.pj->iso_obj.get()); |
321 | 0 | if (coordOperation) { |
322 | 0 | if (coordOperation->gridsNeeded(dbContext, true).empty()) { |
323 | 0 | if (P->iCurCoordOp != i) { |
324 | 0 | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= |
325 | 0 | PJ_LOG_DEBUG) { |
326 | 0 | std::string msg("Using coordinate operation "); |
327 | 0 | msg += alt.name; |
328 | 0 | msg += " as a fallback due to lack of more " |
329 | 0 | "appropriate operations"; |
330 | 0 | pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str()); |
331 | 0 | } |
332 | 0 | P->iCurCoordOp = i; |
333 | 0 | } |
334 | 0 | if (direction == PJ_FWD) { |
335 | 0 | pj_fwd4d(coord, alt.pj); |
336 | 0 | } else { |
337 | 0 | pj_inv4d(coord, alt.pj); |
338 | 0 | } |
339 | 0 | return coord; |
340 | 0 | } |
341 | 0 | } |
342 | 0 | } |
343 | | |
344 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION); |
345 | 0 | return proj_coord_error(); |
346 | 0 | } |
347 | | |
348 | 5.17M | P->iCurCoordOp = |
349 | 5.17M | 0; // dummy value, to be used by proj_trans_get_last_used_operation() |
350 | 5.17M | if (P->hasCoordinateEpoch) |
351 | 0 | coord.xyzt.t = P->coordinateEpoch; |
352 | 5.17M | if (pj_coord_has_nans(coord)) |
353 | 1.00k | coord.v[0] = coord.v[1] = coord.v[2] = coord.v[3] = |
354 | 1.00k | std::numeric_limits<double>::quiet_NaN(); |
355 | 5.17M | else if (direction == PJ_FWD) |
356 | 4.40M | pj_fwd4d(coord, P); |
357 | 774k | else |
358 | 774k | pj_inv4d(coord, P); |
359 | 5.17M | return coord; |
360 | 5.17M | } |
361 | | |
362 | | /*****************************************************************************/ |
363 | | PJ *proj_trans_get_last_used_operation(PJ *P) |
364 | | /****************************************************************************** |
365 | | Return the operation used during the last invocation of proj_trans(). |
366 | | This is especially useful when P has been created with |
367 | | proj_create_crs_to_crs() and has several alternative operations. The returned |
368 | | object must be freed with proj_destroy(). |
369 | | ******************************************************************************/ |
370 | 0 | { |
371 | 0 | if (nullptr == P || P->iCurCoordOp < 0) |
372 | 0 | return nullptr; |
373 | 0 | if (P->alternativeCoordinateOperations.empty()) |
374 | 0 | return proj_clone(P->ctx, P); |
375 | 0 | return proj_clone(P->ctx, |
376 | 0 | P->alternativeCoordinateOperations[P->iCurCoordOp].pj); |
377 | 0 | } |
378 | | |
379 | | /*****************************************************************************/ |
380 | 0 | int proj_trans_array(PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord) { |
381 | | /****************************************************************************** |
382 | | Batch transform an array of PJ_COORD. |
383 | | |
384 | | Performs transformation on all points, even if errors occur on some |
385 | | points. |
386 | | |
387 | | Individual points that fail to transform will have their components set |
388 | | to HUGE_VAL |
389 | | |
390 | | Returns 0 if all coordinates are transformed without error, otherwise |
391 | | returns a precise error number if all coordinates that fail to transform |
392 | | for the same reason, or a generic error code if they fail for different |
393 | | reasons. |
394 | | ******************************************************************************/ |
395 | 0 | size_t i; |
396 | 0 | int retErrno = 0; |
397 | 0 | bool hasSetRetErrno = false; |
398 | 0 | bool sameRetErrno = true; |
399 | |
|
400 | 0 | for (i = 0; i < n; i++) { |
401 | 0 | proj_context_errno_set(P->ctx, 0); |
402 | 0 | coord[i] = proj_trans(P, direction, coord[i]); |
403 | 0 | int thisErrno = proj_errno(P); |
404 | 0 | if (thisErrno != 0) { |
405 | 0 | if (!hasSetRetErrno) { |
406 | 0 | retErrno = thisErrno; |
407 | 0 | hasSetRetErrno = true; |
408 | 0 | } else if (sameRetErrno && retErrno != thisErrno) { |
409 | 0 | sameRetErrno = false; |
410 | 0 | retErrno = PROJ_ERR_COORD_TRANSFM; |
411 | 0 | } |
412 | 0 | } |
413 | 0 | } |
414 | |
|
415 | 0 | proj_context_errno_set(P->ctx, retErrno); |
416 | |
|
417 | 0 | return retErrno; |
418 | 0 | } |
419 | | |
420 | | /*************************************************************************************/ |
421 | | size_t proj_trans_generic(PJ *P, PJ_DIRECTION direction, double *x, size_t sx, |
422 | | size_t nx, double *y, size_t sy, size_t ny, double *z, |
423 | | size_t sz, size_t nz, double *t, size_t st, |
424 | 44.4k | size_t nt) { |
425 | | /************************************************************************************** |
426 | | |
427 | | Transform a series of coordinates, where the individual coordinate |
428 | | dimension may be represented by an array that is either |
429 | | |
430 | | 1. fully populated |
431 | | 2. a null pointer and/or a length of zero, which will be treated as |
432 | | a fully populated array of zeroes |
433 | | 3. of length one, i.e. a constant, which will be treated as a fully |
434 | | populated array of that constant value |
435 | | |
436 | | The strides, sx, sy, sz, st, represent the step length, in bytes, |
437 | | between consecutive elements of the corresponding array. This makes it |
438 | | possible for proj_transform to handle transformation of a large class of |
439 | | application specific data structures, without necessarily understanding the |
440 | | data structure format, as in: |
441 | | |
442 | | typedef struct {double x, y; int quality_level; char |
443 | | surveyor_name[134];} XYQS; XYQS survey[345]; double height = 23.45; PJ *P = |
444 | | {...}; size_t stride = sizeof (XYQS); |
445 | | ... |
446 | | proj_transform ( |
447 | | P, PJ_INV, sizeof(XYQS), |
448 | | &(survey[0].x), stride, 345, (* We have 345 eastings *) |
449 | | &(survey[0].y), stride, 345, (* ...and 345 northings. *) |
450 | | &height, 1, (* The height is the |
451 | | constant 23.45 m *) 0, 0 (* and the time is the |
452 | | constant 0.00 s *) |
453 | | ); |
454 | | |
455 | | This is similar to the inner workings of the pj_transform function, but |
456 | | the stride functionality has been generalized to work for any size of basic |
457 | | unit, not just a fixed number of doubles. |
458 | | |
459 | | In most cases, the stride will be identical for x, y,z, and t, since |
460 | | they will typically be either individual arrays (stride = sizeof(double)), |
461 | | or strided views into an array of application specific data structures |
462 | | (stride = sizeof (...)). |
463 | | |
464 | | But in order to support cases where x, y, z, and t come from |
465 | | heterogeneous sources, individual strides, sx, sy, sz, st, are used. |
466 | | |
467 | | Caveat: Since proj_transform does its work *in place*, this means that |
468 | | even the supposedly constants (i.e. length 1 arrays) will return from the |
469 | | call in altered state. Hence, remember to reinitialize between repeated |
470 | | calls. |
471 | | |
472 | | Return value: Number of transformations completed. |
473 | | |
474 | | **************************************************************************************/ |
475 | 44.4k | PJ_COORD coord = {{0, 0, 0, 0}}; |
476 | 44.4k | size_t i, nmin; |
477 | 44.4k | double null_broadcast = 0; |
478 | 44.4k | double invalid_time = HUGE_VAL; |
479 | | |
480 | 44.4k | if (nullptr == P) |
481 | 0 | return 0; |
482 | | |
483 | 44.4k | if (P->inverted) |
484 | 0 | direction = pj_opposite_direction(direction); |
485 | | |
486 | | /* ignore lengths of null arrays */ |
487 | 44.4k | if (nullptr == x) |
488 | 0 | nx = 0; |
489 | 44.4k | if (nullptr == y) |
490 | 0 | ny = 0; |
491 | 44.4k | if (nullptr == z) |
492 | 44.4k | nz = 0; |
493 | 44.4k | if (nullptr == t) |
494 | 44.4k | nt = 0; |
495 | | |
496 | | /* and make the nullities point to some real world memory for broadcasting |
497 | | * nulls */ |
498 | 44.4k | if (0 == nx) |
499 | 0 | x = &null_broadcast; |
500 | 44.4k | if (0 == ny) |
501 | 0 | y = &null_broadcast; |
502 | 44.4k | if (0 == nz) |
503 | 44.4k | z = &null_broadcast; |
504 | 44.4k | if (0 == nt) |
505 | 44.4k | t = &invalid_time; |
506 | | |
507 | | /* nothing to do? */ |
508 | 44.4k | if (0 == nx + ny + nz + nt) |
509 | 0 | return 0; |
510 | | |
511 | | /* arrays of length 1 are constants, which we broadcast along the longer |
512 | | * arrays */ |
513 | | /* so we need to find the length of the shortest non-unity array to figure |
514 | | * out |
515 | | */ |
516 | | /* how many coordinate pairs we must transform */ |
517 | 44.4k | nmin = (nx > 1) ? nx : (ny > 1) ? ny : (nz > 1) ? nz : (nt > 1) ? nt : 1; |
518 | 44.4k | if ((nx > 1) && (nx < nmin)) |
519 | 0 | nmin = nx; |
520 | 44.4k | if ((ny > 1) && (ny < nmin)) |
521 | 0 | nmin = ny; |
522 | 44.4k | if ((nz > 1) && (nz < nmin)) |
523 | 0 | nmin = nz; |
524 | 44.4k | if ((nt > 1) && (nt < nmin)) |
525 | 0 | nmin = nt; |
526 | | |
527 | | /* Check validity of direction flag */ |
528 | 44.4k | switch (direction) { |
529 | 44.4k | case PJ_FWD: |
530 | 44.4k | case PJ_INV: |
531 | 44.4k | break; |
532 | 0 | case PJ_IDENT: |
533 | 0 | return nmin; |
534 | 44.4k | } |
535 | | |
536 | | /* Arrays of length==0 are broadcast as the constant 0 */ |
537 | | /* Arrays of length==1 are broadcast as their single value */ |
538 | | /* Arrays of length >1 are iterated over (for the first nmin values) */ |
539 | | /* The slightly convolved incremental indexing is used due */ |
540 | | /* to the stride, which may be any size supported by the platform */ |
541 | 3.78M | for (i = 0; i < nmin; i++) { |
542 | 3.73M | coord.xyzt.x = *x; |
543 | 3.73M | coord.xyzt.y = *y; |
544 | 3.73M | coord.xyzt.z = *z; |
545 | 3.73M | coord.xyzt.t = *t; |
546 | | |
547 | 3.73M | coord = proj_trans(P, direction, coord); |
548 | | |
549 | | /* in all full length cases, we overwrite the input with the output, */ |
550 | | /* and step on to the next element. */ |
551 | | /* The casts are somewhat funky, but they compile down to no-ops and */ |
552 | | /* they tell compilers and static analyzers that we know what we do */ |
553 | 3.73M | if (nx > 1) { |
554 | 3.73M | *x = coord.xyzt.x; |
555 | 3.73M | x = reinterpret_cast<double *>((reinterpret_cast<char *>(x) + sx)); |
556 | 3.73M | } |
557 | 3.73M | if (ny > 1) { |
558 | 3.73M | *y = coord.xyzt.y; |
559 | 3.73M | y = reinterpret_cast<double *>((reinterpret_cast<char *>(y) + sy)); |
560 | 3.73M | } |
561 | 3.73M | if (nz > 1) { |
562 | 0 | *z = coord.xyzt.z; |
563 | 0 | z = reinterpret_cast<double *>((reinterpret_cast<char *>(z) + sz)); |
564 | 0 | } |
565 | 3.73M | if (nt > 1) { |
566 | 0 | *t = coord.xyzt.t; |
567 | 0 | t = reinterpret_cast<double *>((reinterpret_cast<char *>(t) + st)); |
568 | 0 | } |
569 | 3.73M | } |
570 | | |
571 | | /* Last time around, we update the length 1 cases with their transformed |
572 | | * alter egos */ |
573 | 44.4k | if (nx == 1) |
574 | 0 | *x = coord.xyzt.x; |
575 | 44.4k | if (ny == 1) |
576 | 0 | *y = coord.xyzt.y; |
577 | 44.4k | if (nz == 1) |
578 | 0 | *z = coord.xyzt.z; |
579 | 44.4k | if (nt == 1) |
580 | 0 | *t = coord.xyzt.t; |
581 | | |
582 | 44.4k | return i; |
583 | 44.4k | } |
584 | | |
585 | 0 | static bool inline coord_is_all_nans(PJ_COORD coo) { |
586 | 0 | return std::isnan(coo.v[0]) && std::isnan(coo.v[1]) && |
587 | 0 | std::isnan(coo.v[2]) && std::isnan(coo.v[3]); |
588 | 0 | } |
589 | | |
590 | | /* Measure numerical deviation after n roundtrips fwd-inv (or inv-fwd) */ |
591 | 0 | double proj_roundtrip(PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) { |
592 | 0 | int i; |
593 | 0 | PJ_COORD t, org; |
594 | |
|
595 | 0 | if (nullptr == P) |
596 | 0 | return HUGE_VAL; |
597 | | |
598 | 0 | if (n < 1) { |
599 | 0 | proj_log_error(P, _("n should be >= 1")); |
600 | 0 | proj_errno_set(P, PROJ_ERR_OTHER_API_MISUSE); |
601 | 0 | return HUGE_VAL; |
602 | 0 | } |
603 | | |
604 | | /* in the first half-step, we generate the output value */ |
605 | 0 | org = *coord; |
606 | 0 | *coord = proj_trans(P, direction, org); |
607 | 0 | t = *coord; |
608 | | |
609 | | /* now we take n-1 full steps in inverse direction: We are */ |
610 | | /* out of phase due to the half step already taken */ |
611 | 0 | for (i = 0; i < n - 1; i++) |
612 | 0 | t = proj_trans(P, direction, |
613 | 0 | proj_trans(P, pj_opposite_direction(direction), t)); |
614 | | |
615 | | /* finally, we take the last half-step */ |
616 | 0 | t = proj_trans(P, pj_opposite_direction(direction), t); |
617 | | |
618 | | /* if we start with any NaN, we expect all NaN as output */ |
619 | 0 | if (pj_coord_has_nans(org) && coord_is_all_nans(t)) { |
620 | 0 | return 0.0; |
621 | 0 | } |
622 | | |
623 | | /* checking for angular *input* since we do a roundtrip, and end where we |
624 | | * begin */ |
625 | 0 | if (proj_angular_input(P, direction)) |
626 | 0 | return proj_lpz_dist(P, org, t); |
627 | | |
628 | 0 | return proj_xyz_dist(org, t); |
629 | 0 | } |