Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * Project: PROJ.4 |
3 | | * Purpose: Implement a (currently minimalistic) proj API based primarily |
4 | | * on the PJ_COORD 4D geodetic spatiotemporal data type. |
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 <assert.h> |
33 | | #include <errno.h> |
34 | | #include <stddef.h> |
35 | | #include <stdio.h> |
36 | | #include <stdlib.h> |
37 | | #include <string.h> |
38 | | #ifndef _MSC_VER |
39 | | #include <strings.h> |
40 | | #endif |
41 | | |
42 | | #include <algorithm> |
43 | | #include <limits> |
44 | | |
45 | | #include "filemanager.hpp" |
46 | | #include "geodesic.h" |
47 | | #include "grids.hpp" |
48 | | #include "proj.h" |
49 | | #include "proj_experimental.h" |
50 | | #include "proj_internal.h" |
51 | | #include <cmath> /* for isnan */ |
52 | | #include <math.h> |
53 | | |
54 | | #include "proj/common.hpp" |
55 | | #include "proj/coordinateoperation.hpp" |
56 | | #include "proj/internal/internal.hpp" |
57 | | #include "proj/internal/io_internal.hpp" |
58 | | |
59 | | using namespace NS_PROJ::internal; |
60 | | |
61 | | /* Initialize PJ_COORD struct */ |
62 | 0 | PJ_COORD proj_coord(double x, double y, double z, double t) { |
63 | 0 | PJ_COORD res; |
64 | 0 | res.v[0] = x; |
65 | 0 | res.v[1] = y; |
66 | 0 | res.v[2] = z; |
67 | 0 | res.v[3] = t; |
68 | 0 | return res; |
69 | 0 | } |
70 | | |
71 | 0 | static PJ_DIRECTION opposite_direction(PJ_DIRECTION dir) { |
72 | 0 | return static_cast<PJ_DIRECTION>(-dir); |
73 | 0 | } |
74 | | |
75 | | /*****************************************************************************/ |
76 | 0 | int proj_angular_input(PJ *P, enum PJ_DIRECTION dir) { |
77 | | /****************************************************************************** |
78 | | Returns 1 if the operator P expects angular input coordinates when |
79 | | operating in direction dir, 0 otherwise. |
80 | | dir: {PJ_FWD, PJ_INV} |
81 | | ******************************************************************************/ |
82 | 0 | if (PJ_FWD == dir) |
83 | 0 | return pj_left(P) == PJ_IO_UNITS_RADIANS; |
84 | 0 | return pj_right(P) == PJ_IO_UNITS_RADIANS; |
85 | 0 | } |
86 | | |
87 | | /*****************************************************************************/ |
88 | 0 | int proj_angular_output(PJ *P, enum PJ_DIRECTION dir) { |
89 | | /****************************************************************************** |
90 | | Returns 1 if the operator P provides angular output coordinates when |
91 | | operating in direction dir, 0 otherwise. |
92 | | dir: {PJ_FWD, PJ_INV} |
93 | | ******************************************************************************/ |
94 | 0 | return proj_angular_input(P, opposite_direction(dir)); |
95 | 0 | } |
96 | | |
97 | | /*****************************************************************************/ |
98 | 0 | int proj_degree_input(PJ *P, enum PJ_DIRECTION dir) { |
99 | | /****************************************************************************** |
100 | | Returns 1 if the operator P expects degree input coordinates when |
101 | | operating in direction dir, 0 otherwise. |
102 | | dir: {PJ_FWD, PJ_INV} |
103 | | ******************************************************************************/ |
104 | 0 | if (PJ_FWD == dir) |
105 | 0 | return pj_left(P) == PJ_IO_UNITS_DEGREES; |
106 | 0 | return pj_right(P) == PJ_IO_UNITS_DEGREES; |
107 | 0 | } |
108 | | |
109 | | /*****************************************************************************/ |
110 | 0 | int proj_degree_output(PJ *P, enum PJ_DIRECTION dir) { |
111 | | /****************************************************************************** |
112 | | Returns 1 if the operator P provides degree output coordinates when |
113 | | operating in direction dir, 0 otherwise. |
114 | | dir: {PJ_FWD, PJ_INV} |
115 | | ******************************************************************************/ |
116 | 0 | return proj_degree_input(P, opposite_direction(dir)); |
117 | 0 | } |
118 | | |
119 | | /* Geodesic distance (in meter) + fwd and rev azimuth between two points on the |
120 | | * ellipsoid */ |
121 | 0 | PJ_COORD proj_geod(const PJ *P, PJ_COORD a, PJ_COORD b) { |
122 | 0 | PJ_COORD c; |
123 | 0 | if (!P->geod) { |
124 | 0 | return proj_coord_error(); |
125 | 0 | } |
126 | | /* Note: the geodesic code takes arguments in degrees */ |
127 | 0 | geod_inverse(P->geod, PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), |
128 | 0 | PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), c.v, c.v + 1, |
129 | 0 | c.v + 2); |
130 | | |
131 | | // cppcheck-suppress uninitvar |
132 | 0 | return c; |
133 | 0 | } |
134 | | |
135 | | /* Geodesic distance (in meter) between two points with angular 2D coordinates |
136 | | */ |
137 | 0 | double proj_lp_dist(const PJ *P, PJ_COORD a, PJ_COORD b) { |
138 | 0 | double s12, azi1, azi2; |
139 | | /* Note: the geodesic code takes arguments in degrees */ |
140 | 0 | if (!P->geod) { |
141 | 0 | return HUGE_VAL; |
142 | 0 | } |
143 | 0 | geod_inverse(P->geod, PJ_TODEG(a.lpz.phi), PJ_TODEG(a.lpz.lam), |
144 | 0 | PJ_TODEG(b.lpz.phi), PJ_TODEG(b.lpz.lam), &s12, &azi1, &azi2); |
145 | 0 | return s12; |
146 | 0 | } |
147 | | |
148 | | /* The geodesic distance AND the vertical offset */ |
149 | 0 | double proj_lpz_dist(const PJ *P, PJ_COORD a, PJ_COORD b) { |
150 | 0 | if (HUGE_VAL == a.lpz.lam || HUGE_VAL == b.lpz.lam) |
151 | 0 | return HUGE_VAL; |
152 | 0 | return hypot(proj_lp_dist(P, a, b), a.lpz.z - b.lpz.z); |
153 | 0 | } |
154 | | |
155 | | /* Euclidean distance between two points with linear 2D coordinates */ |
156 | 0 | double proj_xy_dist(PJ_COORD a, PJ_COORD b) { |
157 | 0 | return hypot(a.xy.x - b.xy.x, a.xy.y - b.xy.y); |
158 | 0 | } |
159 | | |
160 | | /* Euclidean distance between two points with linear 3D coordinates */ |
161 | 0 | double proj_xyz_dist(PJ_COORD a, PJ_COORD b) { |
162 | 0 | return hypot(proj_xy_dist(a, b), a.xyz.z - b.xyz.z); |
163 | 0 | } |
164 | | |
165 | 0 | static bool inline coord_is_all_nans(PJ_COORD coo) { |
166 | 0 | return std::isnan(coo.v[0]) && std::isnan(coo.v[1]) && |
167 | 0 | std::isnan(coo.v[2]) && std::isnan(coo.v[3]); |
168 | 0 | } |
169 | | |
170 | 0 | static bool inline coord_has_nans(PJ_COORD coo) { |
171 | 0 | return std::isnan(coo.v[0]) || std::isnan(coo.v[1]) || |
172 | 0 | std::isnan(coo.v[2]) || std::isnan(coo.v[3]); |
173 | 0 | } |
174 | | |
175 | | /* Measure numerical deviation after n roundtrips fwd-inv (or inv-fwd) */ |
176 | 0 | double proj_roundtrip(PJ *P, PJ_DIRECTION direction, int n, PJ_COORD *coord) { |
177 | 0 | int i; |
178 | 0 | PJ_COORD t, org; |
179 | |
|
180 | 0 | if (nullptr == P) |
181 | 0 | return HUGE_VAL; |
182 | | |
183 | 0 | if (n < 1) { |
184 | 0 | proj_log_error(P, _("n should be >= 1")); |
185 | 0 | proj_errno_set(P, PROJ_ERR_OTHER_API_MISUSE); |
186 | 0 | return HUGE_VAL; |
187 | 0 | } |
188 | | |
189 | | /* in the first half-step, we generate the output value */ |
190 | 0 | org = *coord; |
191 | 0 | *coord = proj_trans(P, direction, org); |
192 | 0 | t = *coord; |
193 | | |
194 | | /* now we take n-1 full steps in inverse direction: We are */ |
195 | | /* out of phase due to the half step already taken */ |
196 | 0 | for (i = 0; i < n - 1; i++) |
197 | 0 | t = proj_trans(P, direction, |
198 | 0 | proj_trans(P, opposite_direction(direction), t)); |
199 | | |
200 | | /* finally, we take the last half-step */ |
201 | 0 | t = proj_trans(P, opposite_direction(direction), t); |
202 | | |
203 | | /* if we start with any NaN, we expect all NaN as output */ |
204 | 0 | if (coord_has_nans(org) && coord_is_all_nans(t)) { |
205 | 0 | return 0.0; |
206 | 0 | } |
207 | | |
208 | | /* checking for angular *input* since we do a roundtrip, and end where we |
209 | | * begin */ |
210 | 0 | if (proj_angular_input(P, direction)) |
211 | 0 | return proj_lpz_dist(P, org, t); |
212 | | |
213 | 0 | return proj_xyz_dist(org, t); |
214 | 0 | } |
215 | | |
216 | | /**************************************************************************************/ |
217 | | int pj_get_suggested_operation(PJ_CONTEXT *, |
218 | | const std::vector<PJCoordOperation> &opList, |
219 | | const int iExcluded[2], bool skipNonInstantiable, |
220 | | PJ_DIRECTION direction, PJ_COORD coord) |
221 | | /**************************************************************************************/ |
222 | 0 | { |
223 | 0 | const auto normalizeLongitude = [](double x) { |
224 | 0 | if (x > 180.0) { |
225 | 0 | x -= 360.0; |
226 | 0 | if (x > 180.0) |
227 | 0 | x = fmod(x + 180.0, 360.0) - 180.0; |
228 | 0 | } else if (x < -180.0) { |
229 | 0 | x += 360.0; |
230 | 0 | if (x < -180.0) |
231 | 0 | x = fmod(x + 180.0, 360.0) - 180.0; |
232 | 0 | } |
233 | 0 | return x; |
234 | 0 | }; |
235 | | |
236 | | // Select the operations that match the area of use |
237 | | // and has the best accuracy. |
238 | 0 | int iBest = -1; |
239 | 0 | double bestAccuracy = std::numeric_limits<double>::max(); |
240 | 0 | const int nOperations = static_cast<int>(opList.size()); |
241 | 0 | for (int i = 0; i < nOperations; i++) { |
242 | 0 | if (i == iExcluded[0] || i == iExcluded[1]) { |
243 | 0 | continue; |
244 | 0 | } |
245 | 0 | const auto &alt = opList[i]; |
246 | 0 | bool spatialCriterionOK = false; |
247 | 0 | if (direction == PJ_FWD) { |
248 | 0 | if (alt.pjSrcGeocentricToLonLat) { |
249 | 0 | if (alt.minxSrc == -180 && alt.minySrc == -90 && |
250 | 0 | alt.maxxSrc == 180 && alt.maxySrc == 90) { |
251 | 0 | spatialCriterionOK = true; |
252 | 0 | } else { |
253 | 0 | PJ_COORD tmp = coord; |
254 | 0 | pj_fwd4d(tmp, alt.pjSrcGeocentricToLonLat); |
255 | 0 | if (tmp.xyzt.x >= alt.minxSrc && |
256 | 0 | tmp.xyzt.y >= alt.minySrc && |
257 | 0 | tmp.xyzt.x <= alt.maxxSrc && |
258 | 0 | tmp.xyzt.y <= alt.maxySrc) { |
259 | 0 | spatialCriterionOK = true; |
260 | 0 | } |
261 | 0 | } |
262 | 0 | } else if (coord.xyzt.x >= alt.minxSrc && |
263 | 0 | coord.xyzt.y >= alt.minySrc && |
264 | 0 | coord.xyzt.x <= alt.maxxSrc && |
265 | 0 | coord.xyzt.y <= alt.maxySrc) { |
266 | 0 | spatialCriterionOK = true; |
267 | 0 | } else if (alt.srcIsLonLatDegree && coord.xyzt.y >= alt.minySrc && |
268 | 0 | coord.xyzt.y <= alt.maxySrc) { |
269 | 0 | const double normalizedLon = normalizeLongitude(coord.xyzt.x); |
270 | 0 | if (normalizedLon >= alt.minxSrc && |
271 | 0 | normalizedLon <= alt.maxxSrc) { |
272 | 0 | spatialCriterionOK = true; |
273 | 0 | } |
274 | 0 | } else if (alt.srcIsLatLonDegree && coord.xyzt.x >= alt.minxSrc && |
275 | 0 | coord.xyzt.x <= alt.maxxSrc) { |
276 | 0 | const double normalizedLon = normalizeLongitude(coord.xyzt.y); |
277 | 0 | if (normalizedLon >= alt.minySrc && |
278 | 0 | normalizedLon <= alt.maxySrc) { |
279 | 0 | spatialCriterionOK = true; |
280 | 0 | } |
281 | 0 | } |
282 | 0 | } else { |
283 | 0 | if (alt.pjDstGeocentricToLonLat) { |
284 | 0 | if (alt.minxDst == -180 && alt.minyDst == -90 && |
285 | 0 | alt.maxxDst == 180 && alt.maxyDst == 90) { |
286 | 0 | spatialCriterionOK = true; |
287 | 0 | } else { |
288 | 0 | PJ_COORD tmp = coord; |
289 | 0 | pj_fwd4d(tmp, alt.pjDstGeocentricToLonLat); |
290 | 0 | if (tmp.xyzt.x >= alt.minxDst && |
291 | 0 | tmp.xyzt.y >= alt.minyDst && |
292 | 0 | tmp.xyzt.x <= alt.maxxDst && |
293 | 0 | tmp.xyzt.y <= alt.maxyDst) { |
294 | 0 | spatialCriterionOK = true; |
295 | 0 | } |
296 | 0 | } |
297 | 0 | } else if (coord.xyzt.x >= alt.minxDst && |
298 | 0 | coord.xyzt.y >= alt.minyDst && |
299 | 0 | coord.xyzt.x <= alt.maxxDst && |
300 | 0 | coord.xyzt.y <= alt.maxyDst) { |
301 | 0 | spatialCriterionOK = true; |
302 | 0 | } else if (alt.dstIsLonLatDegree && coord.xyzt.y >= alt.minyDst && |
303 | 0 | coord.xyzt.y <= alt.maxyDst) { |
304 | 0 | const double normalizedLon = normalizeLongitude(coord.xyzt.x); |
305 | 0 | if (normalizedLon >= alt.minxDst && |
306 | 0 | normalizedLon <= alt.maxxDst) { |
307 | 0 | spatialCriterionOK = true; |
308 | 0 | } |
309 | 0 | } else if (alt.dstIsLatLonDegree && coord.xyzt.x >= alt.minxDst && |
310 | 0 | coord.xyzt.x <= alt.maxxDst) { |
311 | 0 | const double normalizedLon = normalizeLongitude(coord.xyzt.y); |
312 | 0 | if (normalizedLon >= alt.minyDst && |
313 | 0 | normalizedLon <= alt.maxyDst) { |
314 | 0 | spatialCriterionOK = true; |
315 | 0 | } |
316 | 0 | } |
317 | 0 | } |
318 | |
|
319 | 0 | if (spatialCriterionOK) { |
320 | | // The offshore test is for the "Test bug 245 (use +datum=carthage)" |
321 | | // of testvarious. The long=10 lat=34 point belongs both to the |
322 | | // onshore and offshore Tunisia area of uses, but is slightly |
323 | | // onshore. So in a general way, prefer a onshore area to a |
324 | | // offshore one. |
325 | 0 | if (iBest < 0 || |
326 | 0 | (((alt.accuracy >= 0 && alt.accuracy < bestAccuracy) || |
327 | | // If two operations have the same accuracy, use |
328 | | // the one that has the smallest area |
329 | 0 | (alt.accuracy == bestAccuracy && |
330 | 0 | alt.pseudoArea < opList[iBest].pseudoArea && |
331 | 0 | !(alt.isUnknownAreaName && |
332 | 0 | !opList[iBest].isUnknownAreaName) && |
333 | 0 | !opList[iBest].isPriorityOp)) && |
334 | 0 | !alt.isOffshore)) { |
335 | |
|
336 | 0 | if (skipNonInstantiable && !alt.isInstantiable()) { |
337 | 0 | continue; |
338 | 0 | } |
339 | 0 | iBest = i; |
340 | 0 | bestAccuracy = alt.accuracy; |
341 | 0 | } |
342 | 0 | } |
343 | 0 | } |
344 | |
|
345 | 0 | return iBest; |
346 | 0 | } |
347 | | |
348 | | //! @cond Doxygen_Suppress |
349 | | /**************************************************************************************/ |
350 | 0 | PJCoordOperation::~PJCoordOperation() { |
351 | | /**************************************************************************************/ |
352 | 0 | proj_destroy(pj); |
353 | 0 | proj_destroy(pjSrcGeocentricToLonLat); |
354 | 0 | proj_destroy(pjDstGeocentricToLonLat); |
355 | 0 | } |
356 | | |
357 | | /**************************************************************************************/ |
358 | 0 | bool PJCoordOperation::isInstantiable() const { |
359 | | /**************************************************************************************/ |
360 | 0 | if (isInstantiableCached == INSTANTIABLE_STATUS_UNKNOWN) |
361 | 0 | isInstantiableCached = proj_coordoperation_is_instantiable(pj->ctx, pj); |
362 | 0 | return (isInstantiableCached == 1); |
363 | 0 | } |
364 | | //! @endcond |
365 | | |
366 | | /**************************************************************************************/ |
367 | | static void warnAboutMissingGrid(PJ *P) |
368 | | /**************************************************************************************/ |
369 | 0 | { |
370 | 0 | std::string msg("Attempt to use coordinate operation "); |
371 | 0 | msg += proj_get_name(P); |
372 | 0 | msg += " failed."; |
373 | 0 | int gridUsed = proj_coordoperation_get_grid_used_count(P->ctx, P); |
374 | 0 | for (int i = 0; i < gridUsed; ++i) { |
375 | 0 | const char *gridName = ""; |
376 | 0 | int available = FALSE; |
377 | 0 | if (proj_coordoperation_get_grid_used(P->ctx, P, i, &gridName, nullptr, |
378 | 0 | nullptr, nullptr, nullptr, |
379 | 0 | nullptr, &available) && |
380 | 0 | !available) { |
381 | 0 | msg += " Grid "; |
382 | 0 | msg += gridName; |
383 | 0 | msg += " is not available. " |
384 | 0 | "Consult https://proj.org/resource_files.html for guidance."; |
385 | 0 | } |
386 | 0 | } |
387 | 0 | if (!P->errorIfBestTransformationNotAvailable && |
388 | 0 | P->warnIfBestTransformationNotAvailable) { |
389 | 0 | msg += " This might become an error in a future PROJ major release. " |
390 | 0 | "Set the ONLY_BEST option to YES or NO. " |
391 | 0 | "This warning will no longer be emitted (for the current " |
392 | 0 | "transformation instance)."; |
393 | 0 | P->warnIfBestTransformationNotAvailable = false; |
394 | 0 | } |
395 | 0 | pj_log(P->ctx, |
396 | 0 | P->errorIfBestTransformationNotAvailable ? PJ_LOG_ERROR |
397 | 0 | : PJ_LOG_DEBUG, |
398 | 0 | msg.c_str()); |
399 | 0 | } |
400 | | |
401 | | /**************************************************************************************/ |
402 | 0 | PJ_COORD proj_trans(PJ *P, PJ_DIRECTION direction, PJ_COORD coord) { |
403 | | /*************************************************************************************** |
404 | | Apply the transformation P to the coordinate coord, preferring the 4D |
405 | | interfaces if available. |
406 | | |
407 | | See also pj_approx_2D_trans and pj_approx_3D_trans in pj_internal.c, which |
408 | | work similarly, but prefers the 2D resp. 3D interfaces if available. |
409 | | ***************************************************************************************/ |
410 | 0 | if (nullptr == P || direction == PJ_IDENT) |
411 | 0 | return coord; |
412 | 0 | if (P->inverted) |
413 | 0 | direction = opposite_direction(direction); |
414 | |
|
415 | 0 | if (P->iso_obj != nullptr && !P->iso_obj_is_coordinate_operation) { |
416 | 0 | pj_log(P->ctx, PJ_LOG_ERROR, "Object is not a coordinate operation"); |
417 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
418 | 0 | return proj_coord_error(); |
419 | 0 | } |
420 | | |
421 | 0 | if (!P->alternativeCoordinateOperations.empty()) { |
422 | 0 | constexpr int N_MAX_RETRY = 2; |
423 | 0 | int iExcluded[N_MAX_RETRY] = {-1, -1}; |
424 | |
|
425 | 0 | bool skipNonInstantiable = P->skipNonInstantiable && |
426 | 0 | !P->warnIfBestTransformationNotAvailable && |
427 | 0 | !P->errorIfBestTransformationNotAvailable; |
428 | 0 | const int nOperations = |
429 | 0 | static_cast<int>(P->alternativeCoordinateOperations.size()); |
430 | | |
431 | | // We may need several attempts. For example the point at |
432 | | // long=-111.5 lat=45.26 falls into the bounding box of the Canadian |
433 | | // ntv2_0.gsb grid, except that it is not in any of the subgrids, being |
434 | | // in the US. We thus need another retry that will select the conus |
435 | | // grid. |
436 | 0 | for (int iRetry = 0; iRetry <= N_MAX_RETRY; iRetry++) { |
437 | | // Do a first pass and select the operations that match the area of |
438 | | // use and has the best accuracy. |
439 | 0 | int iBest = pj_get_suggested_operation( |
440 | 0 | P->ctx, P->alternativeCoordinateOperations, iExcluded, |
441 | 0 | skipNonInstantiable, direction, coord); |
442 | 0 | if (iBest < 0) { |
443 | 0 | break; |
444 | 0 | } |
445 | 0 | if (iRetry > 0) { |
446 | 0 | const int oldErrno = proj_errno_reset(P); |
447 | 0 | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { |
448 | 0 | pj_log(P->ctx, PJ_LOG_DEBUG, |
449 | 0 | proj_context_errno_string(P->ctx, oldErrno)); |
450 | 0 | } |
451 | 0 | pj_log(P->ctx, PJ_LOG_DEBUG, |
452 | 0 | "Did not result in valid result. " |
453 | 0 | "Attempting a retry with another operation."); |
454 | 0 | } |
455 | |
|
456 | 0 | const auto &alt = P->alternativeCoordinateOperations[iBest]; |
457 | 0 | if (P->iCurCoordOp != iBest) { |
458 | 0 | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= PJ_LOG_DEBUG) { |
459 | 0 | std::string msg("Using coordinate operation "); |
460 | 0 | msg += alt.name; |
461 | 0 | pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str()); |
462 | 0 | } |
463 | 0 | P->iCurCoordOp = iBest; |
464 | 0 | } |
465 | 0 | PJ_COORD res = coord; |
466 | 0 | if (alt.pj->hasCoordinateEpoch) |
467 | 0 | coord.xyzt.t = alt.pj->coordinateEpoch; |
468 | 0 | if (direction == PJ_FWD) |
469 | 0 | pj_fwd4d(res, alt.pj); |
470 | 0 | else |
471 | 0 | pj_inv4d(res, alt.pj); |
472 | 0 | if (proj_errno(alt.pj) == PROJ_ERR_OTHER_NETWORK_ERROR) { |
473 | 0 | return proj_coord_error(); |
474 | 0 | } |
475 | 0 | if (res.xyzt.x != HUGE_VAL) { |
476 | 0 | return res; |
477 | 0 | } else if (P->errorIfBestTransformationNotAvailable || |
478 | 0 | P->warnIfBestTransformationNotAvailable) { |
479 | 0 | warnAboutMissingGrid(alt.pj); |
480 | 0 | if (P->errorIfBestTransformationNotAvailable) { |
481 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION); |
482 | 0 | return res; |
483 | 0 | } |
484 | 0 | P->warnIfBestTransformationNotAvailable = false; |
485 | 0 | skipNonInstantiable = true; |
486 | 0 | } |
487 | 0 | if (iRetry == N_MAX_RETRY) { |
488 | 0 | break; |
489 | 0 | } |
490 | 0 | iExcluded[iRetry] = iBest; |
491 | 0 | } |
492 | | |
493 | | // In case we did not find an operation whose area of use is compatible |
494 | | // with the input coordinate, then goes through again the list, and |
495 | | // use the first operation that does not require grids. |
496 | 0 | NS_PROJ::io::DatabaseContextPtr dbContext; |
497 | 0 | try { |
498 | 0 | if (P->ctx->cpp_context) { |
499 | 0 | dbContext = |
500 | 0 | P->ctx->cpp_context->getDatabaseContext().as_nullable(); |
501 | 0 | } |
502 | 0 | } catch (const std::exception &) { |
503 | 0 | } |
504 | 0 | for (int i = 0; i < nOperations; i++) { |
505 | 0 | const auto &alt = P->alternativeCoordinateOperations[i]; |
506 | 0 | auto coordOperation = |
507 | 0 | dynamic_cast<NS_PROJ::operation::CoordinateOperation *>( |
508 | 0 | alt.pj->iso_obj.get()); |
509 | 0 | if (coordOperation) { |
510 | 0 | if (coordOperation->gridsNeeded(dbContext, true).empty()) { |
511 | 0 | if (P->iCurCoordOp != i) { |
512 | 0 | if (proj_log_level(P->ctx, PJ_LOG_TELL) >= |
513 | 0 | PJ_LOG_DEBUG) { |
514 | 0 | std::string msg("Using coordinate operation "); |
515 | 0 | msg += alt.name; |
516 | 0 | msg += " as a fallback due to lack of more " |
517 | 0 | "appropriate operations"; |
518 | 0 | pj_log(P->ctx, PJ_LOG_DEBUG, msg.c_str()); |
519 | 0 | } |
520 | 0 | P->iCurCoordOp = i; |
521 | 0 | } |
522 | 0 | if (direction == PJ_FWD) { |
523 | 0 | pj_fwd4d(coord, alt.pj); |
524 | 0 | } else { |
525 | 0 | pj_inv4d(coord, alt.pj); |
526 | 0 | } |
527 | 0 | return coord; |
528 | 0 | } |
529 | 0 | } |
530 | 0 | } |
531 | | |
532 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_NO_OPERATION); |
533 | 0 | return proj_coord_error(); |
534 | 0 | } |
535 | | |
536 | 0 | P->iCurCoordOp = |
537 | 0 | 0; // dummy value, to be used by proj_trans_get_last_used_operation() |
538 | 0 | if (P->hasCoordinateEpoch) |
539 | 0 | coord.xyzt.t = P->coordinateEpoch; |
540 | 0 | if (coord_has_nans(coord)) |
541 | 0 | coord.v[0] = coord.v[1] = coord.v[2] = coord.v[3] = |
542 | 0 | std::numeric_limits<double>::quiet_NaN(); |
543 | 0 | else if (direction == PJ_FWD) |
544 | 0 | pj_fwd4d(coord, P); |
545 | 0 | else |
546 | 0 | pj_inv4d(coord, P); |
547 | 0 | return coord; |
548 | 0 | } |
549 | | |
550 | | /*****************************************************************************/ |
551 | | PJ *proj_trans_get_last_used_operation(PJ *P) |
552 | | /****************************************************************************** |
553 | | Return the operation used during the last invocation of proj_trans(). |
554 | | This is especially useful when P has been created with |
555 | | proj_create_crs_to_crs() and has several alternative operations. The returned |
556 | | object must be freed with proj_destroy(). |
557 | | ******************************************************************************/ |
558 | 0 | { |
559 | 0 | if (nullptr == P || P->iCurCoordOp < 0) |
560 | 0 | return nullptr; |
561 | 0 | if (P->alternativeCoordinateOperations.empty()) |
562 | 0 | return proj_clone(P->ctx, P); |
563 | 0 | return proj_clone(P->ctx, |
564 | 0 | P->alternativeCoordinateOperations[P->iCurCoordOp].pj); |
565 | 0 | } |
566 | | |
567 | | /*****************************************************************************/ |
568 | 0 | int proj_trans_array(PJ *P, PJ_DIRECTION direction, size_t n, PJ_COORD *coord) { |
569 | | /****************************************************************************** |
570 | | Batch transform an array of PJ_COORD. |
571 | | |
572 | | Performs transformation on all points, even if errors occur on some |
573 | | points. |
574 | | |
575 | | Individual points that fail to transform will have their components set |
576 | | to HUGE_VAL |
577 | | |
578 | | Returns 0 if all coordinates are transformed without error, otherwise |
579 | | returns a precise error number if all coordinates that fail to transform |
580 | | for the same reason, or a generic error code if they fail for different |
581 | | reasons. |
582 | | ******************************************************************************/ |
583 | 0 | size_t i; |
584 | 0 | int retErrno = 0; |
585 | 0 | bool hasSetRetErrno = false; |
586 | 0 | bool sameRetErrno = true; |
587 | |
|
588 | 0 | for (i = 0; i < n; i++) { |
589 | 0 | proj_context_errno_set(P->ctx, 0); |
590 | 0 | coord[i] = proj_trans(P, direction, coord[i]); |
591 | 0 | int thisErrno = proj_errno(P); |
592 | 0 | if (thisErrno != 0) { |
593 | 0 | if (!hasSetRetErrno) { |
594 | 0 | retErrno = thisErrno; |
595 | 0 | hasSetRetErrno = true; |
596 | 0 | } else if (sameRetErrno && retErrno != thisErrno) { |
597 | 0 | sameRetErrno = false; |
598 | 0 | retErrno = PROJ_ERR_COORD_TRANSFM; |
599 | 0 | } |
600 | 0 | } |
601 | 0 | } |
602 | |
|
603 | 0 | proj_context_errno_set(P->ctx, retErrno); |
604 | |
|
605 | 0 | return retErrno; |
606 | 0 | } |
607 | | |
608 | | /*************************************************************************************/ |
609 | | size_t proj_trans_generic(PJ *P, PJ_DIRECTION direction, double *x, size_t sx, |
610 | | size_t nx, double *y, size_t sy, size_t ny, double *z, |
611 | | size_t sz, size_t nz, double *t, size_t st, |
612 | 0 | size_t nt) { |
613 | | /************************************************************************************** |
614 | | |
615 | | Transform a series of coordinates, where the individual coordinate |
616 | | dimension may be represented by an array that is either |
617 | | |
618 | | 1. fully populated |
619 | | 2. a null pointer and/or a length of zero, which will be treated as |
620 | | a fully populated array of zeroes |
621 | | 3. of length one, i.e. a constant, which will be treated as a fully |
622 | | populated array of that constant value |
623 | | |
624 | | The strides, sx, sy, sz, st, represent the step length, in bytes, |
625 | | between consecutive elements of the corresponding array. This makes it |
626 | | possible for proj_transform to handle transformation of a large class of |
627 | | application specific data structures, without necessarily understanding the |
628 | | data structure format, as in: |
629 | | |
630 | | typedef struct {double x, y; int quality_level; char |
631 | | surveyor_name[134];} XYQS; XYQS survey[345]; double height = 23.45; PJ *P = |
632 | | {...}; size_t stride = sizeof (XYQS); |
633 | | ... |
634 | | proj_transform ( |
635 | | P, PJ_INV, sizeof(XYQS), |
636 | | &(survey[0].x), stride, 345, (* We have 345 eastings *) |
637 | | &(survey[0].y), stride, 345, (* ...and 345 northings. *) |
638 | | &height, 1, (* The height is the |
639 | | constant 23.45 m *) 0, 0 (* and the time is the |
640 | | constant 0.00 s *) |
641 | | ); |
642 | | |
643 | | This is similar to the inner workings of the pj_transform function, but |
644 | | the stride functionality has been generalized to work for any size of basic |
645 | | unit, not just a fixed number of doubles. |
646 | | |
647 | | In most cases, the stride will be identical for x, y,z, and t, since |
648 | | they will typically be either individual arrays (stride = sizeof(double)), |
649 | | or strided views into an array of application specific data structures |
650 | | (stride = sizeof (...)). |
651 | | |
652 | | But in order to support cases where x, y, z, and t come from |
653 | | heterogeneous sources, individual strides, sx, sy, sz, st, are used. |
654 | | |
655 | | Caveat: Since proj_transform does its work *in place*, this means that |
656 | | even the supposedly constants (i.e. length 1 arrays) will return from the |
657 | | call in altered state. Hence, remember to reinitialize between repeated |
658 | | calls. |
659 | | |
660 | | Return value: Number of transformations completed. |
661 | | |
662 | | **************************************************************************************/ |
663 | 0 | PJ_COORD coord = {{0, 0, 0, 0}}; |
664 | 0 | size_t i, nmin; |
665 | 0 | double null_broadcast = 0; |
666 | 0 | double invalid_time = HUGE_VAL; |
667 | |
|
668 | 0 | if (nullptr == P) |
669 | 0 | return 0; |
670 | | |
671 | 0 | if (P->inverted) |
672 | 0 | direction = opposite_direction(direction); |
673 | | |
674 | | /* ignore lengths of null arrays */ |
675 | 0 | if (nullptr == x) |
676 | 0 | nx = 0; |
677 | 0 | if (nullptr == y) |
678 | 0 | ny = 0; |
679 | 0 | if (nullptr == z) |
680 | 0 | nz = 0; |
681 | 0 | if (nullptr == t) |
682 | 0 | nt = 0; |
683 | | |
684 | | /* and make the nullities point to some real world memory for broadcasting |
685 | | * nulls */ |
686 | 0 | if (0 == nx) |
687 | 0 | x = &null_broadcast; |
688 | 0 | if (0 == ny) |
689 | 0 | y = &null_broadcast; |
690 | 0 | if (0 == nz) |
691 | 0 | z = &null_broadcast; |
692 | 0 | if (0 == nt) |
693 | 0 | t = &invalid_time; |
694 | | |
695 | | /* nothing to do? */ |
696 | 0 | if (0 == nx + ny + nz + nt) |
697 | 0 | return 0; |
698 | | |
699 | | /* arrays of length 1 are constants, which we broadcast along the longer |
700 | | * arrays */ |
701 | | /* so we need to find the length of the shortest non-unity array to figure |
702 | | * out |
703 | | */ |
704 | | /* how many coordinate pairs we must transform */ |
705 | 0 | nmin = (nx > 1) ? nx : (ny > 1) ? ny : (nz > 1) ? nz : (nt > 1) ? nt : 1; |
706 | 0 | if ((nx > 1) && (nx < nmin)) |
707 | 0 | nmin = nx; |
708 | 0 | if ((ny > 1) && (ny < nmin)) |
709 | 0 | nmin = ny; |
710 | 0 | if ((nz > 1) && (nz < nmin)) |
711 | 0 | nmin = nz; |
712 | 0 | if ((nt > 1) && (nt < nmin)) |
713 | 0 | nmin = nt; |
714 | | |
715 | | /* Check validity of direction flag */ |
716 | 0 | switch (direction) { |
717 | 0 | case PJ_FWD: |
718 | 0 | case PJ_INV: |
719 | 0 | break; |
720 | 0 | case PJ_IDENT: |
721 | 0 | return nmin; |
722 | 0 | } |
723 | | |
724 | | /* Arrays of length==0 are broadcast as the constant 0 */ |
725 | | /* Arrays of length==1 are broadcast as their single value */ |
726 | | /* Arrays of length >1 are iterated over (for the first nmin values) */ |
727 | | /* The slightly convolved incremental indexing is used due */ |
728 | | /* to the stride, which may be any size supported by the platform */ |
729 | 0 | for (i = 0; i < nmin; i++) { |
730 | 0 | coord.xyzt.x = *x; |
731 | 0 | coord.xyzt.y = *y; |
732 | 0 | coord.xyzt.z = *z; |
733 | 0 | coord.xyzt.t = *t; |
734 | |
|
735 | 0 | coord = proj_trans(P, direction, coord); |
736 | | |
737 | | /* in all full length cases, we overwrite the input with the output, */ |
738 | | /* and step on to the next element. */ |
739 | | /* The casts are somewhat funky, but they compile down to no-ops and */ |
740 | | /* they tell compilers and static analyzers that we know what we do */ |
741 | 0 | if (nx > 1) { |
742 | 0 | *x = coord.xyzt.x; |
743 | 0 | x = (double *)((void *)(((char *)x) + sx)); |
744 | 0 | } |
745 | 0 | if (ny > 1) { |
746 | 0 | *y = coord.xyzt.y; |
747 | 0 | y = (double *)((void *)(((char *)y) + sy)); |
748 | 0 | } |
749 | 0 | if (nz > 1) { |
750 | 0 | *z = coord.xyzt.z; |
751 | 0 | z = (double *)((void *)(((char *)z) + sz)); |
752 | 0 | } |
753 | 0 | if (nt > 1) { |
754 | 0 | *t = coord.xyzt.t; |
755 | 0 | t = (double *)((void *)(((char *)t) + st)); |
756 | 0 | } |
757 | 0 | } |
758 | | |
759 | | /* Last time around, we update the length 1 cases with their transformed |
760 | | * alter egos */ |
761 | 0 | if (nx == 1) |
762 | 0 | *x = coord.xyzt.x; |
763 | 0 | if (ny == 1) |
764 | 0 | *y = coord.xyzt.y; |
765 | 0 | if (nz == 1) |
766 | 0 | *z = coord.xyzt.z; |
767 | 0 | if (nt == 1) |
768 | 0 | *t = coord.xyzt.t; |
769 | |
|
770 | 0 | return i; |
771 | 0 | } |
772 | | |
773 | | /*************************************************************************************/ |
774 | | PJ_COORD pj_geocentric_latitude(const PJ *P, PJ_DIRECTION direction, |
775 | 0 | PJ_COORD coord) { |
776 | | /************************************************************************************** |
777 | | Convert geographical latitude to geocentric (or the other way round if |
778 | | direction = PJ_INV) |
779 | | |
780 | | The conversion involves a call to the tangent function, which goes |
781 | | through the roof at the poles, so very close (the last centimeter) to the |
782 | | poles no conversion takes place and the input latitude is copied directly to |
783 | | the output. |
784 | | |
785 | | Fortunately, the geocentric latitude converges to the geographical at |
786 | | the poles, so the difference is negligible. |
787 | | |
788 | | For the spherical case, the geographical latitude equals the geocentric, |
789 | | and consequently, the input is copied directly to the output. |
790 | | **************************************************************************************/ |
791 | 0 | const double limit = M_HALFPI - 1e-9; |
792 | 0 | PJ_COORD res = coord; |
793 | 0 | if ((coord.lp.phi > limit) || (coord.lp.phi < -limit) || (P->es == 0)) |
794 | 0 | return res; |
795 | 0 | if (direction == PJ_FWD) |
796 | 0 | res.lp.phi = atan(P->one_es * tan(coord.lp.phi)); |
797 | 0 | else |
798 | 0 | res.lp.phi = atan(P->rone_es * tan(coord.lp.phi)); |
799 | |
|
800 | 0 | return res; |
801 | 0 | } |
802 | | |
803 | 0 | double proj_torad(double angle_in_degrees) { |
804 | 0 | return PJ_TORAD(angle_in_degrees); |
805 | 0 | } |
806 | 0 | double proj_todeg(double angle_in_radians) { |
807 | 0 | return PJ_TODEG(angle_in_radians); |
808 | 0 | } |
809 | | |
810 | 732 | double proj_dmstor(const char *is, char **rs) { return dmstor(is, rs); } |
811 | | |
812 | 0 | char *proj_rtodms(char *s, double r, int pos, int neg) { |
813 | | // 40 is the size used for the buffer in proj.cpp |
814 | 0 | size_t arbitrary_size = 40; |
815 | 0 | return rtodms(s, arbitrary_size, r, pos, neg); |
816 | 0 | } |
817 | | |
818 | 0 | char *proj_rtodms2(char *s, size_t sizeof_s, double r, int pos, int neg) { |
819 | 0 | return rtodms(s, sizeof_s, r, pos, neg); |
820 | 0 | } |
821 | | |
822 | | /*************************************************************************************/ |
823 | 73.8k | static PJ *skip_prep_fin(PJ *P) { |
824 | | /************************************************************************************** |
825 | | Skip prepare and finalize function for the various "helper operations" added |
826 | | to P when in cs2cs compatibility mode. |
827 | | **************************************************************************************/ |
828 | 73.8k | P->skip_fwd_prepare = 1; |
829 | 73.8k | P->skip_fwd_finalize = 1; |
830 | 73.8k | P->skip_inv_prepare = 1; |
831 | 73.8k | P->skip_inv_finalize = 1; |
832 | 73.8k | return P; |
833 | 73.8k | } |
834 | | |
835 | | /*************************************************************************************/ |
836 | 183k | static int cs2cs_emulation_setup(PJ *P) { |
837 | | /************************************************************************************** |
838 | | If any cs2cs style modifiers are given (axis=..., towgs84=..., ) create the |
839 | | 4D API equivalent operations, so the preparation and finalization steps in |
840 | | the pj_inv/pj_fwd invocators can emulate the behavior of pj_transform and |
841 | | the cs2cs app. |
842 | | |
843 | | Returns 1 on success, 0 on failure |
844 | | **************************************************************************************/ |
845 | 183k | PJ *Q; |
846 | 183k | paralist *p; |
847 | 183k | int do_cart = 0; |
848 | 183k | if (nullptr == P) |
849 | 17.5k | return 0; |
850 | | |
851 | | /* Don't recurse when calling proj_create (which calls us back) */ |
852 | 165k | if (pj_param_exists(P->params, "break_cs2cs_recursion")) |
853 | 73.8k | return 1; |
854 | | |
855 | | /* Swap axes? */ |
856 | 92.1k | p = pj_param_exists(P->params, "axis"); |
857 | | |
858 | 92.1k | const bool disable_grid_presence_check = |
859 | 92.1k | pj_param_exists(P->params, "disable_grid_presence_check") != nullptr; |
860 | | |
861 | | /* Don't axisswap if data are already in "enu" order */ |
862 | 92.1k | if (p && (0 != strcmp("enu", p->param))) { |
863 | 1.48k | size_t def_size = 100 + strlen(P->axis); |
864 | 1.48k | char *def = static_cast<char *>(malloc(def_size)); |
865 | 1.48k | if (nullptr == def) |
866 | 0 | return 0; |
867 | 1.48k | snprintf(def, def_size, |
868 | 1.48k | "break_cs2cs_recursion proj=axisswap axis=%s", P->axis); |
869 | 1.48k | Q = pj_create_internal(P->ctx, def); |
870 | 1.48k | free(def); |
871 | 1.48k | if (nullptr == Q) |
872 | 5 | return 0; |
873 | 1.48k | P->axisswap = skip_prep_fin(Q); |
874 | 1.48k | } |
875 | | |
876 | | /* Geoid grid(s) given? */ |
877 | 92.1k | p = pj_param_exists(P->params, "geoidgrids"); |
878 | 92.1k | if (!disable_grid_presence_check && p && |
879 | 92.1k | strlen(p->param) > strlen("geoidgrids=")) { |
880 | 935 | char *gridnames = p->param + strlen("geoidgrids="); |
881 | 935 | size_t def_size = 100 + 2 * strlen(gridnames); |
882 | 935 | char *def = static_cast<char *>(malloc(def_size)); |
883 | 935 | if (nullptr == def) |
884 | 0 | return 0; |
885 | 935 | snprintf(def, def_size, |
886 | 935 | "break_cs2cs_recursion proj=vgridshift grids=%s", |
887 | 935 | pj_double_quote_string_param_if_needed(gridnames).c_str()); |
888 | 935 | Q = pj_create_internal(P->ctx, def); |
889 | 935 | free(def); |
890 | 935 | if (nullptr == Q) |
891 | 590 | return 0; |
892 | 345 | P->vgridshift = skip_prep_fin(Q); |
893 | 345 | } |
894 | | |
895 | | /* Datum shift grid(s) given? */ |
896 | 91.5k | p = pj_param_exists(P->params, "nadgrids"); |
897 | 91.5k | if (!disable_grid_presence_check && p && |
898 | 91.5k | strlen(p->param) > strlen("nadgrids=")) { |
899 | 2.37k | char *gridnames = p->param + strlen("nadgrids="); |
900 | 2.37k | size_t def_size = 100 + 2 * strlen(gridnames); |
901 | 2.37k | char *def = static_cast<char *>(malloc(def_size)); |
902 | 2.37k | if (nullptr == def) |
903 | 0 | return 0; |
904 | 2.37k | snprintf(def, def_size, |
905 | 2.37k | "break_cs2cs_recursion proj=hgridshift grids=%s", |
906 | 2.37k | pj_double_quote_string_param_if_needed(gridnames).c_str()); |
907 | 2.37k | Q = pj_create_internal(P->ctx, def); |
908 | 2.37k | free(def); |
909 | 2.37k | if (nullptr == Q) |
910 | 415 | return 0; |
911 | 1.95k | P->hgridshift = skip_prep_fin(Q); |
912 | 1.95k | } |
913 | | |
914 | | /* We ignore helmert if we have grid shift */ |
915 | 91.1k | p = P->hgridshift ? nullptr : pj_param_exists(P->params, "towgs84"); |
916 | 91.1k | while (p) { |
917 | 25.9k | const char *const s = p->param; |
918 | 25.9k | const double *const d = P->datum_params; |
919 | | |
920 | | /* We ignore null helmert shifts (common in auto-translated resource |
921 | | * files, e.g. epsg) */ |
922 | 25.9k | if (0 == d[0] && 0 == d[1] && 0 == d[2] && 0 == d[3] && 0 == d[4] && |
923 | 25.9k | 0 == d[5] && 0 == d[6]) { |
924 | | /* If the current ellipsoid is not WGS84, then make sure the */ |
925 | | /* change in ellipsoid is still done. */ |
926 | 5.37k | if (!(fabs(P->a_orig - 6378137.0) < 1e-8 && |
927 | 5.37k | fabs(P->es_orig - 0.0066943799901413) < 1e-15)) { |
928 | 4.10k | do_cart = 1; |
929 | 4.10k | } |
930 | 5.37k | break; |
931 | 5.37k | } |
932 | | |
933 | 20.5k | const size_t n = strlen(s); |
934 | 20.5k | if (n <= 8) /* 8==strlen ("towgs84=") */ |
935 | 0 | return 0; |
936 | | |
937 | 20.5k | const size_t def_max_size = 100 + n; |
938 | 20.5k | std::string def; |
939 | 20.5k | def.reserve(def_max_size); |
940 | 20.5k | def += "break_cs2cs_recursion proj=helmert exact "; |
941 | 20.5k | def += s; |
942 | 20.5k | def += " convention=position_vector"; |
943 | 20.5k | Q = pj_create_internal(P->ctx, def.c_str()); |
944 | 20.5k | if (nullptr == Q) |
945 | 4 | return 0; |
946 | 20.5k | pj_inherit_ellipsoid_def(P, Q); |
947 | 20.5k | P->helmert = skip_prep_fin(Q); |
948 | | |
949 | 20.5k | break; |
950 | 20.5k | } |
951 | | |
952 | | /* We also need cartesian/geographical transformations if we are working in |
953 | | */ |
954 | | /* geocentric/cartesian space or we need to do a Helmert transform. */ |
955 | 91.1k | if (P->is_geocent || P->helmert || do_cart) { |
956 | 24.8k | char def[150]; |
957 | 24.8k | snprintf(def, sizeof(def), |
958 | 24.8k | "break_cs2cs_recursion proj=cart a=%40.20g es=%40.20g", |
959 | 24.8k | P->a_orig, P->es_orig); |
960 | 24.8k | { |
961 | | /* In case the current locale does not use dot but comma as decimal |
962 | | */ |
963 | | /* separator, replace it with dot, so that proj_atof() behaves */ |
964 | | /* correctly. */ |
965 | | /* TODO later: use C++ ostringstream with |
966 | | * imbue(std::locale::classic()) */ |
967 | | /* to be locale unaware */ |
968 | 24.8k | char *next_pos; |
969 | 24.8k | for (next_pos = def; (next_pos = strchr(next_pos, ',')) != nullptr; |
970 | 24.8k | next_pos++) { |
971 | 0 | *next_pos = '.'; |
972 | 0 | } |
973 | 24.8k | } |
974 | 24.8k | Q = pj_create_internal(P->ctx, def); |
975 | 24.8k | if (nullptr == Q) |
976 | 0 | return 0; |
977 | 24.8k | P->cart = skip_prep_fin(Q); |
978 | | |
979 | 24.8k | if (!P->is_geocent) { |
980 | 24.6k | snprintf(def, sizeof(def), |
981 | 24.6k | "break_cs2cs_recursion proj=cart ellps=WGS84"); |
982 | 24.6k | Q = pj_create_internal(P->ctx, def); |
983 | 24.6k | if (nullptr == Q) |
984 | 0 | return 0; |
985 | 24.6k | P->cart_wgs84 = skip_prep_fin(Q); |
986 | 24.6k | } |
987 | 24.8k | } |
988 | | |
989 | 91.1k | return 1; |
990 | 91.1k | } |
991 | | |
992 | | /*************************************************************************************/ |
993 | 120k | PJ *pj_create_internal(PJ_CONTEXT *ctx, const char *definition) { |
994 | | /*************************************************************************************/ |
995 | | |
996 | | /************************************************************************************** |
997 | | Create a new PJ object in the context ctx, using the given definition. |
998 | | If ctx==0, the default context is used, if definition==0, or invalid, a |
999 | | null-pointer is returned. The definition may use '+' as argument start |
1000 | | indicator, as in |
1001 | | "+proj=utm +zone=32", or leave it out, as in "proj=utm zone=32". |
1002 | | |
1003 | | It may even use free formatting "proj = utm; zone =32 ellps= |
1004 | | GRS80". Note that the semicolon separator is allowed, but not required. |
1005 | | **************************************************************************************/ |
1006 | 120k | char *args, **argv; |
1007 | 120k | size_t argc, n; |
1008 | | |
1009 | 120k | if (nullptr == ctx) |
1010 | 0 | ctx = pj_get_default_ctx(); |
1011 | | |
1012 | | /* Make a copy that we can manipulate */ |
1013 | 120k | n = strlen(definition); |
1014 | 120k | args = (char *)malloc(n + 1); |
1015 | 120k | if (nullptr == args) { |
1016 | 0 | proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); |
1017 | 0 | return nullptr; |
1018 | 0 | } |
1019 | 120k | strcpy(args, definition); |
1020 | | |
1021 | 120k | argc = pj_trim_argc(args); |
1022 | 120k | if (argc == 0) { |
1023 | 11 | free(args); |
1024 | 11 | proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); |
1025 | 11 | return nullptr; |
1026 | 11 | } |
1027 | | |
1028 | 120k | argv = pj_trim_argv(argc, args); |
1029 | 120k | if (!argv) { |
1030 | 0 | free(args); |
1031 | 0 | proj_context_errno_set(ctx, PROJ_ERR_OTHER /*ENOMEM*/); |
1032 | 0 | return nullptr; |
1033 | 0 | } |
1034 | | |
1035 | 120k | PJ *P = pj_create_argv_internal(ctx, (int)argc, argv); |
1036 | | |
1037 | 120k | free(argv); |
1038 | 120k | free(args); |
1039 | | |
1040 | 120k | return P; |
1041 | 120k | } |
1042 | | |
1043 | | /*************************************************************************************/ |
1044 | 0 | PJ *proj_create_argv(PJ_CONTEXT *ctx, int argc, char **argv) { |
1045 | | /************************************************************************************** |
1046 | | Create a new PJ object in the context ctx, using the given definition |
1047 | | argument array argv. If ctx==0, the default context is used, if |
1048 | | definition==0, or invalid, a null-pointer is returned. The definition |
1049 | | arguments may use '+' as argument start indicator, as in {"+proj=utm", |
1050 | | "+zone=32"}, or leave it out, as in {"proj=utm", "zone=32"}. |
1051 | | **************************************************************************************/ |
1052 | |
|
1053 | 0 | if (nullptr == ctx) |
1054 | 0 | ctx = pj_get_default_ctx(); |
1055 | 0 | if (nullptr == argv) { |
1056 | 0 | proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); |
1057 | 0 | return nullptr; |
1058 | 0 | } |
1059 | | |
1060 | | /* We assume that free format is used, and build a full proj_create |
1061 | | * compatible string */ |
1062 | 0 | char *c = pj_make_args(argc, argv); |
1063 | 0 | if (nullptr == c) { |
1064 | 0 | proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP /* ENOMEM */); |
1065 | 0 | return nullptr; |
1066 | 0 | } |
1067 | | |
1068 | 0 | PJ *P = proj_create(ctx, c); |
1069 | |
|
1070 | 0 | free((char *)c); |
1071 | 0 | return P; |
1072 | 0 | } |
1073 | | |
1074 | | /*************************************************************************************/ |
1075 | 183k | PJ *pj_create_argv_internal(PJ_CONTEXT *ctx, int argc, char **argv) { |
1076 | | /************************************************************************************** |
1077 | | For use by pipeline init function. |
1078 | | **************************************************************************************/ |
1079 | 183k | if (nullptr == ctx) |
1080 | 0 | ctx = pj_get_default_ctx(); |
1081 | 183k | if (nullptr == argv) { |
1082 | 0 | proj_context_errno_set(ctx, PROJ_ERR_INVALID_OP_MISSING_ARG); |
1083 | 0 | return nullptr; |
1084 | 0 | } |
1085 | | |
1086 | | /* ...and let pj_init_ctx do the hard work */ |
1087 | | /* New interface: forbid init=epsg:XXXX syntax by default */ |
1088 | 183k | const int allow_init_epsg = |
1089 | 183k | proj_context_get_use_proj4_init_rules(ctx, FALSE); |
1090 | 183k | PJ *P = pj_init_ctx_with_allow_init_epsg(ctx, argc, argv, allow_init_epsg); |
1091 | | |
1092 | | /* Support cs2cs-style modifiers */ |
1093 | 183k | int ret = cs2cs_emulation_setup(P); |
1094 | 183k | if (0 == ret) |
1095 | 18.5k | return proj_destroy(P); |
1096 | | |
1097 | 164k | return P; |
1098 | 183k | } |
1099 | | |
1100 | | /** Create an area of use */ |
1101 | 0 | PJ_AREA *proj_area_create(void) { return new PJ_AREA(); } |
1102 | | |
1103 | | /** Assign a bounding box to an area of use. */ |
1104 | | void proj_area_set_bbox(PJ_AREA *area, double west_lon_degree, |
1105 | | double south_lat_degree, double east_lon_degree, |
1106 | 0 | double north_lat_degree) { |
1107 | 0 | area->bbox_set = TRUE; |
1108 | 0 | area->west_lon_degree = west_lon_degree; |
1109 | 0 | area->south_lat_degree = south_lat_degree; |
1110 | 0 | area->east_lon_degree = east_lon_degree; |
1111 | 0 | area->north_lat_degree = north_lat_degree; |
1112 | 0 | } |
1113 | | |
1114 | | /** Assign the name of an area of use. */ |
1115 | 0 | void proj_area_set_name(PJ_AREA *area, const char *name) { area->name = name; } |
1116 | | |
1117 | | /** Free an area of use */ |
1118 | 0 | void proj_area_destroy(PJ_AREA *area) { delete area; } |
1119 | | |
1120 | | /************************************************************************/ |
1121 | | /* proj_context_use_proj4_init_rules() */ |
1122 | | /************************************************************************/ |
1123 | | |
1124 | 550 | void proj_context_use_proj4_init_rules(PJ_CONTEXT *ctx, int enable) { |
1125 | 550 | if (ctx == nullptr) { |
1126 | 0 | ctx = pj_get_default_ctx(); |
1127 | 0 | } |
1128 | 550 | ctx->use_proj4_init_rules = enable; |
1129 | 550 | } |
1130 | | |
1131 | | /************************************************************************/ |
1132 | | /* EQUAL() */ |
1133 | | /************************************************************************/ |
1134 | | |
1135 | 0 | static int EQUAL(const char *a, const char *b) { |
1136 | | #ifdef _MSC_VER |
1137 | | return _stricmp(a, b) == 0; |
1138 | | #else |
1139 | 0 | return strcasecmp(a, b) == 0; |
1140 | 0 | #endif |
1141 | 0 | } |
1142 | | |
1143 | | /************************************************************************/ |
1144 | | /* proj_context_get_use_proj4_init_rules() */ |
1145 | | /************************************************************************/ |
1146 | | |
1147 | | int proj_context_get_use_proj4_init_rules(PJ_CONTEXT *ctx, |
1148 | 217k | int from_legacy_code_path) { |
1149 | 217k | const char *val = getenv("PROJ_USE_PROJ4_INIT_RULES"); |
1150 | | |
1151 | 217k | if (ctx == nullptr) { |
1152 | 0 | ctx = pj_get_default_ctx(); |
1153 | 0 | } |
1154 | | |
1155 | 217k | if (val) { |
1156 | 0 | if (EQUAL(val, "yes") || EQUAL(val, "on") || EQUAL(val, "true")) { |
1157 | 0 | return TRUE; |
1158 | 0 | } |
1159 | 0 | if (EQUAL(val, "no") || EQUAL(val, "off") || EQUAL(val, "false")) { |
1160 | 0 | return FALSE; |
1161 | 0 | } |
1162 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
1163 | 0 | "Invalid value for PROJ_USE_PROJ4_INIT_RULES"); |
1164 | 0 | } |
1165 | | |
1166 | 217k | if (ctx->use_proj4_init_rules >= 0) { |
1167 | 1.39k | return ctx->use_proj4_init_rules; |
1168 | 1.39k | } |
1169 | 215k | return from_legacy_code_path; |
1170 | 217k | } |
1171 | | |
1172 | | /** Adds a " +type=crs" suffix to a PROJ string (if it is a PROJ string) */ |
1173 | 57.8k | std::string pj_add_type_crs_if_needed(const std::string &str) { |
1174 | 57.8k | std::string ret(str); |
1175 | 57.8k | if ((starts_with(str, "proj=") || starts_with(str, "+proj=") || |
1176 | 57.8k | starts_with(str, "+init=") || starts_with(str, "+title=")) && |
1177 | 57.8k | str.find("type=crs") == std::string::npos) { |
1178 | 21.1k | ret += " +type=crs"; |
1179 | 21.1k | } |
1180 | 57.8k | return ret; |
1181 | 57.8k | } |
1182 | | |
1183 | | // --------------------------------------------------------------------------- |
1184 | 0 | static double simple_min(const double *data, const int arr_len) { |
1185 | 0 | double min_value = data[0]; |
1186 | 0 | for (int iii = 1; iii < arr_len; iii++) { |
1187 | 0 | if (data[iii] < min_value) |
1188 | 0 | min_value = data[iii]; |
1189 | 0 | } |
1190 | 0 | return min_value; |
1191 | 0 | } |
1192 | | |
1193 | | // --------------------------------------------------------------------------- |
1194 | 0 | static double simple_max(const double *data, const int arr_len) { |
1195 | 0 | double max_value = data[0]; |
1196 | 0 | for (int iii = 1; iii < arr_len; iii++) { |
1197 | 0 | if ((data[iii] > max_value || max_value == HUGE_VAL) && |
1198 | 0 | data[iii] != HUGE_VAL) |
1199 | 0 | max_value = data[iii]; |
1200 | 0 | } |
1201 | 0 | return max_value; |
1202 | 0 | } |
1203 | | |
1204 | | // --------------------------------------------------------------------------- |
1205 | | static int find_previous_index(const int iii, const double *data, |
1206 | 0 | const int arr_len) { |
1207 | | // find index of nearest valid previous value if exists |
1208 | 0 | int prev_iii = iii - 1; |
1209 | 0 | if (prev_iii == -1) // handle wraparound |
1210 | 0 | prev_iii = arr_len - 1; |
1211 | 0 | while (data[prev_iii] == HUGE_VAL && prev_iii != iii) { |
1212 | 0 | prev_iii--; |
1213 | 0 | if (prev_iii == -1) // handle wraparound |
1214 | 0 | prev_iii = arr_len - 1; |
1215 | 0 | } |
1216 | 0 | return prev_iii; |
1217 | 0 | } |
1218 | | |
1219 | | // --------------------------------------------------------------------------- |
1220 | | /****************************************************************************** |
1221 | | Handles the case when longitude values cross the antimeridian |
1222 | | when calculating the minimum. |
1223 | | Note: The data array must be in a linear ring. |
1224 | | Note: This requires a densified ring with at least 2 additional |
1225 | | points per edge to correctly handle global extents. |
1226 | | If only 1 additional point: |
1227 | | | | |
1228 | | |RL--x0--|RL-- |
1229 | | | | |
1230 | | -180 180|-180 |
1231 | | If they are evenly spaced and it crosses the antimeridian: |
1232 | | x0 - L = 180 |
1233 | | R - x0 = -180 |
1234 | | For example: |
1235 | | Let R = -179.9, x0 = 0.1, L = -179.89 |
1236 | | x0 - L = 0.1 - -179.9 = 180 |
1237 | | R - x0 = -179.89 - 0.1 ~= -180 |
1238 | | This is the same in the case when it didn't cross the antimeridian. |
1239 | | If you have 2 additional points: |
1240 | | | | |
1241 | | |RL--x0--x1--|RL-- |
1242 | | | | |
1243 | | -180 180|-180 |
1244 | | If they are evenly spaced and it crosses the antimeridian: |
1245 | | x0 - L = 120 |
1246 | | x1 - x0 = 120 |
1247 | | R - x1 = -240 |
1248 | | For example: |
1249 | | Let R = -179.9, x0 = -59.9, x1 = 60.1 L = -179.89 |
1250 | | x0 - L = 59.9 - -179.9 = 120 |
1251 | | x1 - x0 = 60.1 - 59.9 = 120 |
1252 | | R - x1 = -179.89 - 60.1 ~= -240 |
1253 | | However, if they are evenly spaced and it didn't cross the antimeridian: |
1254 | | x0 - L = 120 |
1255 | | x1 - x0 = 120 |
1256 | | R - x1 = 120 |
1257 | | From this, we have a delta that is guaranteed to be significantly |
1258 | | large enough to tell the difference reguarless of the direction |
1259 | | the antimeridian was crossed. |
1260 | | However, even though the spacing was even in the source projection, it isn't |
1261 | | guaranteed in the target geographic projection. So, instead of 240, 200 is used |
1262 | | as it significantly larger than 120 to be sure that the antimeridian was crossed |
1263 | | but smalller than 240 to account for possible irregularities in distances |
1264 | | when re-projecting. Also, 200 ensures latitudes are ignored for axis order |
1265 | | handling. |
1266 | | ******************************************************************************/ |
1267 | 0 | static double antimeridian_min(const double *data, const int arr_len) { |
1268 | 0 | double positive_min = HUGE_VAL; |
1269 | 0 | double min_value = HUGE_VAL; |
1270 | 0 | int crossed_meridian_count = 0; |
1271 | 0 | bool positive_meridian = false; |
1272 | |
|
1273 | 0 | for (int iii = 0; iii < arr_len; iii++) { |
1274 | 0 | if (data[iii] == HUGE_VAL) |
1275 | 0 | continue; |
1276 | 0 | int prev_iii = find_previous_index(iii, data, arr_len); |
1277 | | // check if crossed meridian |
1278 | 0 | double delta = data[prev_iii] - data[iii]; |
1279 | | // 180 -> -180 |
1280 | 0 | if (delta >= 200 && delta != HUGE_VAL) { |
1281 | 0 | if (crossed_meridian_count == 0) |
1282 | 0 | positive_min = min_value; |
1283 | 0 | crossed_meridian_count++; |
1284 | 0 | positive_meridian = false; |
1285 | | // -180 -> 180 |
1286 | 0 | } else if (delta <= -200 && delta != HUGE_VAL) { |
1287 | 0 | if (crossed_meridian_count == 0) |
1288 | 0 | positive_min = data[iii]; |
1289 | 0 | crossed_meridian_count++; |
1290 | 0 | positive_meridian = true; |
1291 | 0 | } |
1292 | | // positive meridian side min |
1293 | 0 | if (positive_meridian && data[iii] < positive_min) |
1294 | 0 | positive_min = data[iii]; |
1295 | | // track general min value |
1296 | 0 | if (data[iii] < min_value) |
1297 | 0 | min_value = data[iii]; |
1298 | 0 | } |
1299 | |
|
1300 | 0 | if (crossed_meridian_count == 2) |
1301 | 0 | return positive_min; |
1302 | 0 | else if (crossed_meridian_count == 4) |
1303 | | // bounds extends beyond -180/180 |
1304 | 0 | return -180; |
1305 | 0 | return min_value; |
1306 | 0 | } |
1307 | | |
1308 | | // --------------------------------------------------------------------------- |
1309 | | // Handles the case when longitude values cross the antimeridian |
1310 | | // when calculating the minimum. |
1311 | | // Note: The data array must be in a linear ring. |
1312 | | // Note: This requires a densified ring with at least 2 additional |
1313 | | // points per edge to correctly handle global extents. |
1314 | | // See antimeridian_min docstring for reasoning. |
1315 | 0 | static double antimeridian_max(const double *data, const int arr_len) { |
1316 | 0 | double negative_max = -HUGE_VAL; |
1317 | 0 | double max_value = -HUGE_VAL; |
1318 | 0 | bool negative_meridian = false; |
1319 | 0 | int crossed_meridian_count = 0; |
1320 | |
|
1321 | 0 | for (int iii = 0; iii < arr_len; iii++) { |
1322 | 0 | if (data[iii] == HUGE_VAL) |
1323 | 0 | continue; |
1324 | 0 | int prev_iii = find_previous_index(iii, data, arr_len); |
1325 | | // check if crossed meridian |
1326 | 0 | double delta = data[prev_iii] - data[iii]; |
1327 | | // 180 -> -180 |
1328 | 0 | if (delta >= 200 && delta != HUGE_VAL) { |
1329 | 0 | if (crossed_meridian_count == 0) |
1330 | 0 | negative_max = data[iii]; |
1331 | 0 | crossed_meridian_count++; |
1332 | 0 | negative_meridian = true; |
1333 | | // -180 -> 180 |
1334 | 0 | } else if (delta <= -200 && delta != HUGE_VAL) { |
1335 | 0 | if (crossed_meridian_count == 0) |
1336 | 0 | negative_max = max_value; |
1337 | 0 | negative_meridian = false; |
1338 | 0 | crossed_meridian_count++; |
1339 | 0 | } |
1340 | | // negative meridian side max |
1341 | 0 | if (negative_meridian && |
1342 | 0 | (data[iii] > negative_max || negative_max == HUGE_VAL) && |
1343 | 0 | data[iii] != HUGE_VAL) |
1344 | 0 | negative_max = data[iii]; |
1345 | | // track general max value |
1346 | 0 | if ((data[iii] > max_value || max_value == HUGE_VAL) && |
1347 | 0 | data[iii] != HUGE_VAL) |
1348 | 0 | max_value = data[iii]; |
1349 | 0 | } |
1350 | 0 | if (crossed_meridian_count == 2) |
1351 | 0 | return negative_max; |
1352 | 0 | else if (crossed_meridian_count == 4) |
1353 | | // bounds extends beyond -180/180 |
1354 | 0 | return 180; |
1355 | 0 | return max_value; |
1356 | 0 | } |
1357 | | |
1358 | | // --------------------------------------------------------------------------- |
1359 | | // Check if the original projected bounds contains |
1360 | | // the north pole. |
1361 | | // This assumes that the destination CRS is geographic. |
1362 | | static bool contains_north_pole(PJ *projobj, PJ_DIRECTION pj_direction, |
1363 | | const double xmin, const double ymin, |
1364 | | const double xmax, const double ymax, |
1365 | 0 | bool lon_lat_order) { |
1366 | 0 | double pole_y = 90; |
1367 | 0 | double pole_x = 0; |
1368 | 0 | if (!lon_lat_order) { |
1369 | 0 | pole_y = 0; |
1370 | 0 | pole_x = 90; |
1371 | 0 | } |
1372 | 0 | proj_trans_generic(projobj, opposite_direction(pj_direction), &pole_x, |
1373 | 0 | sizeof(double), 1, &pole_y, sizeof(double), 1, nullptr, |
1374 | 0 | sizeof(double), 0, nullptr, sizeof(double), 0); |
1375 | 0 | if (xmin < pole_x && pole_x < xmax && ymax > pole_y && pole_y > ymin) |
1376 | 0 | return true; |
1377 | 0 | return false; |
1378 | 0 | } |
1379 | | |
1380 | | // --------------------------------------------------------------------------- |
1381 | | // Check if the original projected bounds contains |
1382 | | // the south pole. |
1383 | | // This assumes that the destination CRS is geographic. |
1384 | | static bool contains_south_pole(PJ *projobj, PJ_DIRECTION pj_direction, |
1385 | | const double xmin, const double ymin, |
1386 | | const double xmax, const double ymax, |
1387 | 0 | bool lon_lat_order) { |
1388 | 0 | double pole_y = -90; |
1389 | 0 | double pole_x = 0; |
1390 | 0 | if (!lon_lat_order) { |
1391 | 0 | pole_y = 0; |
1392 | 0 | pole_x = -90; |
1393 | 0 | } |
1394 | 0 | proj_trans_generic(projobj, opposite_direction(pj_direction), &pole_x, |
1395 | 0 | sizeof(double), 1, &pole_y, sizeof(double), 1, nullptr, |
1396 | 0 | sizeof(double), 0, nullptr, sizeof(double), 0); |
1397 | 0 | if (xmin < pole_x && pole_x < xmax && ymax > pole_y && pole_y > ymin) |
1398 | 0 | return true; |
1399 | 0 | return false; |
1400 | 0 | } |
1401 | | |
1402 | | // --------------------------------------------------------------------------- |
1403 | | // Check if the target CRS of the transformation |
1404 | | // has the longitude latitude axis order. |
1405 | | // This assumes that the destination CRS is geographic. |
1406 | | static int target_crs_lon_lat_order(PJ_CONTEXT *transformer_ctx, |
1407 | | PJ *transformer_pj, |
1408 | 0 | PJ_DIRECTION pj_direction) { |
1409 | 0 | PJ *target_crs = nullptr; |
1410 | 0 | if (pj_direction == PJ_FWD) |
1411 | 0 | target_crs = proj_get_target_crs(transformer_ctx, transformer_pj); |
1412 | 0 | else if (pj_direction == PJ_INV) |
1413 | 0 | target_crs = proj_get_source_crs(transformer_ctx, transformer_pj); |
1414 | 0 | if (target_crs == nullptr) { |
1415 | 0 | proj_context_log_debug(transformer_ctx, |
1416 | 0 | "Unable to retrieve target CRS"); |
1417 | 0 | return -1; |
1418 | 0 | } |
1419 | 0 | PJ *coord_system_pj = |
1420 | 0 | proj_crs_get_coordinate_system(transformer_ctx, target_crs); |
1421 | 0 | proj_destroy(target_crs); |
1422 | 0 | if (coord_system_pj == nullptr) { |
1423 | 0 | proj_context_log_debug(transformer_ctx, |
1424 | 0 | "Unable to get target CRS coordinate system."); |
1425 | 0 | return -1; |
1426 | 0 | } |
1427 | 0 | const char *abbrev = nullptr; |
1428 | 0 | int success = proj_cs_get_axis_info(transformer_ctx, coord_system_pj, 0, |
1429 | 0 | nullptr, &abbrev, nullptr, nullptr, |
1430 | 0 | nullptr, nullptr, nullptr); |
1431 | 0 | proj_destroy(coord_system_pj); |
1432 | 0 | if (success != 1) |
1433 | 0 | return -1; |
1434 | 0 | return strcmp(abbrev, "lon") == 0 || strcmp(abbrev, "Lon") == 0; |
1435 | 0 | } |
1436 | | |
1437 | | // --------------------------------------------------------------------------- |
1438 | | |
1439 | | /** \brief Transform boundary, |
1440 | | * |
1441 | | * Transform boundary densifying the edges to account for nonlinear |
1442 | | * transformations along these edges and extracting the outermost bounds. |
1443 | | * |
1444 | | * If the destination CRS is geographic, the first axis is longitude, |
1445 | | * and xmax < xmin then the bounds crossed the antimeridian. |
1446 | | * In this scenario there are two polygons, one on each side of the |
1447 | | * antimeridian. The first polygon should be constructed with (xmin, ymin, 180, |
1448 | | * ymax) and the second with (-180, ymin, xmax, ymax). |
1449 | | * |
1450 | | * If the destination CRS is geographic, the first axis is latitude, |
1451 | | * and ymax < ymin then the bounds crossed the antimeridian. |
1452 | | * In this scenario there are two polygons, one on each side of the |
1453 | | * antimeridian. The first polygon should be constructed with (ymin, xmin, ymax, |
1454 | | * 180) and the second with (ymin, -180, ymax, xmax). |
1455 | | * |
1456 | | * @param context The PJ_CONTEXT object. |
1457 | | * @param P The PJ object representing the transformation. |
1458 | | * @param direction The direction of the transformation. |
1459 | | * @param xmin Minimum bounding coordinate of the first axis in source CRS |
1460 | | * (target CRS if direction is inverse). |
1461 | | * @param ymin Minimum bounding coordinate of the second axis in source CRS. |
1462 | | * (target CRS if direction is inverse). |
1463 | | * @param xmax Maximum bounding coordinate of the first axis in source CRS. |
1464 | | * (target CRS if direction is inverse). |
1465 | | * @param ymax Maximum bounding coordinate of the second axis in source CRS. |
1466 | | * (target CRS if direction is inverse). |
1467 | | * @param out_xmin Minimum bounding coordinate of the first axis in target CRS |
1468 | | * (source CRS if direction is inverse). |
1469 | | * @param out_ymin Minimum bounding coordinate of the second axis in target CRS. |
1470 | | * (source CRS if direction is inverse). |
1471 | | * @param out_xmax Maximum bounding coordinate of the first axis in target CRS. |
1472 | | * (source CRS if direction is inverse). |
1473 | | * @param out_ymax Maximum bounding coordinate of the second axis in target CRS. |
1474 | | * (source CRS if direction is inverse). |
1475 | | * @param densify_pts Recommended to use 21. This is the number of points |
1476 | | * to use to densify the bounding polygon in the transformation. |
1477 | | * @return an integer. 1 if successful. 0 if failures encountered. |
1478 | | * @since 8.2 |
1479 | | */ |
1480 | | int proj_trans_bounds(PJ_CONTEXT *context, PJ *P, PJ_DIRECTION direction, |
1481 | | const double xmin, const double ymin, const double xmax, |
1482 | | const double ymax, double *out_xmin, double *out_ymin, |
1483 | | double *out_xmax, double *out_ymax, |
1484 | 0 | const int densify_pts) { |
1485 | 0 | *out_xmin = HUGE_VAL; |
1486 | 0 | *out_ymin = HUGE_VAL; |
1487 | 0 | *out_xmax = HUGE_VAL; |
1488 | 0 | *out_ymax = HUGE_VAL; |
1489 | |
|
1490 | 0 | if (P == nullptr) { |
1491 | 0 | proj_log_error(P, _("NULL P object not allowed.")); |
1492 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
1493 | 0 | return false; |
1494 | 0 | } |
1495 | 0 | if (densify_pts < 0 || densify_pts > 10000) { |
1496 | 0 | proj_log_error(P, _("densify_pts must be between 0-10000.")); |
1497 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
1498 | 0 | return false; |
1499 | 0 | } |
1500 | | |
1501 | 0 | PJ_PROJ_INFO pj_info = proj_pj_info(P); |
1502 | 0 | if (pj_info.id == nullptr) { |
1503 | 0 | proj_log_error(P, _("NULL transformation not allowed,")); |
1504 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
1505 | 0 | return false; |
1506 | 0 | } |
1507 | 0 | if (strcmp(pj_info.id, "noop") == 0 || direction == PJ_IDENT) { |
1508 | 0 | *out_xmin = xmin; |
1509 | 0 | *out_xmax = xmax; |
1510 | 0 | *out_ymin = ymin; |
1511 | 0 | *out_ymax = ymax; |
1512 | 0 | return true; |
1513 | 0 | } |
1514 | | |
1515 | 0 | bool degree_output = proj_degree_output(P, direction) != 0; |
1516 | 0 | bool degree_input = proj_degree_input(P, direction) != 0; |
1517 | 0 | if (degree_output && densify_pts < 2) { |
1518 | 0 | proj_log_error( |
1519 | 0 | P, |
1520 | 0 | _("densify_pts must be at least 2 if the output is geographic.")); |
1521 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
1522 | 0 | return false; |
1523 | 0 | } |
1524 | | |
1525 | 0 | int side_pts = densify_pts + 1; // add one because we are densifying |
1526 | 0 | const int boundary_len = side_pts * 4; |
1527 | 0 | std::vector<double> x_boundary_array; |
1528 | 0 | std::vector<double> y_boundary_array; |
1529 | 0 | try { |
1530 | 0 | x_boundary_array.resize(boundary_len); |
1531 | 0 | y_boundary_array.resize(boundary_len); |
1532 | 0 | } catch (const std::exception &e) // memory allocation failure |
1533 | 0 | { |
1534 | 0 | proj_log_error(P, e.what()); |
1535 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
1536 | 0 | return false; |
1537 | 0 | } |
1538 | 0 | double delta_x = 0; |
1539 | 0 | double delta_y = 0; |
1540 | 0 | bool north_pole_in_bounds = false; |
1541 | 0 | bool south_pole_in_bounds = false; |
1542 | 0 | bool input_lon_lat_order = false; |
1543 | 0 | bool output_lon_lat_order = false; |
1544 | 0 | if (degree_input) { |
1545 | 0 | int in_order_lon_lat = |
1546 | 0 | target_crs_lon_lat_order(context, P, opposite_direction(direction)); |
1547 | 0 | if (in_order_lon_lat == -1) |
1548 | 0 | return false; |
1549 | 0 | input_lon_lat_order = in_order_lon_lat != 0; |
1550 | 0 | } |
1551 | 0 | if (degree_output) { |
1552 | 0 | int out_order_lon_lat = target_crs_lon_lat_order(context, P, direction); |
1553 | 0 | if (out_order_lon_lat == -1) |
1554 | 0 | return false; |
1555 | 0 | output_lon_lat_order = out_order_lon_lat != 0; |
1556 | 0 | north_pole_in_bounds = contains_north_pole( |
1557 | 0 | P, direction, xmin, ymin, xmax, ymax, output_lon_lat_order); |
1558 | 0 | south_pole_in_bounds = contains_south_pole( |
1559 | 0 | P, direction, xmin, ymin, xmax, ymax, output_lon_lat_order); |
1560 | 0 | } |
1561 | | |
1562 | 0 | if (degree_input && xmax < xmin) { |
1563 | 0 | if (!input_lon_lat_order) { |
1564 | 0 | proj_log_error(P, _("latitude max < latitude min.")); |
1565 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
1566 | 0 | return false; |
1567 | 0 | } |
1568 | | // handle antimeridian |
1569 | 0 | delta_x = (xmax - xmin + 360.0) / side_pts; |
1570 | 0 | } else { |
1571 | 0 | delta_x = (xmax - xmin) / side_pts; |
1572 | 0 | } |
1573 | 0 | if (degree_input && ymax < ymin) { |
1574 | 0 | if (input_lon_lat_order) { |
1575 | 0 | proj_log_error(P, _("latitude max < latitude min.")); |
1576 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
1577 | 0 | return false; |
1578 | 0 | } |
1579 | | // handle antimeridian |
1580 | 0 | delta_y = (ymax - ymin + 360.0) / side_pts; |
1581 | 0 | } else { |
1582 | 0 | delta_y = (ymax - ymin) / side_pts; |
1583 | 0 | } |
1584 | | |
1585 | | // build densified bounding box |
1586 | | // Note: must be a linear ring for antimeridian logic |
1587 | 0 | for (int iii = 0; iii < side_pts; iii++) { |
1588 | | // xmin boundary |
1589 | 0 | y_boundary_array[iii] = ymax - iii * delta_y; |
1590 | 0 | x_boundary_array[iii] = xmin; |
1591 | | // ymin boundary |
1592 | 0 | y_boundary_array[iii + side_pts] = ymin; |
1593 | 0 | x_boundary_array[iii + side_pts] = xmin + iii * delta_x; |
1594 | | // xmax boundary |
1595 | 0 | y_boundary_array[iii + side_pts * 2] = ymin + iii * delta_y; |
1596 | 0 | x_boundary_array[iii + side_pts * 2] = xmax; |
1597 | | // ymax boundary |
1598 | 0 | y_boundary_array[iii + side_pts * 3] = ymax; |
1599 | 0 | x_boundary_array[iii + side_pts * 3] = xmax - iii * delta_x; |
1600 | 0 | } |
1601 | 0 | proj_trans_generic(P, direction, &x_boundary_array[0], sizeof(double), |
1602 | 0 | boundary_len, &y_boundary_array[0], sizeof(double), |
1603 | 0 | boundary_len, nullptr, 0, 0, nullptr, 0, 0); |
1604 | |
|
1605 | 0 | if (!degree_output) { |
1606 | 0 | *out_xmin = simple_min(&x_boundary_array[0], boundary_len); |
1607 | 0 | *out_xmax = simple_max(&x_boundary_array[0], boundary_len); |
1608 | 0 | *out_ymin = simple_min(&y_boundary_array[0], boundary_len); |
1609 | 0 | *out_ymax = simple_max(&y_boundary_array[0], boundary_len); |
1610 | 0 | } else if (north_pole_in_bounds && output_lon_lat_order) { |
1611 | 0 | *out_xmin = -180; |
1612 | 0 | *out_ymin = simple_min(&y_boundary_array[0], boundary_len); |
1613 | 0 | *out_xmax = 180; |
1614 | 0 | *out_ymax = 90; |
1615 | 0 | } else if (north_pole_in_bounds) { |
1616 | 0 | *out_xmin = simple_min(&x_boundary_array[0], boundary_len); |
1617 | 0 | *out_ymin = -180; |
1618 | 0 | *out_xmax = 90; |
1619 | 0 | *out_ymax = 180; |
1620 | 0 | } else if (south_pole_in_bounds && output_lon_lat_order) { |
1621 | 0 | *out_xmin = -180; |
1622 | 0 | *out_ymin = -90; |
1623 | 0 | *out_xmax = 180; |
1624 | 0 | *out_ymax = simple_max(&y_boundary_array[0], boundary_len); |
1625 | 0 | } else if (south_pole_in_bounds) { |
1626 | 0 | *out_xmin = -90; |
1627 | 0 | *out_ymin = -180; |
1628 | 0 | *out_xmax = simple_max(&x_boundary_array[0], boundary_len); |
1629 | 0 | *out_ymax = 180; |
1630 | 0 | } else if (output_lon_lat_order) { |
1631 | 0 | *out_xmin = antimeridian_min(&x_boundary_array[0], boundary_len); |
1632 | 0 | *out_xmax = antimeridian_max(&x_boundary_array[0], boundary_len); |
1633 | 0 | *out_ymin = simple_min(&y_boundary_array[0], boundary_len); |
1634 | 0 | *out_ymax = simple_max(&y_boundary_array[0], boundary_len); |
1635 | 0 | } else { |
1636 | 0 | *out_xmin = simple_min(&x_boundary_array[0], boundary_len); |
1637 | 0 | *out_xmax = simple_max(&x_boundary_array[0], boundary_len); |
1638 | 0 | *out_ymin = antimeridian_min(&y_boundary_array[0], boundary_len); |
1639 | 0 | *out_ymax = antimeridian_max(&y_boundary_array[0], boundary_len); |
1640 | 0 | } |
1641 | 0 | return true; |
1642 | 0 | } |
1643 | | |
1644 | | /*****************************************************************************/ |
1645 | | static void reproject_bbox(PJ *pjGeogToCrs, double west_lon, double south_lat, |
1646 | | double east_lon, double north_lat, double &minx, |
1647 | 0 | double &miny, double &maxx, double &maxy) { |
1648 | | /*****************************************************************************/ |
1649 | |
|
1650 | 0 | minx = -std::numeric_limits<double>::max(); |
1651 | 0 | miny = -std::numeric_limits<double>::max(); |
1652 | 0 | maxx = std::numeric_limits<double>::max(); |
1653 | 0 | maxy = std::numeric_limits<double>::max(); |
1654 | |
|
1655 | 0 | if (!(west_lon == -180.0 && east_lon == 180.0 && south_lat == -90.0 && |
1656 | 0 | north_lat == 90.0)) { |
1657 | 0 | minx = -minx; |
1658 | 0 | miny = -miny; |
1659 | 0 | maxx = -maxx; |
1660 | 0 | maxy = -maxy; |
1661 | |
|
1662 | 0 | constexpr int N_STEPS = 20; |
1663 | 0 | constexpr int N_STEPS_P1 = N_STEPS + 1; |
1664 | 0 | constexpr int XY_SIZE = N_STEPS_P1 * 4; |
1665 | 0 | std::vector<double> x(XY_SIZE); |
1666 | 0 | std::vector<double> y(XY_SIZE); |
1667 | 0 | const double step_lon = (east_lon - west_lon) / N_STEPS; |
1668 | 0 | const double step_lat = (north_lat - south_lat) / N_STEPS; |
1669 | 0 | for (int j = 0; j <= N_STEPS; j++) { |
1670 | 0 | x[j] = west_lon + j * step_lon; |
1671 | 0 | y[j] = south_lat; |
1672 | 0 | x[N_STEPS_P1 + j] = x[j]; |
1673 | 0 | y[N_STEPS_P1 + j] = north_lat; |
1674 | 0 | x[N_STEPS_P1 * 2 + j] = west_lon; |
1675 | 0 | y[N_STEPS_P1 * 2 + j] = south_lat + j * step_lat; |
1676 | 0 | x[N_STEPS_P1 * 3 + j] = east_lon; |
1677 | 0 | y[N_STEPS_P1 * 3 + j] = y[N_STEPS_P1 * 2 + j]; |
1678 | 0 | } |
1679 | 0 | proj_trans_generic(pjGeogToCrs, PJ_FWD, &x[0], sizeof(double), XY_SIZE, |
1680 | 0 | &y[0], sizeof(double), XY_SIZE, nullptr, 0, 0, |
1681 | 0 | nullptr, 0, 0); |
1682 | 0 | for (int j = 0; j < XY_SIZE; j++) { |
1683 | 0 | if (x[j] != HUGE_VAL && y[j] != HUGE_VAL) { |
1684 | 0 | minx = std::min(minx, x[j]); |
1685 | 0 | miny = std::min(miny, y[j]); |
1686 | 0 | maxx = std::max(maxx, x[j]); |
1687 | 0 | maxy = std::max(maxy, y[j]); |
1688 | 0 | } |
1689 | 0 | } |
1690 | 0 | } |
1691 | 0 | } |
1692 | | |
1693 | | /*****************************************************************************/ |
1694 | | static PJ *add_coord_op_to_list( |
1695 | | int idxInOriginalList, PJ *op, double west_lon, double south_lat, |
1696 | | double east_lon, double north_lat, PJ *pjGeogToSrc, PJ *pjGeogToDst, |
1697 | | const PJ *pjSrcGeocentricToLonLat, const PJ *pjDstGeocentricToLonLat, |
1698 | 0 | const char *areaName, std::vector<PJCoordOperation> &altCoordOps) { |
1699 | | /*****************************************************************************/ |
1700 | |
|
1701 | 0 | double minxSrc; |
1702 | 0 | double minySrc; |
1703 | 0 | double maxxSrc; |
1704 | 0 | double maxySrc; |
1705 | 0 | double minxDst; |
1706 | 0 | double minyDst; |
1707 | 0 | double maxxDst; |
1708 | 0 | double maxyDst; |
1709 | |
|
1710 | 0 | double w = west_lon / 180 * M_PI; |
1711 | 0 | double s = south_lat / 180 * M_PI; |
1712 | 0 | double e = east_lon / 180 * M_PI; |
1713 | 0 | double n = north_lat / 180 * M_PI; |
1714 | 0 | if (w > e) { |
1715 | 0 | e += 2 * M_PI; |
1716 | 0 | } |
1717 | | // Integrate cos(lat) between south_lat and north_lat |
1718 | 0 | const double pseudoArea = (e - w) * (std::sin(n) - std::sin(s)); |
1719 | |
|
1720 | 0 | if (pjSrcGeocentricToLonLat) { |
1721 | 0 | minxSrc = west_lon; |
1722 | 0 | minySrc = south_lat; |
1723 | 0 | maxxSrc = east_lon; |
1724 | 0 | maxySrc = north_lat; |
1725 | 0 | } else { |
1726 | 0 | reproject_bbox(pjGeogToSrc, west_lon, south_lat, east_lon, north_lat, |
1727 | 0 | minxSrc, minySrc, maxxSrc, maxySrc); |
1728 | 0 | } |
1729 | |
|
1730 | 0 | if (pjDstGeocentricToLonLat) { |
1731 | 0 | minxDst = west_lon; |
1732 | 0 | minyDst = south_lat; |
1733 | 0 | maxxDst = east_lon; |
1734 | 0 | maxyDst = north_lat; |
1735 | 0 | } else { |
1736 | 0 | reproject_bbox(pjGeogToDst, west_lon, south_lat, east_lon, north_lat, |
1737 | 0 | minxDst, minyDst, maxxDst, maxyDst); |
1738 | 0 | } |
1739 | |
|
1740 | 0 | if (minxSrc <= maxxSrc && minxDst <= maxxDst) { |
1741 | 0 | const char *c_name = proj_get_name(op); |
1742 | 0 | std::string name(c_name ? c_name : ""); |
1743 | |
|
1744 | 0 | const double accuracy = proj_coordoperation_get_accuracy(op->ctx, op); |
1745 | 0 | altCoordOps.emplace_back( |
1746 | 0 | idxInOriginalList, minxSrc, minySrc, maxxSrc, maxySrc, minxDst, |
1747 | 0 | minyDst, maxxDst, maxyDst, op, name, accuracy, pseudoArea, areaName, |
1748 | 0 | pjSrcGeocentricToLonLat, pjDstGeocentricToLonLat); |
1749 | 0 | op = nullptr; |
1750 | 0 | } |
1751 | 0 | return op; |
1752 | 0 | } |
1753 | | |
1754 | | namespace { |
1755 | | struct ObjectKeeper { |
1756 | | PJ *m_obj = nullptr; |
1757 | 0 | explicit ObjectKeeper(PJ *obj) : m_obj(obj) {} |
1758 | 0 | ~ObjectKeeper() { proj_destroy(m_obj); } |
1759 | | ObjectKeeper(const ObjectKeeper &) = delete; |
1760 | | ObjectKeeper &operator=(const ObjectKeeper &) = delete; |
1761 | | }; |
1762 | | } // namespace |
1763 | | |
1764 | | /*****************************************************************************/ |
1765 | 0 | static PJ *create_operation_to_geog_crs(PJ_CONTEXT *ctx, const PJ *crs) { |
1766 | | /*****************************************************************************/ |
1767 | |
|
1768 | 0 | std::unique_ptr<ObjectKeeper> keeper; |
1769 | 0 | if (proj_get_type(crs) == PJ_TYPE_COORDINATE_METADATA) { |
1770 | 0 | auto tmp = proj_get_source_crs(ctx, crs); |
1771 | 0 | assert(tmp); |
1772 | 0 | keeper.reset(new ObjectKeeper(tmp)); |
1773 | 0 | crs = tmp; |
1774 | 0 | } |
1775 | 0 | (void)keeper; |
1776 | | |
1777 | | // Create a geographic 2D long-lat degrees CRS that is related to the |
1778 | | // CRS |
1779 | 0 | auto geodetic_crs = proj_crs_get_geodetic_crs(ctx, crs); |
1780 | 0 | if (!geodetic_crs) { |
1781 | 0 | proj_context_log_debug(ctx, "Cannot find geodetic CRS matching CRS"); |
1782 | 0 | return nullptr; |
1783 | 0 | } |
1784 | | |
1785 | 0 | auto geodetic_crs_type = proj_get_type(geodetic_crs); |
1786 | 0 | if (geodetic_crs_type == PJ_TYPE_GEOCENTRIC_CRS || |
1787 | 0 | geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_2D_CRS || |
1788 | 0 | geodetic_crs_type == PJ_TYPE_GEOGRAPHIC_3D_CRS) { |
1789 | 0 | auto datum = proj_crs_get_datum_forced(ctx, geodetic_crs); |
1790 | 0 | assert(datum); |
1791 | 0 | auto cs = proj_create_ellipsoidal_2D_cs( |
1792 | 0 | ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, nullptr, 0); |
1793 | 0 | auto ellps = proj_get_ellipsoid(ctx, datum); |
1794 | 0 | proj_destroy(datum); |
1795 | 0 | double semi_major_metre = 0; |
1796 | 0 | double inv_flattening = 0; |
1797 | 0 | proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, nullptr, |
1798 | 0 | nullptr, &inv_flattening); |
1799 | | // It is critical to set the prime meridian to 0 |
1800 | 0 | auto temp = proj_create_geographic_crs( |
1801 | 0 | ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps), |
1802 | 0 | semi_major_metre, inv_flattening, "Reference prime meridian", 0, |
1803 | 0 | nullptr, 0, cs); |
1804 | 0 | proj_destroy(ellps); |
1805 | 0 | proj_destroy(cs); |
1806 | 0 | proj_destroy(geodetic_crs); |
1807 | 0 | geodetic_crs = temp; |
1808 | 0 | geodetic_crs_type = proj_get_type(geodetic_crs); |
1809 | 0 | } |
1810 | 0 | if (geodetic_crs_type != PJ_TYPE_GEOGRAPHIC_2D_CRS) { |
1811 | | // Shouldn't happen |
1812 | 0 | proj_context_log_debug(ctx, "Cannot find geographic CRS matching CRS"); |
1813 | 0 | proj_destroy(geodetic_crs); |
1814 | 0 | return nullptr; |
1815 | 0 | } |
1816 | | |
1817 | | // Create the transformation from this geographic 2D CRS to the source CRS |
1818 | 0 | auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr); |
1819 | 0 | proj_operation_factory_context_set_spatial_criterion( |
1820 | 0 | ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); |
1821 | 0 | proj_operation_factory_context_set_grid_availability_use( |
1822 | 0 | ctx, operation_ctx, |
1823 | 0 | PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); |
1824 | 0 | auto target_crs_2D = proj_crs_demote_to_2D(ctx, nullptr, crs); |
1825 | 0 | auto op_list_to_geodetic = |
1826 | 0 | proj_create_operations(ctx, geodetic_crs, target_crs_2D, operation_ctx); |
1827 | 0 | proj_destroy(target_crs_2D); |
1828 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
1829 | 0 | proj_destroy(geodetic_crs); |
1830 | |
|
1831 | 0 | const int nOpCount = op_list_to_geodetic == nullptr |
1832 | 0 | ? 0 |
1833 | 0 | : proj_list_get_count(op_list_to_geodetic); |
1834 | 0 | if (nOpCount == 0) { |
1835 | 0 | proj_context_log_debug( |
1836 | 0 | ctx, "Cannot compute transformation from geographic CRS to CRS"); |
1837 | 0 | proj_list_destroy(op_list_to_geodetic); |
1838 | 0 | return nullptr; |
1839 | 0 | } |
1840 | 0 | PJ *opGeogToCrs = nullptr; |
1841 | | // Use in priority operations *without* grids |
1842 | 0 | for (int i = 0; i < nOpCount; i++) { |
1843 | 0 | auto op = proj_list_get(ctx, op_list_to_geodetic, i); |
1844 | 0 | assert(op); |
1845 | 0 | if (proj_coordoperation_get_grid_used_count(ctx, op) == 0) { |
1846 | 0 | opGeogToCrs = op; |
1847 | 0 | break; |
1848 | 0 | } |
1849 | 0 | proj_destroy(op); |
1850 | 0 | } |
1851 | 0 | if (opGeogToCrs == nullptr) { |
1852 | 0 | opGeogToCrs = proj_list_get(ctx, op_list_to_geodetic, 0); |
1853 | 0 | assert(opGeogToCrs); |
1854 | 0 | } |
1855 | 0 | proj_list_destroy(op_list_to_geodetic); |
1856 | 0 | return opGeogToCrs; |
1857 | 0 | } |
1858 | | |
1859 | | /*****************************************************************************/ |
1860 | | static PJ *create_operation_geocentric_crs_to_geog_crs(PJ_CONTEXT *ctx, |
1861 | | const PJ *geocentric_crs) |
1862 | | /*****************************************************************************/ |
1863 | 0 | { |
1864 | 0 | assert(proj_get_type(geocentric_crs) == PJ_TYPE_GEOCENTRIC_CRS); |
1865 | |
|
1866 | 0 | auto datum = proj_crs_get_datum_forced(ctx, geocentric_crs); |
1867 | 0 | assert(datum); |
1868 | 0 | auto cs = proj_create_ellipsoidal_2D_cs(ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, |
1869 | 0 | nullptr, 0); |
1870 | 0 | auto ellps = proj_get_ellipsoid(ctx, datum); |
1871 | 0 | proj_destroy(datum); |
1872 | 0 | double semi_major_metre = 0; |
1873 | 0 | double inv_flattening = 0; |
1874 | 0 | proj_ellipsoid_get_parameters(ctx, ellps, &semi_major_metre, nullptr, |
1875 | 0 | nullptr, &inv_flattening); |
1876 | | // It is critical to set the prime meridian to 0 |
1877 | 0 | auto lon_lat_crs = proj_create_geographic_crs( |
1878 | 0 | ctx, "unnamed crs", "unnamed datum", proj_get_name(ellps), |
1879 | 0 | semi_major_metre, inv_flattening, "Reference prime meridian", 0, |
1880 | 0 | nullptr, 0, cs); |
1881 | 0 | proj_destroy(ellps); |
1882 | 0 | proj_destroy(cs); |
1883 | | |
1884 | | // Create the transformation from this geocentric CRS to the lon-lat one |
1885 | 0 | auto operation_ctx = proj_create_operation_factory_context(ctx, nullptr); |
1886 | 0 | proj_operation_factory_context_set_spatial_criterion( |
1887 | 0 | ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); |
1888 | 0 | proj_operation_factory_context_set_grid_availability_use( |
1889 | 0 | ctx, operation_ctx, |
1890 | 0 | PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); |
1891 | 0 | auto op_list = |
1892 | 0 | proj_create_operations(ctx, geocentric_crs, lon_lat_crs, operation_ctx); |
1893 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
1894 | 0 | proj_destroy(lon_lat_crs); |
1895 | |
|
1896 | 0 | const int nOpCount = op_list == nullptr ? 0 : proj_list_get_count(op_list); |
1897 | 0 | if (nOpCount != 1) { |
1898 | 0 | proj_context_log_debug(ctx, "Cannot compute transformation from " |
1899 | 0 | "geocentric CRS to geographic CRS"); |
1900 | 0 | proj_list_destroy(op_list); |
1901 | 0 | return nullptr; |
1902 | 0 | } |
1903 | | |
1904 | 0 | auto op = proj_list_get(ctx, op_list, 0); |
1905 | 0 | assert(op); |
1906 | 0 | proj_list_destroy(op_list); |
1907 | 0 | return op; |
1908 | 0 | } |
1909 | | |
1910 | | /*****************************************************************************/ |
1911 | | PJ *proj_create_crs_to_crs(PJ_CONTEXT *ctx, const char *source_crs, |
1912 | 28.9k | const char *target_crs, PJ_AREA *area) { |
1913 | | /****************************************************************************** |
1914 | | Create a transformation pipeline between two known coordinate reference |
1915 | | systems. |
1916 | | |
1917 | | See docs/source/development/reference/functions.rst |
1918 | | |
1919 | | ******************************************************************************/ |
1920 | 28.9k | if (!ctx) { |
1921 | 28.9k | ctx = pj_get_default_ctx(); |
1922 | 28.9k | } |
1923 | | |
1924 | 28.9k | PJ *src; |
1925 | 28.9k | PJ *dst; |
1926 | 28.9k | try { |
1927 | 28.9k | std::string source_crs_modified(pj_add_type_crs_if_needed(source_crs)); |
1928 | 28.9k | std::string target_crs_modified(pj_add_type_crs_if_needed(target_crs)); |
1929 | | |
1930 | 28.9k | src = proj_create(ctx, source_crs_modified.c_str()); |
1931 | 28.9k | if (!src) { |
1932 | 12.3k | proj_context_log_debug(ctx, "Cannot instantiate source_crs"); |
1933 | 12.3k | return nullptr; |
1934 | 12.3k | } |
1935 | | |
1936 | 16.5k | dst = proj_create(ctx, target_crs_modified.c_str()); |
1937 | 16.5k | if (!dst) { |
1938 | 7.43k | proj_context_log_debug(ctx, "Cannot instantiate target_crs"); |
1939 | 7.43k | proj_destroy(src); |
1940 | 7.43k | return nullptr; |
1941 | 7.43k | } |
1942 | 16.5k | } catch (const std::exception &) { |
1943 | 0 | return nullptr; |
1944 | 0 | } |
1945 | | |
1946 | 9.08k | auto ret = proj_create_crs_to_crs_from_pj(ctx, src, dst, area, nullptr); |
1947 | 9.08k | proj_destroy(src); |
1948 | 9.08k | proj_destroy(dst); |
1949 | 9.08k | return ret; |
1950 | 28.9k | } |
1951 | | |
1952 | | /*****************************************************************************/ |
1953 | | std::vector<PJCoordOperation> |
1954 | | pj_create_prepared_operations(PJ_CONTEXT *ctx, const PJ *source_crs, |
1955 | | const PJ *target_crs, PJ_OBJ_LIST *op_list) |
1956 | | /*****************************************************************************/ |
1957 | 0 | { |
1958 | 0 | PJ *pjGeogToSrc = nullptr; |
1959 | 0 | PJ *pjSrcGeocentricToLonLat = nullptr; |
1960 | 0 | if (proj_get_type(source_crs) == PJ_TYPE_GEOCENTRIC_CRS) { |
1961 | 0 | pjSrcGeocentricToLonLat = |
1962 | 0 | create_operation_geocentric_crs_to_geog_crs(ctx, source_crs); |
1963 | 0 | if (!pjSrcGeocentricToLonLat) { |
1964 | | // shouldn't happen |
1965 | 0 | return {}; |
1966 | 0 | } |
1967 | 0 | } else { |
1968 | 0 | pjGeogToSrc = create_operation_to_geog_crs(ctx, source_crs); |
1969 | 0 | if (!pjGeogToSrc) { |
1970 | 0 | proj_context_log_debug( |
1971 | 0 | ctx, "Cannot create transformation from geographic " |
1972 | 0 | "CRS of source CRS to source CRS"); |
1973 | 0 | return {}; |
1974 | 0 | } |
1975 | 0 | } |
1976 | | |
1977 | 0 | PJ *pjGeogToDst = nullptr; |
1978 | 0 | PJ *pjDstGeocentricToLonLat = nullptr; |
1979 | 0 | if (proj_get_type(target_crs) == PJ_TYPE_GEOCENTRIC_CRS) { |
1980 | 0 | pjDstGeocentricToLonLat = |
1981 | 0 | create_operation_geocentric_crs_to_geog_crs(ctx, target_crs); |
1982 | 0 | if (!pjDstGeocentricToLonLat) { |
1983 | | // shouldn't happen |
1984 | 0 | proj_destroy(pjSrcGeocentricToLonLat); |
1985 | 0 | proj_destroy(pjGeogToSrc); |
1986 | 0 | return {}; |
1987 | 0 | } |
1988 | 0 | } else { |
1989 | 0 | pjGeogToDst = create_operation_to_geog_crs(ctx, target_crs); |
1990 | 0 | if (!pjGeogToDst) { |
1991 | 0 | proj_context_log_debug( |
1992 | 0 | ctx, "Cannot create transformation from geographic " |
1993 | 0 | "CRS of target CRS to target CRS"); |
1994 | 0 | proj_destroy(pjSrcGeocentricToLonLat); |
1995 | 0 | proj_destroy(pjGeogToSrc); |
1996 | 0 | return {}; |
1997 | 0 | } |
1998 | 0 | } |
1999 | | |
2000 | 0 | try { |
2001 | 0 | std::vector<PJCoordOperation> preparedOpList; |
2002 | | |
2003 | | // Iterate over source->target candidate transformations and reproject |
2004 | | // their long-lat bounding box into the source CRS. |
2005 | 0 | const auto op_count = proj_list_get_count(op_list); |
2006 | 0 | for (int i = 0; i < op_count; i++) { |
2007 | 0 | auto op = proj_list_get(ctx, op_list, i); |
2008 | 0 | assert(op); |
2009 | 0 | double west_lon = 0.0; |
2010 | 0 | double south_lat = 0.0; |
2011 | 0 | double east_lon = 0.0; |
2012 | 0 | double north_lat = 0.0; |
2013 | |
|
2014 | 0 | const char *areaName = nullptr; |
2015 | 0 | if (!proj_get_area_of_use(ctx, op, &west_lon, &south_lat, &east_lon, |
2016 | 0 | &north_lat, &areaName)) { |
2017 | 0 | west_lon = -180; |
2018 | 0 | south_lat = -90; |
2019 | 0 | east_lon = 180; |
2020 | 0 | north_lat = 90; |
2021 | 0 | } |
2022 | |
|
2023 | 0 | if (west_lon <= east_lon) { |
2024 | 0 | op = add_coord_op_to_list( |
2025 | 0 | i, op, west_lon, south_lat, east_lon, north_lat, |
2026 | 0 | pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat, |
2027 | 0 | pjDstGeocentricToLonLat, areaName, preparedOpList); |
2028 | 0 | } else { |
2029 | 0 | auto op_clone = proj_clone(ctx, op); |
2030 | |
|
2031 | 0 | op = add_coord_op_to_list( |
2032 | 0 | i, op, west_lon, south_lat, 180, north_lat, pjGeogToSrc, |
2033 | 0 | pjGeogToDst, pjSrcGeocentricToLonLat, |
2034 | 0 | pjDstGeocentricToLonLat, areaName, preparedOpList); |
2035 | 0 | op_clone = add_coord_op_to_list( |
2036 | 0 | i, op_clone, -180, south_lat, east_lon, north_lat, |
2037 | 0 | pjGeogToSrc, pjGeogToDst, pjSrcGeocentricToLonLat, |
2038 | 0 | pjDstGeocentricToLonLat, areaName, preparedOpList); |
2039 | 0 | proj_destroy(op_clone); |
2040 | 0 | } |
2041 | |
|
2042 | 0 | proj_destroy(op); |
2043 | 0 | } |
2044 | |
|
2045 | 0 | proj_destroy(pjGeogToSrc); |
2046 | 0 | proj_destroy(pjGeogToDst); |
2047 | 0 | proj_destroy(pjSrcGeocentricToLonLat); |
2048 | 0 | proj_destroy(pjDstGeocentricToLonLat); |
2049 | 0 | return preparedOpList; |
2050 | 0 | } catch (const std::exception &) { |
2051 | 0 | proj_destroy(pjGeogToSrc); |
2052 | 0 | proj_destroy(pjGeogToDst); |
2053 | 0 | proj_destroy(pjSrcGeocentricToLonLat); |
2054 | 0 | proj_destroy(pjDstGeocentricToLonLat); |
2055 | 0 | return {}; |
2056 | 0 | } |
2057 | 0 | } |
2058 | | |
2059 | | // --------------------------------------------------------------------------- |
2060 | | |
2061 | | //! @cond Doxygen_Suppress |
2062 | | static const char *getOptionValue(const char *option, |
2063 | 0 | const char *keyWithEqual) noexcept { |
2064 | 0 | if (ci_starts_with(option, keyWithEqual)) { |
2065 | 0 | return option + strlen(keyWithEqual); |
2066 | 0 | } |
2067 | 0 | return nullptr; |
2068 | 0 | } |
2069 | | //! @endcond |
2070 | | |
2071 | | /*****************************************************************************/ |
2072 | | PJ *proj_create_crs_to_crs_from_pj(PJ_CONTEXT *ctx, const PJ *source_crs, |
2073 | | const PJ *target_crs, PJ_AREA *area, |
2074 | 9.08k | const char *const *options) { |
2075 | | /****************************************************************************** |
2076 | | Create a transformation pipeline between two known coordinate reference |
2077 | | systems. |
2078 | | |
2079 | | See docs/source/development/reference/functions.rst |
2080 | | |
2081 | | ******************************************************************************/ |
2082 | 9.08k | if (!ctx) { |
2083 | 0 | ctx = pj_get_default_ctx(); |
2084 | 0 | } |
2085 | 9.08k | pj_load_ini( |
2086 | 9.08k | ctx); // to set ctx->errorIfBestTransformationNotAvailableDefault |
2087 | | |
2088 | 9.08k | const char *authority = nullptr; |
2089 | 9.08k | double accuracy = -1; |
2090 | 9.08k | bool allowBallparkTransformations = true; |
2091 | 9.08k | bool forceOver = false; |
2092 | 9.08k | bool warnIfBestTransformationNotAvailable = |
2093 | 9.08k | ctx->warnIfBestTransformationNotAvailableDefault; |
2094 | 9.08k | bool errorIfBestTransformationNotAvailable = |
2095 | 9.08k | ctx->errorIfBestTransformationNotAvailableDefault; |
2096 | 9.08k | for (auto iter = options; iter && iter[0]; ++iter) { |
2097 | 0 | const char *value; |
2098 | 0 | if ((value = getOptionValue(*iter, "AUTHORITY="))) { |
2099 | 0 | authority = value; |
2100 | 0 | } else if ((value = getOptionValue(*iter, "ACCURACY="))) { |
2101 | 0 | accuracy = pj_atof(value); |
2102 | 0 | } else if ((value = getOptionValue(*iter, "ALLOW_BALLPARK="))) { |
2103 | 0 | if (ci_equal(value, "yes")) |
2104 | 0 | allowBallparkTransformations = true; |
2105 | 0 | else if (ci_equal(value, "no")) |
2106 | 0 | allowBallparkTransformations = false; |
2107 | 0 | else { |
2108 | 0 | ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, |
2109 | 0 | "Invalid value for ALLOW_BALLPARK option."); |
2110 | 0 | return nullptr; |
2111 | 0 | } |
2112 | 0 | } else if ((value = getOptionValue(*iter, "ONLY_BEST="))) { |
2113 | 0 | warnIfBestTransformationNotAvailable = false; |
2114 | 0 | if (ci_equal(value, "yes")) |
2115 | 0 | errorIfBestTransformationNotAvailable = true; |
2116 | 0 | else if (ci_equal(value, "no")) |
2117 | 0 | errorIfBestTransformationNotAvailable = false; |
2118 | 0 | else { |
2119 | 0 | ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, |
2120 | 0 | "Invalid value for ONLY_BEST option."); |
2121 | 0 | return nullptr; |
2122 | 0 | } |
2123 | 0 | } else if ((value = getOptionValue(*iter, "FORCE_OVER="))) { |
2124 | 0 | if (ci_equal(value, "yes")) { |
2125 | 0 | forceOver = true; |
2126 | 0 | } |
2127 | 0 | } else { |
2128 | 0 | std::string msg("Unknown option :"); |
2129 | 0 | msg += *iter; |
2130 | 0 | ctx->logger(ctx->logger_app_data, PJ_LOG_ERROR, msg.c_str()); |
2131 | 0 | return nullptr; |
2132 | 0 | } |
2133 | 0 | } |
2134 | | |
2135 | 9.08k | auto operation_ctx = proj_create_operation_factory_context(ctx, authority); |
2136 | 9.08k | if (!operation_ctx) { |
2137 | 0 | return nullptr; |
2138 | 0 | } |
2139 | | |
2140 | 9.08k | proj_operation_factory_context_set_allow_ballpark_transformations( |
2141 | 9.08k | ctx, operation_ctx, allowBallparkTransformations); |
2142 | | |
2143 | 9.08k | if (accuracy >= 0) { |
2144 | 0 | proj_operation_factory_context_set_desired_accuracy(ctx, operation_ctx, |
2145 | 0 | accuracy); |
2146 | 0 | } |
2147 | | |
2148 | 9.08k | if (area && area->bbox_set) { |
2149 | 0 | proj_operation_factory_context_set_area_of_interest( |
2150 | 0 | ctx, operation_ctx, area->west_lon_degree, area->south_lat_degree, |
2151 | 0 | area->east_lon_degree, area->north_lat_degree); |
2152 | |
|
2153 | 0 | if (!area->name.empty()) { |
2154 | 0 | proj_operation_factory_context_set_area_of_interest_name( |
2155 | 0 | ctx, operation_ctx, area->name.c_str()); |
2156 | 0 | } |
2157 | 0 | } |
2158 | | |
2159 | 9.08k | proj_operation_factory_context_set_spatial_criterion( |
2160 | 9.08k | ctx, operation_ctx, PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); |
2161 | 9.08k | proj_operation_factory_context_set_grid_availability_use( |
2162 | 9.08k | ctx, operation_ctx, |
2163 | 9.08k | (errorIfBestTransformationNotAvailable || |
2164 | 9.08k | warnIfBestTransformationNotAvailable || |
2165 | 9.08k | proj_context_is_network_enabled(ctx)) |
2166 | 9.08k | ? PROJ_GRID_AVAILABILITY_KNOWN_AVAILABLE |
2167 | 9.08k | : PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); |
2168 | | |
2169 | 9.08k | auto op_list = |
2170 | 9.08k | proj_create_operations(ctx, source_crs, target_crs, operation_ctx); |
2171 | 9.08k | proj_operation_factory_context_destroy(operation_ctx); |
2172 | | |
2173 | 9.08k | if (!op_list) { |
2174 | 1.29k | return nullptr; |
2175 | 1.29k | } |
2176 | | |
2177 | 7.79k | auto op_count = proj_list_get_count(op_list); |
2178 | 7.79k | if (op_count == 0) { |
2179 | 176 | proj_list_destroy(op_list); |
2180 | | |
2181 | 176 | proj_context_log_debug(ctx, "No operation found matching criteria"); |
2182 | 176 | return nullptr; |
2183 | 176 | } |
2184 | | |
2185 | 7.61k | ctx->forceOver = forceOver; |
2186 | | |
2187 | 7.61k | const int old_debug_level = ctx->debug_level; |
2188 | 7.61k | if (errorIfBestTransformationNotAvailable || |
2189 | 7.61k | warnIfBestTransformationNotAvailable) |
2190 | 0 | ctx->debug_level = PJ_LOG_NONE; |
2191 | 7.61k | PJ *P = proj_list_get(ctx, op_list, 0); |
2192 | 7.61k | ctx->debug_level = old_debug_level; |
2193 | 7.61k | assert(P); |
2194 | | |
2195 | 7.61k | if (P != nullptr) { |
2196 | 7.61k | P->errorIfBestTransformationNotAvailable = |
2197 | 7.61k | errorIfBestTransformationNotAvailable; |
2198 | 7.61k | P->warnIfBestTransformationNotAvailable = |
2199 | 7.61k | warnIfBestTransformationNotAvailable; |
2200 | 7.61k | P->skipNonInstantiable = warnIfBestTransformationNotAvailable; |
2201 | 7.61k | } |
2202 | | |
2203 | 7.61k | const bool mayNeedToReRunWithDiscardMissing = |
2204 | 7.61k | (errorIfBestTransformationNotAvailable || |
2205 | 7.61k | warnIfBestTransformationNotAvailable) && |
2206 | 7.61k | !proj_context_is_network_enabled(ctx); |
2207 | 7.61k | int singleOpIsInstanciable = -1; |
2208 | 7.61k | if (P != nullptr && op_count == 1 && mayNeedToReRunWithDiscardMissing) { |
2209 | 0 | singleOpIsInstanciable = proj_coordoperation_is_instantiable(ctx, P); |
2210 | 0 | } |
2211 | | |
2212 | 7.61k | const auto backup_errno = proj_context_errno(ctx); |
2213 | 7.61k | if (P == nullptr || |
2214 | 7.61k | (op_count == 1 && (!mayNeedToReRunWithDiscardMissing || |
2215 | 7.61k | errorIfBestTransformationNotAvailable || |
2216 | 7.61k | singleOpIsInstanciable == static_cast<int>(true)))) { |
2217 | 7.61k | proj_list_destroy(op_list); |
2218 | 7.61k | ctx->forceOver = false; |
2219 | | |
2220 | 7.61k | if (P != nullptr && (errorIfBestTransformationNotAvailable || |
2221 | 7.61k | warnIfBestTransformationNotAvailable)) { |
2222 | 0 | if (singleOpIsInstanciable < 0) { |
2223 | 0 | singleOpIsInstanciable = |
2224 | 0 | proj_coordoperation_is_instantiable(ctx, P); |
2225 | 0 | } |
2226 | 0 | if (!singleOpIsInstanciable) { |
2227 | 0 | warnAboutMissingGrid(P); |
2228 | 0 | if (errorIfBestTransformationNotAvailable) { |
2229 | 0 | proj_destroy(P); |
2230 | 0 | return nullptr; |
2231 | 0 | } |
2232 | 0 | } |
2233 | 0 | } |
2234 | | |
2235 | 7.61k | if (P != nullptr) { |
2236 | 7.61k | P->over = forceOver; |
2237 | 7.61k | } |
2238 | 7.61k | return P; |
2239 | 7.61k | } else if (op_count == 1 && mayNeedToReRunWithDiscardMissing && |
2240 | 0 | !singleOpIsInstanciable) { |
2241 | 0 | warnAboutMissingGrid(P); |
2242 | 0 | } |
2243 | | |
2244 | 0 | if (errorIfBestTransformationNotAvailable || |
2245 | 0 | warnIfBestTransformationNotAvailable) |
2246 | 0 | ctx->debug_level = PJ_LOG_NONE; |
2247 | 0 | auto preparedOpList = |
2248 | 0 | pj_create_prepared_operations(ctx, source_crs, target_crs, op_list); |
2249 | 0 | ctx->debug_level = old_debug_level; |
2250 | |
|
2251 | 0 | ctx->forceOver = false; |
2252 | 0 | proj_list_destroy(op_list); |
2253 | |
|
2254 | 0 | if (preparedOpList.empty()) { |
2255 | 0 | proj_destroy(P); |
2256 | 0 | return nullptr; |
2257 | 0 | } |
2258 | | |
2259 | 0 | bool foundInstanciableAndNonBallpark = false; |
2260 | |
|
2261 | 0 | for (auto &op : preparedOpList) { |
2262 | 0 | op.pj->over = forceOver; |
2263 | 0 | op.pj->errorIfBestTransformationNotAvailable = |
2264 | 0 | errorIfBestTransformationNotAvailable; |
2265 | 0 | op.pj->warnIfBestTransformationNotAvailable = |
2266 | 0 | warnIfBestTransformationNotAvailable; |
2267 | 0 | if (mayNeedToReRunWithDiscardMissing && |
2268 | 0 | !foundInstanciableAndNonBallpark) { |
2269 | 0 | if (!proj_coordoperation_has_ballpark_transformation(op.pj->ctx, |
2270 | 0 | op.pj) && |
2271 | 0 | op.isInstantiable()) { |
2272 | 0 | foundInstanciableAndNonBallpark = true; |
2273 | 0 | } |
2274 | 0 | } |
2275 | 0 | } |
2276 | 0 | if (mayNeedToReRunWithDiscardMissing && !foundInstanciableAndNonBallpark) { |
2277 | | // Re-run proj_create_operations with |
2278 | | // PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID |
2279 | | // Can happen for example for NAD27->NAD83 transformation when we |
2280 | | // have no grid and thus have fallback to Helmert transformation and |
2281 | | // a WGS84 intermediate. |
2282 | 0 | operation_ctx = proj_create_operation_factory_context(ctx, authority); |
2283 | 0 | if (operation_ctx) { |
2284 | 0 | proj_operation_factory_context_set_allow_ballpark_transformations( |
2285 | 0 | ctx, operation_ctx, allowBallparkTransformations); |
2286 | |
|
2287 | 0 | if (accuracy >= 0) { |
2288 | 0 | proj_operation_factory_context_set_desired_accuracy( |
2289 | 0 | ctx, operation_ctx, accuracy); |
2290 | 0 | } |
2291 | |
|
2292 | 0 | if (area && area->bbox_set) { |
2293 | 0 | proj_operation_factory_context_set_area_of_interest( |
2294 | 0 | ctx, operation_ctx, area->west_lon_degree, |
2295 | 0 | area->south_lat_degree, area->east_lon_degree, |
2296 | 0 | area->north_lat_degree); |
2297 | |
|
2298 | 0 | if (!area->name.empty()) { |
2299 | 0 | proj_operation_factory_context_set_area_of_interest_name( |
2300 | 0 | ctx, operation_ctx, area->name.c_str()); |
2301 | 0 | } |
2302 | 0 | } |
2303 | |
|
2304 | 0 | proj_operation_factory_context_set_spatial_criterion( |
2305 | 0 | ctx, operation_ctx, |
2306 | 0 | PROJ_SPATIAL_CRITERION_PARTIAL_INTERSECTION); |
2307 | 0 | proj_operation_factory_context_set_grid_availability_use( |
2308 | 0 | ctx, operation_ctx, |
2309 | 0 | PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID); |
2310 | |
|
2311 | 0 | op_list = proj_create_operations(ctx, source_crs, target_crs, |
2312 | 0 | operation_ctx); |
2313 | 0 | proj_operation_factory_context_destroy(operation_ctx); |
2314 | |
|
2315 | 0 | if (op_list) { |
2316 | 0 | ctx->forceOver = forceOver; |
2317 | 0 | ctx->debug_level = PJ_LOG_NONE; |
2318 | 0 | auto preparedOpList2 = pj_create_prepared_operations( |
2319 | 0 | ctx, source_crs, target_crs, op_list); |
2320 | 0 | ctx->debug_level = old_debug_level; |
2321 | 0 | ctx->forceOver = false; |
2322 | 0 | proj_list_destroy(op_list); |
2323 | |
|
2324 | 0 | if (!preparedOpList2.empty()) { |
2325 | | // Append new lists of operations to previous one |
2326 | 0 | std::vector<PJCoordOperation> newOpList; |
2327 | 0 | for (auto &&op : preparedOpList) { |
2328 | 0 | if (!proj_coordoperation_has_ballpark_transformation( |
2329 | 0 | op.pj->ctx, op.pj)) { |
2330 | 0 | newOpList.emplace_back(std::move(op)); |
2331 | 0 | } |
2332 | 0 | } |
2333 | 0 | for (auto &&op : preparedOpList2) { |
2334 | 0 | op.pj->over = forceOver; |
2335 | 0 | op.pj->errorIfBestTransformationNotAvailable = |
2336 | 0 | errorIfBestTransformationNotAvailable; |
2337 | 0 | op.pj->warnIfBestTransformationNotAvailable = |
2338 | 0 | warnIfBestTransformationNotAvailable; |
2339 | 0 | newOpList.emplace_back(std::move(op)); |
2340 | 0 | } |
2341 | 0 | preparedOpList = std::move(newOpList); |
2342 | 0 | } else { |
2343 | | // We get there in "cs2cs --only-best --no-ballpark |
2344 | | // EPSG:4326+3855 EPSG:4979" use case, where the initial |
2345 | | // create_operations returned 1 operation, and the retry |
2346 | | // with |
2347 | | // PROJ_GRID_AVAILABILITY_DISCARD_OPERATION_IF_MISSING_GRID |
2348 | | // returned 0. |
2349 | 0 | if (op_count == 1 && |
2350 | 0 | errorIfBestTransformationNotAvailable) { |
2351 | 0 | if (singleOpIsInstanciable < 0) { |
2352 | 0 | singleOpIsInstanciable = |
2353 | 0 | proj_coordoperation_is_instantiable(ctx, P); |
2354 | 0 | } |
2355 | 0 | if (!singleOpIsInstanciable) { |
2356 | 0 | proj_destroy(P); |
2357 | 0 | proj_context_errno_set(ctx, backup_errno); |
2358 | 0 | return nullptr; |
2359 | 0 | } |
2360 | 0 | } |
2361 | 0 | } |
2362 | 0 | } |
2363 | 0 | } |
2364 | 0 | } |
2365 | | |
2366 | | // If there's finally juste a single result, return it directly |
2367 | 0 | if (preparedOpList.size() == 1) { |
2368 | 0 | auto retP = preparedOpList[0].pj; |
2369 | 0 | preparedOpList[0].pj = nullptr; |
2370 | 0 | proj_destroy(P); |
2371 | 0 | return retP; |
2372 | 0 | } |
2373 | | |
2374 | 0 | P->alternativeCoordinateOperations = std::move(preparedOpList); |
2375 | | // The returned P is rather dummy |
2376 | 0 | P->descr = "Set of coordinate operations"; |
2377 | 0 | P->over = forceOver; |
2378 | 0 | P->iso_obj = nullptr; |
2379 | 0 | P->fwd = nullptr; |
2380 | 0 | P->inv = nullptr; |
2381 | 0 | P->fwd3d = nullptr; |
2382 | 0 | P->inv3d = nullptr; |
2383 | 0 | P->fwd4d = nullptr; |
2384 | 0 | P->inv4d = nullptr; |
2385 | |
|
2386 | 0 | return P; |
2387 | 0 | } |
2388 | | |
2389 | | /*****************************************************************************/ |
2390 | 1.48M | int proj_errno(const PJ *P) { |
2391 | | /****************************************************************************** |
2392 | | Read an error level from the context of a PJ. |
2393 | | ******************************************************************************/ |
2394 | 1.48M | return proj_context_errno(pj_get_ctx((PJ *)P)); |
2395 | 1.48M | } |
2396 | | |
2397 | | /*****************************************************************************/ |
2398 | 1.54M | int proj_context_errno(PJ_CONTEXT *ctx) { |
2399 | | /****************************************************************************** |
2400 | | Read an error directly from a context, without going through a PJ |
2401 | | belonging to that context. |
2402 | | ******************************************************************************/ |
2403 | 1.54M | if (nullptr == ctx) |
2404 | 0 | ctx = pj_get_default_ctx(); |
2405 | 1.54M | return ctx->last_errno; |
2406 | 1.54M | } |
2407 | | |
2408 | | /*****************************************************************************/ |
2409 | 13.1k | int proj_errno_set(const PJ *P, int err) { |
2410 | | /****************************************************************************** |
2411 | | Set context-errno, bubble it up to the thread local errno, return err |
2412 | | ******************************************************************************/ |
2413 | | /* Use proj_errno_reset to explicitly clear the error status */ |
2414 | 13.1k | if (0 == err) |
2415 | 0 | return 0; |
2416 | | |
2417 | | /* For P==0 err goes to the default context */ |
2418 | 13.1k | proj_context_errno_set(pj_get_ctx((PJ *)P), err); |
2419 | 13.1k | errno = err; |
2420 | | |
2421 | 13.1k | return err; |
2422 | 13.1k | } |
2423 | | |
2424 | | /*****************************************************************************/ |
2425 | 550k | int proj_errno_restore(const PJ *P, int err) { |
2426 | | /****************************************************************************** |
2427 | | Use proj_errno_restore when the current function succeeds, but the |
2428 | | error flag was set on entry, and stored/reset using proj_errno_reset |
2429 | | in order to monitor for new errors. |
2430 | | |
2431 | | See usage example under proj_errno_reset () |
2432 | | ******************************************************************************/ |
2433 | 550k | if (0 == err) |
2434 | 549k | return 0; |
2435 | 1.23k | proj_errno_set(P, err); |
2436 | 1.23k | return 0; |
2437 | 550k | } |
2438 | | |
2439 | | /*****************************************************************************/ |
2440 | 591k | int proj_errno_reset(const PJ *P) { |
2441 | | /****************************************************************************** |
2442 | | Clears errno in the context and thread local levels |
2443 | | through the low level pj_ctx interface. |
2444 | | |
2445 | | Returns the previous value of the errno, for convenient reset/restore |
2446 | | operations: |
2447 | | |
2448 | | int foo (PJ *P) { |
2449 | | // errno may be set on entry, but we need to reset it to be able to |
2450 | | // check for errors from "do_something_with_P(P)" |
2451 | | int last_errno = proj_errno_reset (P); |
2452 | | |
2453 | | // local failure |
2454 | | if (0==P) |
2455 | | return proj_errno_set (P, 42); |
2456 | | |
2457 | | // call to function that may fail |
2458 | | do_something_with_P (P); |
2459 | | |
2460 | | // failure in do_something_with_P? - keep latest error status |
2461 | | if (proj_errno(P)) |
2462 | | return proj_errno (P); |
2463 | | |
2464 | | // success - restore previous error status, return 0 |
2465 | | return proj_errno_restore (P, last_errno); |
2466 | | } |
2467 | | ******************************************************************************/ |
2468 | 591k | int last_errno; |
2469 | 591k | last_errno = proj_errno(P); |
2470 | | |
2471 | 591k | proj_context_errno_set(pj_get_ctx((PJ *)P), 0); |
2472 | 591k | errno = 0; |
2473 | 591k | return last_errno; |
2474 | 591k | } |
2475 | | |
2476 | | /* Create a new context based on the default context */ |
2477 | 14.6k | PJ_CONTEXT *proj_context_create(void) { |
2478 | 14.6k | return new (std::nothrow) pj_ctx(*pj_get_default_ctx()); |
2479 | 14.6k | } |
2480 | | |
2481 | 14.6k | PJ_CONTEXT *proj_context_destroy(PJ_CONTEXT *ctx) { |
2482 | 14.6k | if (nullptr == ctx) |
2483 | 0 | return nullptr; |
2484 | | |
2485 | | /* Trying to free the default context is a no-op (since it is statically |
2486 | | * allocated) */ |
2487 | 14.6k | if (pj_get_default_ctx() == ctx) |
2488 | 0 | return nullptr; |
2489 | | |
2490 | 14.6k | delete ctx; |
2491 | 14.6k | return nullptr; |
2492 | 14.6k | } |
2493 | | |
2494 | | /*****************************************************************************/ |
2495 | 0 | static char *path_append(char *buf, const char *app, size_t *buf_size) { |
2496 | | /****************************************************************************** |
2497 | | Helper for proj_info() below. Append app to buf, separated by a |
2498 | | semicolon. Also handle allocation of longer buffer if needed. |
2499 | | |
2500 | | Returns buffer and adjusts *buf_size through provided pointer arg. |
2501 | | ******************************************************************************/ |
2502 | 0 | char *p; |
2503 | 0 | size_t len, applen = 0, buflen = 0; |
2504 | | #ifdef _WIN32 |
2505 | | const char *delim = ";"; |
2506 | | #else |
2507 | 0 | const char *delim = ":"; |
2508 | 0 | #endif |
2509 | | |
2510 | | /* Nothing to do? */ |
2511 | 0 | if (nullptr == app) |
2512 | 0 | return buf; |
2513 | 0 | applen = strlen(app); |
2514 | 0 | if (0 == applen) |
2515 | 0 | return buf; |
2516 | | |
2517 | | /* Start checking whether buf is long enough */ |
2518 | 0 | if (nullptr != buf) |
2519 | 0 | buflen = strlen(buf); |
2520 | 0 | len = buflen + applen + strlen(delim) + 1; |
2521 | | |
2522 | | /* "pj_realloc", so to speak */ |
2523 | 0 | if (*buf_size < len) { |
2524 | 0 | p = static_cast<char *>(calloc(2 * len, sizeof(char))); |
2525 | 0 | if (nullptr == p) { |
2526 | 0 | free(buf); |
2527 | 0 | return nullptr; |
2528 | 0 | } |
2529 | 0 | *buf_size = 2 * len; |
2530 | 0 | if (buf != nullptr) |
2531 | 0 | strcpy(p, buf); |
2532 | 0 | free(buf); |
2533 | 0 | buf = p; |
2534 | 0 | } |
2535 | 0 | assert(buf); |
2536 | | |
2537 | | /* Only append a delimiter if something's already there */ |
2538 | 0 | if (0 != buflen) |
2539 | 0 | strcat(buf, delim); |
2540 | 0 | strcat(buf, app); |
2541 | 0 | return buf; |
2542 | 0 | } |
2543 | | |
2544 | | static const char *empty = {""}; |
2545 | | static char version[64] = {""}; |
2546 | | static PJ_INFO info = {0, 0, 0, nullptr, nullptr, nullptr, nullptr, 0}; |
2547 | | |
2548 | | /*****************************************************************************/ |
2549 | 0 | PJ_INFO proj_info(void) { |
2550 | | /****************************************************************************** |
2551 | | Basic info about the current instance of the PROJ.4 library. |
2552 | | |
2553 | | Returns PJ_INFO struct. |
2554 | | ******************************************************************************/ |
2555 | 0 | size_t buf_size = 0; |
2556 | 0 | char *buf = nullptr; |
2557 | |
|
2558 | 0 | pj_acquire_lock(); |
2559 | |
|
2560 | 0 | info.major = PROJ_VERSION_MAJOR; |
2561 | 0 | info.minor = PROJ_VERSION_MINOR; |
2562 | 0 | info.patch = PROJ_VERSION_PATCH; |
2563 | | |
2564 | | /* A normal version string is xx.yy.zz which is 8 characters |
2565 | | long and there is room for 64 bytes in the version string. */ |
2566 | 0 | snprintf(version, sizeof(version), "%d.%d.%d", info.major, info.minor, |
2567 | 0 | info.patch); |
2568 | |
|
2569 | 0 | info.version = version; |
2570 | 0 | info.release = pj_get_release(); |
2571 | | |
2572 | | /* build search path string */ |
2573 | 0 | auto ctx = pj_get_default_ctx(); |
2574 | 0 | if (ctx->search_paths.empty()) { |
2575 | 0 | const auto searchpaths = pj_get_default_searchpaths(ctx); |
2576 | 0 | for (const auto &path : searchpaths) { |
2577 | 0 | buf = path_append(buf, path.c_str(), &buf_size); |
2578 | 0 | } |
2579 | 0 | } else { |
2580 | 0 | for (const auto &path : ctx->search_paths) { |
2581 | 0 | buf = path_append(buf, path.c_str(), &buf_size); |
2582 | 0 | } |
2583 | 0 | } |
2584 | |
|
2585 | 0 | if (info.searchpath != empty) |
2586 | 0 | free(const_cast<char *>(info.searchpath)); |
2587 | 0 | info.searchpath = buf ? buf : empty; |
2588 | |
|
2589 | 0 | info.paths = ctx->c_compat_paths; |
2590 | 0 | info.path_count = static_cast<int>(ctx->search_paths.size()); |
2591 | |
|
2592 | 0 | pj_release_lock(); |
2593 | 0 | return info; |
2594 | 0 | } |
2595 | | |
2596 | | /*****************************************************************************/ |
2597 | 0 | PJ_PROJ_INFO proj_pj_info(PJ *P) { |
2598 | | /****************************************************************************** |
2599 | | Basic info about a particular instance of a projection object. |
2600 | | |
2601 | | Returns PJ_PROJ_INFO struct. |
2602 | | ******************************************************************************/ |
2603 | 0 | PJ_PROJ_INFO pjinfo; |
2604 | 0 | char *def; |
2605 | |
|
2606 | 0 | memset(&pjinfo, 0, sizeof(PJ_PROJ_INFO)); |
2607 | |
|
2608 | 0 | pjinfo.accuracy = -1.0; |
2609 | |
|
2610 | 0 | if (nullptr == P) |
2611 | 0 | return pjinfo; |
2612 | | |
2613 | | /* coordinate operation description */ |
2614 | 0 | if (!P->alternativeCoordinateOperations.empty()) { |
2615 | 0 | if (P->iCurCoordOp >= 0) { |
2616 | 0 | P = P->alternativeCoordinateOperations[P->iCurCoordOp].pj; |
2617 | 0 | } else { |
2618 | 0 | PJ *candidateOp = nullptr; |
2619 | | // If there's just a single coordinate operation which is |
2620 | | // instanciable, use it. |
2621 | 0 | for (const auto &op : P->alternativeCoordinateOperations) { |
2622 | 0 | if (op.isInstantiable()) { |
2623 | 0 | if (candidateOp == nullptr) { |
2624 | 0 | candidateOp = op.pj; |
2625 | 0 | } else { |
2626 | 0 | candidateOp = nullptr; |
2627 | 0 | break; |
2628 | 0 | } |
2629 | 0 | } |
2630 | 0 | } |
2631 | 0 | if (candidateOp) { |
2632 | 0 | P = candidateOp; |
2633 | 0 | } else { |
2634 | 0 | pjinfo.id = "unknown"; |
2635 | 0 | pjinfo.description = "unavailable until proj_trans is called"; |
2636 | 0 | pjinfo.definition = "unavailable until proj_trans is called"; |
2637 | 0 | return pjinfo; |
2638 | 0 | } |
2639 | 0 | } |
2640 | 0 | } |
2641 | | |
2642 | | /* projection id */ |
2643 | 0 | if (pj_param(P->ctx, P->params, "tproj").i) |
2644 | 0 | pjinfo.id = pj_param(P->ctx, P->params, "sproj").s; |
2645 | |
|
2646 | 0 | pjinfo.description = P->descr; |
2647 | 0 | if (P->iso_obj) { |
2648 | 0 | auto identifiedObj = |
2649 | 0 | dynamic_cast<NS_PROJ::common::IdentifiedObject *>(P->iso_obj.get()); |
2650 | 0 | if (identifiedObj) { |
2651 | 0 | pjinfo.description = identifiedObj->nameStr().c_str(); |
2652 | 0 | } |
2653 | 0 | } |
2654 | | |
2655 | | // accuracy |
2656 | 0 | if (P->iso_obj) { |
2657 | 0 | auto conv = dynamic_cast<const NS_PROJ::operation::Conversion *>( |
2658 | 0 | P->iso_obj.get()); |
2659 | 0 | if (conv) { |
2660 | 0 | pjinfo.accuracy = 0.0; |
2661 | 0 | } else { |
2662 | 0 | auto op = |
2663 | 0 | dynamic_cast<const NS_PROJ::operation::CoordinateOperation *>( |
2664 | 0 | P->iso_obj.get()); |
2665 | 0 | if (op) { |
2666 | 0 | const auto &accuracies = op->coordinateOperationAccuracies(); |
2667 | 0 | if (!accuracies.empty()) { |
2668 | 0 | try { |
2669 | 0 | pjinfo.accuracy = std::stod(accuracies[0]->value()); |
2670 | 0 | } catch (const std::exception &) { |
2671 | 0 | } |
2672 | 0 | } |
2673 | 0 | } |
2674 | 0 | } |
2675 | 0 | } |
2676 | | |
2677 | | /* projection definition */ |
2678 | 0 | if (P->def_full) |
2679 | 0 | def = P->def_full; |
2680 | 0 | else |
2681 | 0 | def = pj_get_def(P, 0); /* pj_get_def takes a non-const PJ pointer */ |
2682 | 0 | if (nullptr == def) |
2683 | 0 | pjinfo.definition = empty; |
2684 | 0 | else |
2685 | 0 | pjinfo.definition = pj_shrink(def); |
2686 | | /* Make proj_destroy clean this up eventually */ |
2687 | 0 | P->def_full = def; |
2688 | |
|
2689 | 0 | pjinfo.has_inverse = pj_has_inverse(P); |
2690 | 0 | return pjinfo; |
2691 | 0 | } |
2692 | | |
2693 | | /*****************************************************************************/ |
2694 | 0 | PJ_GRID_INFO proj_grid_info(const char *gridname) { |
2695 | | /****************************************************************************** |
2696 | | Information about a named datum grid. |
2697 | | |
2698 | | Returns PJ_GRID_INFO struct. |
2699 | | ******************************************************************************/ |
2700 | 0 | PJ_GRID_INFO grinfo; |
2701 | | |
2702 | | /*PJ_CONTEXT *ctx = proj_context_create(); */ |
2703 | 0 | PJ_CONTEXT *ctx = pj_get_default_ctx(); |
2704 | 0 | memset(&grinfo, 0, sizeof(PJ_GRID_INFO)); |
2705 | |
|
2706 | 0 | const auto fillGridInfo = [&grinfo, ctx, |
2707 | 0 | gridname](const NS_PROJ::Grid &grid, |
2708 | 0 | const std::string &format) { |
2709 | 0 | const auto &extent = grid.extentAndRes(); |
2710 | | |
2711 | | /* name of grid */ |
2712 | 0 | strncpy(grinfo.gridname, gridname, sizeof(grinfo.gridname) - 1); |
2713 | | |
2714 | | /* full path of grid */ |
2715 | 0 | if (!pj_find_file(ctx, gridname, grinfo.filename, |
2716 | 0 | sizeof(grinfo.filename) - 1)) { |
2717 | | // Can happen when using a remote grid |
2718 | 0 | grinfo.filename[0] = 0; |
2719 | 0 | } |
2720 | | |
2721 | | /* grid format */ |
2722 | 0 | strncpy(grinfo.format, format.c_str(), sizeof(grinfo.format) - 1); |
2723 | | |
2724 | | /* grid size */ |
2725 | 0 | grinfo.n_lon = grid.width(); |
2726 | 0 | grinfo.n_lat = grid.height(); |
2727 | | |
2728 | | /* cell size */ |
2729 | 0 | grinfo.cs_lon = extent.resX; |
2730 | 0 | grinfo.cs_lat = extent.resY; |
2731 | | |
2732 | | /* bounds of grid */ |
2733 | 0 | grinfo.lowerleft.lam = extent.west; |
2734 | 0 | grinfo.lowerleft.phi = extent.south; |
2735 | 0 | grinfo.upperright.lam = extent.east; |
2736 | 0 | grinfo.upperright.phi = extent.north; |
2737 | 0 | }; |
2738 | |
|
2739 | 0 | { |
2740 | 0 | const auto gridSet = NS_PROJ::VerticalShiftGridSet::open(ctx, gridname); |
2741 | 0 | if (gridSet) { |
2742 | 0 | const auto &grids = gridSet->grids(); |
2743 | 0 | if (!grids.empty()) { |
2744 | 0 | const auto &grid = grids.front(); |
2745 | 0 | fillGridInfo(*grid, gridSet->format()); |
2746 | 0 | return grinfo; |
2747 | 0 | } |
2748 | 0 | } |
2749 | 0 | } |
2750 | | |
2751 | 0 | { |
2752 | 0 | const auto gridSet = |
2753 | 0 | NS_PROJ::HorizontalShiftGridSet::open(ctx, gridname); |
2754 | 0 | if (gridSet) { |
2755 | 0 | const auto &grids = gridSet->grids(); |
2756 | 0 | if (!grids.empty()) { |
2757 | 0 | const auto &grid = grids.front(); |
2758 | 0 | fillGridInfo(*grid, gridSet->format()); |
2759 | 0 | return grinfo; |
2760 | 0 | } |
2761 | 0 | } |
2762 | 0 | } |
2763 | 0 | strcpy(grinfo.format, "missing"); |
2764 | 0 | return grinfo; |
2765 | 0 | } |
2766 | | |
2767 | | /*****************************************************************************/ |
2768 | 0 | PJ_INIT_INFO proj_init_info(const char *initname) { |
2769 | | /****************************************************************************** |
2770 | | Information about a named init file. |
2771 | | |
2772 | | Maximum length of initname is 64. |
2773 | | |
2774 | | Returns PJ_INIT_INFO struct. |
2775 | | |
2776 | | If the init file is not found all members of the return struct are set |
2777 | | to the empty string. |
2778 | | |
2779 | | If the init file is found, but the metadata is missing, the value is |
2780 | | set to "Unknown". |
2781 | | ******************************************************************************/ |
2782 | 0 | int file_found; |
2783 | 0 | char param[80], key[74]; |
2784 | 0 | paralist *start, *next; |
2785 | 0 | PJ_INIT_INFO ininfo; |
2786 | 0 | PJ_CONTEXT *ctx = pj_get_default_ctx(); |
2787 | |
|
2788 | 0 | memset(&ininfo, 0, sizeof(PJ_INIT_INFO)); |
2789 | |
|
2790 | 0 | file_found = |
2791 | 0 | pj_find_file(ctx, initname, ininfo.filename, sizeof(ininfo.filename)); |
2792 | 0 | if (!file_found || strlen(initname) > 64) { |
2793 | 0 | if (strcmp(initname, "epsg") == 0 || strcmp(initname, "EPSG") == 0) { |
2794 | 0 | const char *val; |
2795 | |
|
2796 | 0 | proj_context_errno_set(ctx, 0); |
2797 | |
|
2798 | 0 | strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1); |
2799 | 0 | strcpy(ininfo.origin, "EPSG"); |
2800 | 0 | val = proj_context_get_database_metadata(ctx, "EPSG.VERSION"); |
2801 | 0 | if (val) { |
2802 | 0 | strncpy(ininfo.version, val, sizeof(ininfo.version) - 1); |
2803 | 0 | } |
2804 | 0 | val = proj_context_get_database_metadata(ctx, "EPSG.DATE"); |
2805 | 0 | if (val) { |
2806 | 0 | strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1); |
2807 | 0 | } |
2808 | 0 | return ininfo; |
2809 | 0 | } |
2810 | | |
2811 | 0 | if (strcmp(initname, "IGNF") == 0) { |
2812 | 0 | const char *val; |
2813 | |
|
2814 | 0 | proj_context_errno_set(ctx, 0); |
2815 | |
|
2816 | 0 | strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1); |
2817 | 0 | strcpy(ininfo.origin, "IGNF"); |
2818 | 0 | val = proj_context_get_database_metadata(ctx, "IGNF.VERSION"); |
2819 | 0 | if (val) { |
2820 | 0 | strncpy(ininfo.version, val, sizeof(ininfo.version) - 1); |
2821 | 0 | } |
2822 | 0 | val = proj_context_get_database_metadata(ctx, "IGNF.DATE"); |
2823 | 0 | if (val) { |
2824 | 0 | strncpy(ininfo.lastupdate, val, sizeof(ininfo.lastupdate) - 1); |
2825 | 0 | } |
2826 | 0 | return ininfo; |
2827 | 0 | } |
2828 | | |
2829 | 0 | return ininfo; |
2830 | 0 | } |
2831 | | |
2832 | | /* The initial memset (0) makes strncpy safe here */ |
2833 | 0 | strncpy(ininfo.name, initname, sizeof(ininfo.name) - 1); |
2834 | 0 | strcpy(ininfo.origin, "Unknown"); |
2835 | 0 | strcpy(ininfo.version, "Unknown"); |
2836 | 0 | strcpy(ininfo.lastupdate, "Unknown"); |
2837 | |
|
2838 | 0 | strncpy(key, initname, 64); /* make room for ":metadata\0" at the end */ |
2839 | 0 | key[64] = 0; |
2840 | 0 | memcpy(key + strlen(key), ":metadata", 9 + 1); |
2841 | 0 | strcpy(param, "+init="); |
2842 | | /* The +strlen(param) avoids a cppcheck false positive warning */ |
2843 | 0 | strncat(param + strlen(param), key, sizeof(param) - 1 - strlen(param)); |
2844 | |
|
2845 | 0 | start = pj_mkparam(param); |
2846 | 0 | pj_expand_init(ctx, start); |
2847 | |
|
2848 | 0 | if (pj_param(ctx, start, "tversion").i) |
2849 | 0 | strncpy(ininfo.version, pj_param(ctx, start, "sversion").s, |
2850 | 0 | sizeof(ininfo.version) - 1); |
2851 | |
|
2852 | 0 | if (pj_param(ctx, start, "torigin").i) |
2853 | 0 | strncpy(ininfo.origin, pj_param(ctx, start, "sorigin").s, |
2854 | 0 | sizeof(ininfo.origin) - 1); |
2855 | |
|
2856 | 0 | if (pj_param(ctx, start, "tlastupdate").i) |
2857 | 0 | strncpy(ininfo.lastupdate, pj_param(ctx, start, "slastupdate").s, |
2858 | 0 | sizeof(ininfo.lastupdate) - 1); |
2859 | |
|
2860 | 0 | for (; start; start = next) { |
2861 | 0 | next = start->next; |
2862 | 0 | free(start); |
2863 | 0 | } |
2864 | |
|
2865 | 0 | return ininfo; |
2866 | 0 | } |
2867 | | |
2868 | | /*****************************************************************************/ |
2869 | 0 | PJ_FACTORS proj_factors(PJ *P, PJ_COORD lp) { |
2870 | | /****************************************************************************** |
2871 | | Cartographic characteristics at point lp. |
2872 | | |
2873 | | Characteristics include meridian, parallel and areal scales, angular |
2874 | | distortion, meridian/parallel, meridian convergence and scale error. |
2875 | | |
2876 | | returns PJ_FACTORS. If unsuccessful, error number is set and the |
2877 | | struct returned contains NULL data. |
2878 | | ******************************************************************************/ |
2879 | 0 | PJ_FACTORS factors = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; |
2880 | 0 | struct FACTORS f; |
2881 | |
|
2882 | 0 | if (nullptr == P) |
2883 | 0 | return factors; |
2884 | | |
2885 | 0 | const auto type = proj_get_type(P); |
2886 | |
|
2887 | 0 | if (type == PJ_TYPE_COMPOUND_CRS) { |
2888 | 0 | auto ctx = P->ctx; |
2889 | 0 | auto horiz = proj_crs_get_sub_crs(ctx, P, 0); |
2890 | 0 | if (horiz) { |
2891 | 0 | auto ret = proj_factors(horiz, lp); |
2892 | 0 | proj_destroy(horiz); |
2893 | 0 | return ret; |
2894 | 0 | } |
2895 | 0 | } |
2896 | | |
2897 | 0 | if (type == PJ_TYPE_PROJECTED_CRS) { |
2898 | | // If it is a projected CRS, then compute the factors on the conversion |
2899 | | // associated to it. We need to start from a temporary geographic CRS |
2900 | | // using the same datum as the one of the projected CRS, and with |
2901 | | // input coordinates being in longitude, latitude order in radian, |
2902 | | // to be consistent with the expectations of the lp input parameter. |
2903 | | // We also need to create a modified projected CRS with a normalized |
2904 | | // easting/northing axis order in metre, so the resulting operation is |
2905 | | // just a single step pipeline with no axisswap or unitconvert steps. |
2906 | |
|
2907 | 0 | auto ctx = P->ctx; |
2908 | 0 | auto geodetic_crs = proj_get_source_crs(ctx, P); |
2909 | 0 | assert(geodetic_crs); |
2910 | 0 | auto pm = proj_get_prime_meridian(ctx, geodetic_crs); |
2911 | 0 | double pm_longitude = 0; |
2912 | 0 | proj_prime_meridian_get_parameters(ctx, pm, &pm_longitude, nullptr, |
2913 | 0 | nullptr); |
2914 | 0 | proj_destroy(pm); |
2915 | 0 | PJ *geogCRSNormalized; |
2916 | 0 | auto cs = proj_create_ellipsoidal_2D_cs( |
2917 | 0 | ctx, PJ_ELLPS2D_LONGITUDE_LATITUDE, "Radian", 1.0); |
2918 | 0 | if (pm_longitude != 0) { |
2919 | 0 | auto ellipsoid = proj_get_ellipsoid(ctx, geodetic_crs); |
2920 | 0 | double semi_major_metre = 0; |
2921 | 0 | double inv_flattening = 0; |
2922 | 0 | proj_ellipsoid_get_parameters(ctx, ellipsoid, &semi_major_metre, |
2923 | 0 | nullptr, nullptr, &inv_flattening); |
2924 | 0 | geogCRSNormalized = proj_create_geographic_crs( |
2925 | 0 | ctx, "unname crs", "unnamed datum", proj_get_name(ellipsoid), |
2926 | 0 | semi_major_metre, inv_flattening, "reference prime meridian", 0, |
2927 | 0 | nullptr, 0, cs); |
2928 | 0 | proj_destroy(ellipsoid); |
2929 | 0 | } else { |
2930 | 0 | auto datum = proj_crs_get_datum(ctx, geodetic_crs); |
2931 | 0 | auto datum_ensemble = |
2932 | 0 | proj_crs_get_datum_ensemble(ctx, geodetic_crs); |
2933 | 0 | geogCRSNormalized = proj_create_geographic_crs_from_datum( |
2934 | 0 | ctx, "unnamed crs", datum ? datum : datum_ensemble, cs); |
2935 | 0 | proj_destroy(datum); |
2936 | 0 | proj_destroy(datum_ensemble); |
2937 | 0 | } |
2938 | 0 | proj_destroy(cs); |
2939 | 0 | auto conversion = proj_crs_get_coordoperation(ctx, P); |
2940 | 0 | auto projCS = proj_create_cartesian_2D_cs( |
2941 | 0 | ctx, PJ_CART2D_EASTING_NORTHING, "metre", 1.0); |
2942 | 0 | auto projCRSNormalized = proj_create_projected_crs( |
2943 | 0 | ctx, nullptr, geodetic_crs, conversion, projCS); |
2944 | 0 | assert(projCRSNormalized); |
2945 | 0 | proj_destroy(geodetic_crs); |
2946 | 0 | proj_destroy(conversion); |
2947 | 0 | proj_destroy(projCS); |
2948 | 0 | auto newOp = proj_create_crs_to_crs_from_pj( |
2949 | 0 | ctx, geogCRSNormalized, projCRSNormalized, nullptr, nullptr); |
2950 | 0 | proj_destroy(geogCRSNormalized); |
2951 | 0 | proj_destroy(projCRSNormalized); |
2952 | 0 | assert(newOp); |
2953 | | // For debugging: |
2954 | | // printf("%s\n", proj_as_proj_string(ctx, newOp, PJ_PROJ_5, nullptr)); |
2955 | 0 | auto ret = proj_factors(newOp, lp); |
2956 | 0 | proj_destroy(newOp); |
2957 | 0 | return ret; |
2958 | 0 | } |
2959 | | |
2960 | 0 | if (type != PJ_TYPE_CONVERSION && type != PJ_TYPE_TRANSFORMATION && |
2961 | 0 | type != PJ_TYPE_CONCATENATED_OPERATION && |
2962 | 0 | type != PJ_TYPE_OTHER_COORDINATE_OPERATION) { |
2963 | 0 | proj_log_error(P, _("Invalid type for P object")); |
2964 | 0 | proj_errno_set(P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
2965 | 0 | return factors; |
2966 | 0 | } |
2967 | | |
2968 | 0 | if (pj_factors(lp.lp, P, 0.0, &f)) |
2969 | 0 | return factors; |
2970 | | |
2971 | 0 | factors.meridional_scale = f.h; |
2972 | 0 | factors.parallel_scale = f.k; |
2973 | 0 | factors.areal_scale = f.s; |
2974 | |
|
2975 | 0 | factors.angular_distortion = f.omega; |
2976 | 0 | factors.meridian_parallel_angle = f.thetap; |
2977 | 0 | factors.meridian_convergence = f.conv; |
2978 | |
|
2979 | 0 | factors.tissot_semimajor = f.a; |
2980 | 0 | factors.tissot_semiminor = f.b; |
2981 | | |
2982 | | /* Raw derivatives, for completeness's sake */ |
2983 | 0 | factors.dx_dlam = f.der.x_l; |
2984 | 0 | factors.dx_dphi = f.der.x_p; |
2985 | 0 | factors.dy_dlam = f.der.y_l; |
2986 | 0 | factors.dy_dphi = f.der.y_p; |
2987 | |
|
2988 | 0 | return factors; |
2989 | 0 | } |
2990 | | |
2991 | | // --------------------------------------------------------------------------- |
2992 | | |
2993 | | //! @cond Doxygen_Suppress |
2994 | | |
2995 | | // Returns true if the passed operation uses NADCON5 grids for NAD83 to |
2996 | | // NAD83(HARN) |
2997 | 0 | static bool isSpecialCaseForNAD83_to_NAD83HARN(const std::string &opName) { |
2998 | 0 | return opName.find("NAD83 to NAD83(HARN) (47)") != std::string::npos || |
2999 | 0 | opName.find("NAD83 to NAD83(HARN) (48)") != std::string::npos || |
3000 | 0 | opName.find("NAD83 to NAD83(HARN) (49)") != std::string::npos || |
3001 | 0 | opName.find("NAD83 to NAD83(HARN) (50)") != std::string::npos; |
3002 | 0 | } |
3003 | | |
3004 | | // Returns true if the passed operation uses "GDA94 to WGS 84 (1)", which |
3005 | | // is the null transformation |
3006 | 0 | static bool isSpecialCaseForGDA94_to_WGS84(const std::string &opName) { |
3007 | 0 | return opName.find("GDA94 to WGS 84 (1)") != std::string::npos; |
3008 | 0 | } |
3009 | | |
3010 | | // Returns true if the passed operation uses "GDA2020 to WGS 84 (2)", which |
3011 | | // is the null transformation |
3012 | 0 | static bool isSpecialCaseForWGS84_to_GDA2020(const std::string &opName) { |
3013 | 0 | return opName.find("GDA2020 to WGS 84 (2)") != std::string::npos; |
3014 | 0 | } |
3015 | | |
3016 | | PJCoordOperation::PJCoordOperation( |
3017 | | int idxInOriginalListIn, double minxSrcIn, double minySrcIn, |
3018 | | double maxxSrcIn, double maxySrcIn, double minxDstIn, double minyDstIn, |
3019 | | double maxxDstIn, double maxyDstIn, PJ *pjIn, const std::string &nameIn, |
3020 | | double accuracyIn, double pseudoAreaIn, const char *areaNameIn, |
3021 | | const PJ *pjSrcGeocentricToLonLatIn, const PJ *pjDstGeocentricToLonLatIn) |
3022 | | : idxInOriginalList(idxInOriginalListIn), minxSrc(minxSrcIn), |
3023 | | minySrc(minySrcIn), maxxSrc(maxxSrcIn), maxySrc(maxySrcIn), |
3024 | | minxDst(minxDstIn), minyDst(minyDstIn), maxxDst(maxxDstIn), |
3025 | | maxyDst(maxyDstIn), pj(pjIn), name(nameIn), accuracy(accuracyIn), |
3026 | | pseudoArea(pseudoAreaIn), areaName(areaNameIn ? areaNameIn : ""), |
3027 | | isOffshore(areaName.find("- offshore") != std::string::npos), |
3028 | | isUnknownAreaName(areaName.empty() || areaName == "unknown"), |
3029 | | isPriorityOp(isSpecialCaseForNAD83_to_NAD83HARN(name) || |
3030 | | isSpecialCaseForGDA94_to_WGS84(name) || |
3031 | | isSpecialCaseForWGS84_to_GDA2020(name)), |
3032 | | pjSrcGeocentricToLonLat(pjSrcGeocentricToLonLatIn |
3033 | | ? proj_clone(pjSrcGeocentricToLonLatIn->ctx, |
3034 | | pjSrcGeocentricToLonLatIn) |
3035 | | : nullptr), |
3036 | | pjDstGeocentricToLonLat(pjDstGeocentricToLonLatIn |
3037 | | ? proj_clone(pjDstGeocentricToLonLatIn->ctx, |
3038 | | pjDstGeocentricToLonLatIn) |
3039 | 0 | : nullptr) { |
3040 | 0 | const auto IsLonLatOrLatLon = [](const PJ *crs, bool &isLonLatDegreeOut, |
3041 | 0 | bool &isLatLonDegreeOut) { |
3042 | 0 | const auto eType = proj_get_type(crs); |
3043 | 0 | if (eType == PJ_TYPE_GEOGRAPHIC_2D_CRS || |
3044 | 0 | eType == PJ_TYPE_GEOGRAPHIC_3D_CRS) { |
3045 | 0 | const auto cs = proj_crs_get_coordinate_system(crs->ctx, crs); |
3046 | 0 | const char *direction = ""; |
3047 | 0 | double conv_factor = 0; |
3048 | 0 | constexpr double EPS = 1e-14; |
3049 | 0 | if (proj_cs_get_axis_info(crs->ctx, cs, 0, nullptr, nullptr, |
3050 | 0 | &direction, &conv_factor, nullptr, |
3051 | 0 | nullptr, nullptr) && |
3052 | 0 | ci_equal(direction, "East")) { |
3053 | 0 | isLonLatDegreeOut = fabs(conv_factor - M_PI / 180) < EPS; |
3054 | 0 | } else if (proj_cs_get_axis_info(crs->ctx, cs, 1, nullptr, nullptr, |
3055 | 0 | &direction, &conv_factor, nullptr, |
3056 | 0 | nullptr, nullptr) && |
3057 | 0 | ci_equal(direction, "East")) { |
3058 | 0 | isLatLonDegreeOut = fabs(conv_factor - M_PI / 180) < EPS; |
3059 | 0 | } |
3060 | 0 | proj_destroy(cs); |
3061 | 0 | } |
3062 | 0 | }; |
3063 | |
|
3064 | 0 | const auto source = proj_get_source_crs(pj->ctx, pj); |
3065 | 0 | if (source) { |
3066 | 0 | IsLonLatOrLatLon(source, srcIsLonLatDegree, srcIsLatLonDegree); |
3067 | 0 | proj_destroy(source); |
3068 | 0 | } |
3069 | |
|
3070 | 0 | const auto target = proj_get_target_crs(pj->ctx, pj); |
3071 | 0 | if (target) { |
3072 | 0 | IsLonLatOrLatLon(target, dstIsLonLatDegree, dstIsLatLonDegree); |
3073 | 0 | proj_destroy(target); |
3074 | 0 | } |
3075 | 0 | } |
3076 | | |
3077 | | //! @endcond |