/src/proj/src/transformations/gridshift.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * Project: PROJ |
3 | | * Purpose: Generic grid shifting, in particular Geographic 3D offsets |
4 | | * Author: Even Rouault, <even.rouault at spatialys.com> |
5 | | * |
6 | | ****************************************************************************** |
7 | | * Copyright (c) 2022, Even Rouault, <even.rouault at spatialys.com> |
8 | | * |
9 | | * Permission is hereby granted, free of charge, to any person obtaining a |
10 | | * copy of this software and associated documentation files (the "Software"), |
11 | | * to deal in the Software without restriction, including without limitation |
12 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
13 | | * and/or sell copies of the Software, and to permit persons to whom the |
14 | | * Software is furnished to do so, subject to the following conditions: |
15 | | * |
16 | | * The above copyright notice and this permission notice shall be included |
17 | | * in all copies or substantial portions of the Software. |
18 | | * |
19 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
20 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
21 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
22 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
23 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
24 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
25 | | * DEALINGS IN THE SOFTWARE. |
26 | | *****************************************************************************/ |
27 | | |
28 | | #ifndef FROM_PROJ_CPP |
29 | | #define FROM_PROJ_CPP |
30 | | #endif |
31 | | |
32 | | #include <errno.h> |
33 | | #include <mutex> |
34 | | #include <stddef.h> |
35 | | #include <string.h> |
36 | | #include <time.h> |
37 | | |
38 | | #include "grids.hpp" |
39 | | #include "proj/internal/internal.hpp" |
40 | | #include "proj_internal.h" |
41 | | |
42 | | #include <algorithm> |
43 | | #include <cmath> |
44 | | #include <limits> |
45 | | #include <map> |
46 | | #include <utility> |
47 | | |
48 | | PROJ_HEAD(gridshift, "Generic grid shift"); |
49 | | |
50 | | static std::mutex gMutex{}; |
51 | | // Map of (name, isProjected) |
52 | | static std::map<std::string, bool> gKnownGrids{}; |
53 | | |
54 | | using namespace NS_PROJ; |
55 | | |
56 | | namespace { // anonymous namespace |
57 | | |
58 | | struct IXY { |
59 | | int32_t x, y; |
60 | | |
61 | 0 | inline bool operator!=(const IXY &other) const { |
62 | 0 | return x != other.x || y != other.y; |
63 | 0 | } |
64 | | }; |
65 | | |
66 | | struct GridInfo { |
67 | | int idxSampleX = -1; |
68 | | int idxSampleY = -1; |
69 | | int idxSampleZ = -1; |
70 | | bool eastingNorthingOffset = false; |
71 | | bool bilinearInterpolation = true; |
72 | | std::vector<float> shifts{}; |
73 | | bool swapXYInRes = false; |
74 | | std::vector<int> idxSampleXYZ{-1, -1, -1}; |
75 | | IXY lastIdxXY = IXY{-1, -1}; |
76 | | }; |
77 | | |
78 | | // --------------------------------------------------------------------------- |
79 | | |
80 | | struct gridshiftData { |
81 | | ListOfGenericGrids m_grids{}; |
82 | | bool m_defer_grid_opening = false; |
83 | | int m_error_code_in_defer_grid_opening = 0; |
84 | | bool m_bHasHorizontalOffset = false; |
85 | | bool m_bHasGeographic3DOffset = false; |
86 | | bool m_bHasEllipsoidalHeightOffset = false; |
87 | | bool m_bHasVerticalToVertical = false; |
88 | | bool m_bHasGeographicToVertical = false; |
89 | | bool m_mainGridTypeIsGeographic3DOffset = false; |
90 | | bool m_skip_z_transform = false; |
91 | | std::string m_mainGridType{}; |
92 | | std::string m_auxGridType{}; |
93 | | std::string m_interpolation{}; |
94 | | std::map<const GenericShiftGrid *, GridInfo> m_cacheGridInfo{}; |
95 | | |
96 | | //! Offset in X to add in the forward direction, after the correction |
97 | | // has been applied. (and reciprocally to subtract in the inverse direction |
98 | | // before reading the grid). Used typically for the S-JTSK --> S-JTSK/05 |
99 | | // grid |
100 | | double m_offsetX = 0; |
101 | | |
102 | | //! Offset in Y to add in the forward direction, after the correction |
103 | | // has been applied. (and reciprocally to subtract in the inverse direction |
104 | | // before reading the grid). Used typically for the S-JTSK --> S-JTSK/05 |
105 | | // grid |
106 | | double m_offsetY = 0; |
107 | | |
108 | | bool checkGridTypes(PJ *P, bool &isProjectedCoord); |
109 | | bool loadGridsIfNeeded(PJ *P); |
110 | | const GenericShiftGrid *findGrid(const std::string &type, |
111 | | const PJ_XYZ &input, |
112 | | GenericShiftGridSet *&gridSetOut) const; |
113 | | PJ_XYZ grid_interpolate(PJ_CONTEXT *ctx, const std::string &type, PJ_XY xy, |
114 | | const GenericShiftGrid *grid, |
115 | | bool &biquadraticInterpolationOut); |
116 | | PJ_XYZ grid_apply_internal(PJ_CONTEXT *ctx, const std::string &type, |
117 | | bool isVerticalOnly, const PJ_XYZ in, |
118 | | PJ_DIRECTION direction, |
119 | | const GenericShiftGrid *grid, |
120 | | GenericShiftGridSet *gridset, bool &shouldRetry); |
121 | | |
122 | | PJ_XYZ apply(PJ *P, PJ_DIRECTION dir, PJ_XYZ xyz); |
123 | | }; |
124 | | |
125 | | // --------------------------------------------------------------------------- |
126 | | |
127 | 169 | bool gridshiftData::checkGridTypes(PJ *P, bool &isProjectedCoord) { |
128 | 169 | std::string offsetX, offsetY; |
129 | 169 | int gridCount = 0; |
130 | 169 | isProjectedCoord = false; |
131 | 169 | for (const auto &gridset : m_grids) { |
132 | 11 | for (const auto &grid : gridset->grids()) { |
133 | 11 | ++gridCount; |
134 | 11 | const auto &type = grid->metadataItem("TYPE"); |
135 | 11 | if (type == "HORIZONTAL_OFFSET") { |
136 | 0 | m_bHasHorizontalOffset = true; |
137 | 0 | if (offsetX.empty()) { |
138 | 0 | offsetX = grid->metadataItem("constant_offset", 0); |
139 | 0 | } |
140 | 0 | if (offsetY.empty()) { |
141 | 0 | offsetY = grid->metadataItem("constant_offset", 1); |
142 | 0 | } |
143 | 11 | } else if (type == "GEOGRAPHIC_3D_OFFSET") |
144 | 0 | m_bHasGeographic3DOffset = true; |
145 | 11 | else if (type == "ELLIPSOIDAL_HEIGHT_OFFSET") |
146 | 0 | m_bHasEllipsoidalHeightOffset = true; |
147 | 11 | else if (type == "VERTICAL_OFFSET_VERTICAL_TO_VERTICAL") |
148 | 0 | m_bHasVerticalToVertical = true; |
149 | 11 | else if (type == "VERTICAL_OFFSET_GEOGRAPHIC_TO_VERTICAL") |
150 | 0 | m_bHasGeographicToVertical = true; |
151 | 11 | else if (type.empty()) { |
152 | 11 | proj_log_error(P, _("Missing TYPE metadata item in grid(s).")); |
153 | 11 | return false; |
154 | 11 | } else { |
155 | 0 | proj_log_error( |
156 | 0 | P, _("Unhandled value for TYPE metadata item in grid(s).")); |
157 | 0 | return false; |
158 | 0 | } |
159 | | |
160 | 0 | isProjectedCoord = !grid->extentAndRes().isGeographic; |
161 | 0 | } |
162 | 11 | } |
163 | | |
164 | 158 | if (!offsetX.empty() || !offsetY.empty()) { |
165 | 0 | if (gridCount > 1) { |
166 | | // Makes life easier... |
167 | 0 | proj_log_error(P, _("Shift offset found in one grid. Only one grid " |
168 | 0 | "with shift offset is supported at a time.")); |
169 | 0 | return false; |
170 | 0 | } |
171 | 0 | try { |
172 | 0 | m_offsetX = NS_PROJ::internal::c_locale_stod(offsetX); |
173 | 0 | } catch (const std::exception &) { |
174 | 0 | proj_log_error(P, _("Invalid offset value")); |
175 | 0 | return false; |
176 | 0 | } |
177 | 0 | try { |
178 | 0 | m_offsetY = NS_PROJ::internal::c_locale_stod(offsetY); |
179 | 0 | } catch (const std::exception &) { |
180 | 0 | proj_log_error(P, _("Invalid offset value")); |
181 | 0 | return false; |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | 158 | if (((m_bHasEllipsoidalHeightOffset ? 1 : 0) + |
186 | 158 | (m_bHasVerticalToVertical ? 1 : 0) + |
187 | 158 | (m_bHasGeographicToVertical ? 1 : 0)) > 1) { |
188 | 0 | proj_log_error(P, _("Unsupported mix of grid types.")); |
189 | 0 | return false; |
190 | 0 | } |
191 | | |
192 | 158 | if (m_bHasGeographic3DOffset) { |
193 | 0 | m_mainGridTypeIsGeographic3DOffset = true; |
194 | 0 | m_mainGridType = "GEOGRAPHIC_3D_OFFSET"; |
195 | 158 | } else if (!m_bHasHorizontalOffset) { |
196 | 158 | if (m_bHasEllipsoidalHeightOffset) |
197 | 0 | m_mainGridType = "ELLIPSOIDAL_HEIGHT_OFFSET"; |
198 | 158 | else if (m_bHasGeographicToVertical) |
199 | 0 | m_mainGridType = "VERTICAL_OFFSET_GEOGRAPHIC_TO_VERTICAL"; |
200 | 158 | else { |
201 | 158 | assert(m_bHasVerticalToVertical); |
202 | 158 | m_mainGridType = "VERTICAL_OFFSET_VERTICAL_TO_VERTICAL"; |
203 | 158 | } |
204 | 158 | } else { |
205 | 0 | assert(m_bHasHorizontalOffset); |
206 | 0 | m_mainGridType = "HORIZONTAL_OFFSET"; |
207 | 0 | } |
208 | | |
209 | 158 | if (m_bHasHorizontalOffset) { |
210 | 0 | if (m_bHasEllipsoidalHeightOffset) |
211 | 0 | m_auxGridType = "ELLIPSOIDAL_HEIGHT_OFFSET"; |
212 | 0 | else if (m_bHasGeographicToVertical) |
213 | 0 | m_auxGridType = "VERTICAL_OFFSET_GEOGRAPHIC_TO_VERTICAL"; |
214 | 0 | else if (m_bHasVerticalToVertical) { |
215 | 0 | m_auxGridType = "VERTICAL_OFFSET_VERTICAL_TO_VERTICAL"; |
216 | 0 | } |
217 | 0 | } |
218 | | |
219 | 158 | return true; |
220 | 158 | } |
221 | | |
222 | | // --------------------------------------------------------------------------- |
223 | | |
224 | | const GenericShiftGrid * |
225 | | gridshiftData::findGrid(const std::string &type, const PJ_XYZ &input, |
226 | 0 | GenericShiftGridSet *&gridSetOut) const { |
227 | 0 | for (const auto &gridset : m_grids) { |
228 | 0 | auto grid = gridset->gridAt(type, input.x, input.y); |
229 | 0 | if (grid) { |
230 | 0 | gridSetOut = gridset.get(); |
231 | 0 | return grid; |
232 | 0 | } |
233 | 0 | } |
234 | 0 | return nullptr; |
235 | 0 | } |
236 | | |
237 | | // --------------------------------------------------------------------------- |
238 | | |
239 | 0 | #define REL_TOLERANCE_HGRIDSHIFT 1e-5 |
240 | | |
241 | | PJ_XYZ gridshiftData::grid_interpolate(PJ_CONTEXT *ctx, const std::string &type, |
242 | | PJ_XY xy, const GenericShiftGrid *grid, |
243 | 0 | bool &biquadraticInterpolationOut) { |
244 | 0 | PJ_XYZ val; |
245 | |
|
246 | 0 | val.x = val.y = HUGE_VAL; |
247 | 0 | val.z = 0; |
248 | |
|
249 | 0 | const bool isProjectedCoord = !grid->extentAndRes().isGeographic; |
250 | 0 | auto iterCache = m_cacheGridInfo.find(grid); |
251 | 0 | if (iterCache == m_cacheGridInfo.end()) { |
252 | 0 | bool eastingNorthingOffset = false; |
253 | 0 | const auto samplesPerPixel = grid->samplesPerPixel(); |
254 | 0 | int idxSampleY = -1; |
255 | 0 | int idxSampleX = -1; |
256 | 0 | int idxSampleZ = -1; |
257 | 0 | for (int i = 0; i < samplesPerPixel; i++) { |
258 | 0 | const auto desc = grid->description(i); |
259 | 0 | if (!isProjectedCoord && desc == "latitude_offset") { |
260 | 0 | idxSampleY = i; |
261 | 0 | const auto unit = grid->unit(idxSampleY); |
262 | 0 | if (!unit.empty() && unit != "arc-second") { |
263 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
264 | 0 | "gridshift: Only unit=arc-second currently handled"); |
265 | 0 | return val; |
266 | 0 | } |
267 | 0 | } else if (!isProjectedCoord && desc == "longitude_offset") { |
268 | 0 | idxSampleX = i; |
269 | 0 | const auto unit = grid->unit(idxSampleX); |
270 | 0 | if (!unit.empty() && unit != "arc-second") { |
271 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
272 | 0 | "gridshift: Only unit=arc-second currently handled"); |
273 | 0 | return val; |
274 | 0 | } |
275 | 0 | } else if (isProjectedCoord && desc == "easting_offset") { |
276 | 0 | eastingNorthingOffset = true; |
277 | 0 | idxSampleX = i; |
278 | 0 | const auto unit = grid->unit(idxSampleX); |
279 | 0 | if (!unit.empty() && unit != "metre") { |
280 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
281 | 0 | "gridshift: Only unit=metre currently handled"); |
282 | 0 | return val; |
283 | 0 | } |
284 | 0 | } else if (isProjectedCoord && desc == "northing_offset") { |
285 | 0 | eastingNorthingOffset = true; |
286 | 0 | idxSampleY = i; |
287 | 0 | const auto unit = grid->unit(idxSampleY); |
288 | 0 | if (!unit.empty() && unit != "metre") { |
289 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
290 | 0 | "gridshift: Only unit=metre currently handled"); |
291 | 0 | return val; |
292 | 0 | } |
293 | 0 | } else if (desc == "ellipsoidal_height_offset" || |
294 | 0 | desc == "geoid_undulation" || desc == "hydroid_height" || |
295 | 0 | desc == "vertical_offset") { |
296 | 0 | idxSampleZ = i; |
297 | 0 | const auto unit = grid->unit(idxSampleZ); |
298 | 0 | if (!unit.empty() && unit != "metre") { |
299 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
300 | 0 | "gridshift: Only unit=metre currently handled"); |
301 | 0 | return val; |
302 | 0 | } |
303 | 0 | } |
304 | 0 | } |
305 | 0 | if (samplesPerPixel >= 2 && idxSampleY < 0 && idxSampleX < 0 && |
306 | 0 | type == "HORIZONTAL_OFFSET") { |
307 | 0 | if (isProjectedCoord) { |
308 | 0 | eastingNorthingOffset = true; |
309 | 0 | idxSampleX = 0; |
310 | 0 | idxSampleY = 1; |
311 | 0 | } else { |
312 | | // X=longitude assumed to be the second component if metadata |
313 | | // lacking |
314 | 0 | idxSampleX = 1; |
315 | | // Y=latitude assumed to be the first component if metadata |
316 | | // lacking |
317 | 0 | idxSampleY = 0; |
318 | 0 | } |
319 | 0 | } |
320 | 0 | if (type == "HORIZONTAL_OFFSET" || type == "GEOGRAPHIC_3D_OFFSET") { |
321 | 0 | if (idxSampleY < 0 || idxSampleX < 0) { |
322 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
323 | 0 | "gridshift: grid has not expected samples"); |
324 | 0 | return val; |
325 | 0 | } |
326 | 0 | } |
327 | 0 | if (type == "ELLIPSOIDAL_HEIGHT_OFFSET" || |
328 | 0 | type == "VERTICAL_OFFSET_GEOGRAPHIC_TO_VERTICAL" || |
329 | 0 | type == "VERTICAL_OFFSET_VERTICAL_TO_VERTICAL" || |
330 | 0 | type == "GEOGRAPHIC_3D_OFFSET") { |
331 | 0 | if (idxSampleZ < 0) { |
332 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
333 | 0 | "gridshift: grid has not expected samples"); |
334 | 0 | return val; |
335 | 0 | } |
336 | 0 | } |
337 | | |
338 | 0 | std::string interpolation(m_interpolation); |
339 | 0 | if (interpolation.empty()) |
340 | 0 | interpolation = grid->metadataItem("interpolation_method"); |
341 | 0 | if (interpolation.empty()) |
342 | 0 | interpolation = "bilinear"; |
343 | |
|
344 | 0 | if (interpolation != "bilinear" && interpolation != "biquadratic") { |
345 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
346 | 0 | "gridshift: Unsupported interpolation_method in grid"); |
347 | 0 | return val; |
348 | 0 | } |
349 | | |
350 | 0 | GridInfo gridInfo; |
351 | 0 | gridInfo.idxSampleX = idxSampleX; |
352 | 0 | gridInfo.idxSampleY = idxSampleY; |
353 | 0 | gridInfo.idxSampleZ = m_skip_z_transform ? -1 : idxSampleZ; |
354 | 0 | gridInfo.eastingNorthingOffset = eastingNorthingOffset; |
355 | 0 | gridInfo.bilinearInterpolation = |
356 | 0 | (interpolation == "bilinear" || grid->width() < 3 || |
357 | 0 | grid->height() < 3); |
358 | 0 | gridInfo.shifts.resize(3 * 3 * 3); |
359 | 0 | if (idxSampleX == 1 && idxSampleY == 0) { |
360 | | // Little optimization for the common of grids storing shifts in |
361 | | // latitude, longitude, in that order. |
362 | | // We want to request data in the order it is stored in the grid, |
363 | | // which triggers a read optimization. |
364 | | // But we must compensate for that by switching the role of x and y |
365 | | // after computation. |
366 | 0 | gridInfo.swapXYInRes = true; |
367 | 0 | gridInfo.idxSampleXYZ[0] = 0; |
368 | 0 | gridInfo.idxSampleXYZ[1] = 1; |
369 | 0 | } else { |
370 | 0 | gridInfo.idxSampleXYZ[0] = idxSampleX; |
371 | 0 | gridInfo.idxSampleXYZ[1] = idxSampleY; |
372 | 0 | } |
373 | 0 | gridInfo.idxSampleXYZ[2] = idxSampleZ; |
374 | 0 | iterCache = m_cacheGridInfo.emplace(grid, std::move(gridInfo)).first; |
375 | 0 | } |
376 | | // cppcheck-suppress derefInvalidIteratorRedundantCheck |
377 | 0 | GridInfo &gridInfo = iterCache->second; |
378 | 0 | const int idxSampleX = gridInfo.idxSampleX; |
379 | 0 | const int idxSampleY = gridInfo.idxSampleY; |
380 | 0 | const int idxSampleZ = gridInfo.idxSampleZ; |
381 | 0 | const bool bilinearInterpolation = gridInfo.bilinearInterpolation; |
382 | 0 | biquadraticInterpolationOut = !bilinearInterpolation; |
383 | |
|
384 | 0 | IXY indxy; |
385 | 0 | const auto &extent = grid->extentAndRes(); |
386 | 0 | double x = (xy.x - extent.west) / extent.resX; |
387 | 0 | indxy.x = std::isnan(x) ? 0 : (int32_t)lround(floor(x)); |
388 | 0 | double y = (xy.y - extent.south) / extent.resY; |
389 | 0 | indxy.y = std::isnan(y) ? 0 : (int32_t)lround(floor(y)); |
390 | |
|
391 | 0 | PJ_XY frct; |
392 | 0 | frct.x = x - indxy.x; |
393 | 0 | frct.y = y - indxy.y; |
394 | 0 | int tmpInt; |
395 | 0 | if (indxy.x < 0) { |
396 | 0 | if (indxy.x == -1 && frct.x > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) { |
397 | 0 | ++indxy.x; |
398 | 0 | frct.x = 0.; |
399 | 0 | } else |
400 | 0 | return val; |
401 | 0 | } else if ((tmpInt = indxy.x + 1) >= grid->width()) { |
402 | 0 | if (tmpInt == grid->width() && frct.x < 10 * REL_TOLERANCE_HGRIDSHIFT) { |
403 | 0 | --indxy.x; |
404 | 0 | frct.x = 1.; |
405 | 0 | } else |
406 | 0 | return val; |
407 | 0 | } |
408 | 0 | if (indxy.y < 0) { |
409 | 0 | if (indxy.y == -1 && frct.y > 1 - 10 * REL_TOLERANCE_HGRIDSHIFT) { |
410 | 0 | ++indxy.y; |
411 | 0 | frct.y = 0.; |
412 | 0 | } else |
413 | 0 | return val; |
414 | 0 | } else if ((tmpInt = indxy.y + 1) >= grid->height()) { |
415 | 0 | if (tmpInt == grid->height() && |
416 | 0 | frct.y < 10 * REL_TOLERANCE_HGRIDSHIFT) { |
417 | 0 | --indxy.y; |
418 | 0 | frct.y = 1.; |
419 | 0 | } else |
420 | 0 | return val; |
421 | 0 | } |
422 | | |
423 | 0 | bool nodataFound = false; |
424 | 0 | if (bilinearInterpolation) { |
425 | 0 | double m10 = frct.x; |
426 | 0 | double m11 = m10; |
427 | 0 | double m01 = 1. - frct.x; |
428 | 0 | double m00 = m01; |
429 | 0 | m11 *= frct.y; |
430 | 0 | m01 *= frct.y; |
431 | 0 | frct.y = 1. - frct.y; |
432 | 0 | m00 *= frct.y; |
433 | 0 | m10 *= frct.y; |
434 | 0 | if (idxSampleX >= 0 && idxSampleY >= 0) { |
435 | 0 | if (gridInfo.lastIdxXY != indxy) { |
436 | 0 | if (!grid->valuesAt(indxy.x, indxy.y, 2, 2, |
437 | 0 | idxSampleZ >= 0 ? 3 : 2, |
438 | 0 | gridInfo.idxSampleXYZ.data(), |
439 | 0 | gridInfo.shifts.data(), nodataFound) || |
440 | 0 | nodataFound) { |
441 | 0 | return val; |
442 | 0 | } |
443 | 0 | gridInfo.lastIdxXY = indxy; |
444 | 0 | } |
445 | 0 | if (idxSampleZ >= 0) { |
446 | 0 | val.x = (m00 * gridInfo.shifts[0] + m10 * gridInfo.shifts[3] + |
447 | 0 | m01 * gridInfo.shifts[6] + m11 * gridInfo.shifts[9]); |
448 | 0 | val.y = (m00 * gridInfo.shifts[1] + m10 * gridInfo.shifts[4] + |
449 | 0 | m01 * gridInfo.shifts[7] + m11 * gridInfo.shifts[10]); |
450 | 0 | val.z = m00 * gridInfo.shifts[2] + m10 * gridInfo.shifts[5] + |
451 | 0 | m01 * gridInfo.shifts[8] + m11 * gridInfo.shifts[11]; |
452 | 0 | } else { |
453 | 0 | val.x = (m00 * gridInfo.shifts[0] + m10 * gridInfo.shifts[2] + |
454 | 0 | m01 * gridInfo.shifts[4] + m11 * gridInfo.shifts[6]); |
455 | 0 | val.y = (m00 * gridInfo.shifts[1] + m10 * gridInfo.shifts[3] + |
456 | 0 | m01 * gridInfo.shifts[5] + m11 * gridInfo.shifts[7]); |
457 | 0 | } |
458 | 0 | } else { |
459 | 0 | val.x = 0; |
460 | 0 | val.y = 0; |
461 | 0 | if (idxSampleZ >= 0) { |
462 | 0 | if (gridInfo.lastIdxXY != indxy) { |
463 | 0 | if (!grid->valuesAt(indxy.x, indxy.y, 2, 2, 1, &idxSampleZ, |
464 | 0 | gridInfo.shifts.data(), nodataFound) || |
465 | 0 | nodataFound) { |
466 | 0 | return val; |
467 | 0 | } |
468 | 0 | gridInfo.lastIdxXY = indxy; |
469 | 0 | } |
470 | 0 | val.z = m00 * gridInfo.shifts[0] + m10 * gridInfo.shifts[1] + |
471 | 0 | m01 * gridInfo.shifts[2] + m11 * gridInfo.shifts[3]; |
472 | 0 | } |
473 | 0 | } |
474 | 0 | } else // biquadratic |
475 | 0 | { |
476 | | // Cf https://geodesy.noaa.gov/library/pdfs/NOAA_TM_NOS_NGS_0084.pdf |
477 | | // Depending if we are before or after half-pixel, shift the 3x3 window |
478 | | // of interpolation |
479 | 0 | if ((frct.x <= 0.5 && indxy.x > 0) || (indxy.x + 2 == grid->width())) { |
480 | 0 | indxy.x -= 1; |
481 | 0 | frct.x += 1; |
482 | 0 | } |
483 | 0 | if ((frct.y <= 0.5 && indxy.y > 0) || (indxy.y + 2 == grid->height())) { |
484 | 0 | indxy.y -= 1; |
485 | 0 | frct.y += 1; |
486 | 0 | } |
487 | | |
488 | | // Port of qterp() Fortran function from NOAA |
489 | | // xToInterp must be in [0,2] range |
490 | | // f0 must be f(0), f1 must be f(1), f2 must be f(2) |
491 | | // Returns f(xToInterp) interpolated value along the parabolic function |
492 | 0 | const auto quadraticInterpol = [](double xToInterp, double f0, |
493 | 0 | double f1, double f2) { |
494 | 0 | const double df0 = f1 - f0; |
495 | 0 | const double df1 = f2 - f1; |
496 | 0 | const double d2f0 = df1 - df0; |
497 | 0 | return f0 + xToInterp * df0 + |
498 | 0 | 0.5 * xToInterp * (xToInterp - 1.0) * d2f0; |
499 | 0 | }; |
500 | |
|
501 | 0 | if (idxSampleX >= 0 && idxSampleY >= 0) { |
502 | 0 | if (gridInfo.lastIdxXY != indxy) { |
503 | 0 | if (!grid->valuesAt(indxy.x, indxy.y, 3, 3, |
504 | 0 | idxSampleZ >= 0 ? 3 : 2, |
505 | 0 | gridInfo.idxSampleXYZ.data(), |
506 | 0 | gridInfo.shifts.data(), nodataFound) || |
507 | 0 | nodataFound) { |
508 | 0 | return val; |
509 | 0 | } |
510 | 0 | gridInfo.lastIdxXY = indxy; |
511 | 0 | } |
512 | 0 | const auto *shifts_ptr = gridInfo.shifts.data(); |
513 | 0 | if (idxSampleZ >= 0) { |
514 | 0 | double xyz_shift[3][4]; |
515 | 0 | for (int j = 0; j <= 2; ++j) { |
516 | 0 | xyz_shift[j][0] = quadraticInterpol( |
517 | 0 | frct.x, shifts_ptr[0], shifts_ptr[3], shifts_ptr[6]); |
518 | 0 | xyz_shift[j][1] = quadraticInterpol( |
519 | 0 | frct.x, shifts_ptr[1], shifts_ptr[4], shifts_ptr[7]); |
520 | 0 | xyz_shift[j][2] = quadraticInterpol( |
521 | 0 | frct.x, shifts_ptr[2], shifts_ptr[5], shifts_ptr[8]); |
522 | 0 | shifts_ptr += 9; |
523 | 0 | } |
524 | 0 | val.x = quadraticInterpol(frct.y, xyz_shift[0][0], |
525 | 0 | xyz_shift[1][0], xyz_shift[2][0]); |
526 | 0 | val.y = quadraticInterpol(frct.y, xyz_shift[0][1], |
527 | 0 | xyz_shift[1][1], xyz_shift[2][1]); |
528 | 0 | val.z = quadraticInterpol(frct.y, xyz_shift[0][2], |
529 | 0 | xyz_shift[1][2], xyz_shift[2][2]); |
530 | 0 | } else { |
531 | 0 | double xy_shift[3][2]; |
532 | 0 | for (int j = 0; j <= 2; ++j) { |
533 | 0 | xy_shift[j][0] = quadraticInterpol( |
534 | 0 | frct.x, shifts_ptr[0], shifts_ptr[2], shifts_ptr[4]); |
535 | 0 | xy_shift[j][1] = quadraticInterpol( |
536 | 0 | frct.x, shifts_ptr[1], shifts_ptr[3], shifts_ptr[5]); |
537 | 0 | shifts_ptr += 6; |
538 | 0 | } |
539 | 0 | val.x = quadraticInterpol(frct.y, xy_shift[0][0], |
540 | 0 | xy_shift[1][0], xy_shift[2][0]); |
541 | 0 | val.y = quadraticInterpol(frct.y, xy_shift[0][1], |
542 | 0 | xy_shift[1][1], xy_shift[2][1]); |
543 | 0 | } |
544 | 0 | } else { |
545 | 0 | val.x = 0; |
546 | 0 | val.y = 0; |
547 | 0 | if (idxSampleZ >= 0) { |
548 | 0 | if (gridInfo.lastIdxXY != indxy) { |
549 | 0 | if (!grid->valuesAt(indxy.x, indxy.y, 3, 3, 1, &idxSampleZ, |
550 | 0 | gridInfo.shifts.data(), nodataFound) || |
551 | 0 | nodataFound) { |
552 | 0 | return val; |
553 | 0 | } |
554 | 0 | gridInfo.lastIdxXY = indxy; |
555 | 0 | } |
556 | 0 | double z_shift[3]; |
557 | 0 | const auto *shifts_ptr = gridInfo.shifts.data(); |
558 | 0 | for (int j = 0; j <= 2; ++j) { |
559 | 0 | z_shift[j] = quadraticInterpol( |
560 | 0 | frct.x, shifts_ptr[0], shifts_ptr[1], shifts_ptr[2]); |
561 | 0 | shifts_ptr += 3; |
562 | 0 | } |
563 | 0 | val.z = quadraticInterpol(frct.y, z_shift[0], z_shift[1], |
564 | 0 | z_shift[2]); |
565 | 0 | } |
566 | 0 | } |
567 | 0 | } |
568 | | |
569 | 0 | if (idxSampleX >= 0 && idxSampleY >= 0 && !gridInfo.eastingNorthingOffset) { |
570 | 0 | constexpr double convFactorXY = 1. / 3600 / 180 * M_PI; |
571 | 0 | val.x *= convFactorXY; |
572 | 0 | val.y *= convFactorXY; |
573 | 0 | } |
574 | |
|
575 | 0 | if (gridInfo.swapXYInRes) { |
576 | 0 | std::swap(val.x, val.y); |
577 | 0 | } |
578 | |
|
579 | 0 | return val; |
580 | 0 | } |
581 | | |
582 | | // --------------------------------------------------------------------------- |
583 | | |
584 | | static PJ_XY normalizeX(const GenericShiftGrid *grid, const PJ_XYZ in, |
585 | 0 | const NS_PROJ::ExtentAndRes *&extentOut) { |
586 | 0 | PJ_XY normalized; |
587 | 0 | normalized.x = in.x; |
588 | 0 | normalized.y = in.y; |
589 | 0 | extentOut = &(grid->extentAndRes()); |
590 | 0 | if (extentOut->isGeographic) { |
591 | 0 | const double epsilon = |
592 | 0 | (extentOut->resX + extentOut->resY) * REL_TOLERANCE_HGRIDSHIFT; |
593 | 0 | if (normalized.x < extentOut->west - epsilon) |
594 | 0 | normalized.x += 2 * M_PI; |
595 | 0 | else if (normalized.x > extentOut->east + epsilon) |
596 | 0 | normalized.x -= 2 * M_PI; |
597 | 0 | } |
598 | 0 | return normalized; |
599 | 0 | } |
600 | | |
601 | | // --------------------------------------------------------------------------- |
602 | | |
603 | 0 | #define MAX_ITERATIONS 10 |
604 | 0 | #define TOL 1e-12 |
605 | | |
606 | | PJ_XYZ gridshiftData::grid_apply_internal( |
607 | | PJ_CONTEXT *ctx, const std::string &type, bool isVerticalOnly, |
608 | | const PJ_XYZ in, PJ_DIRECTION direction, const GenericShiftGrid *grid, |
609 | 0 | GenericShiftGridSet *gridset, bool &shouldRetry) { |
610 | |
|
611 | 0 | shouldRetry = false; |
612 | 0 | if (in.x == HUGE_VAL) |
613 | 0 | return in; |
614 | | |
615 | | /* normalized longitude of input */ |
616 | 0 | const NS_PROJ::ExtentAndRes *extent; |
617 | 0 | PJ_XY normalized_in = normalizeX(grid, in, extent); |
618 | |
|
619 | 0 | bool biquadraticInterpolationOut = false; |
620 | 0 | PJ_XYZ shift = grid_interpolate(ctx, type, normalized_in, grid, |
621 | 0 | biquadraticInterpolationOut); |
622 | 0 | if (grid->hasChanged()) { |
623 | 0 | shouldRetry = gridset->reopen(ctx); |
624 | 0 | PJ_XYZ out; |
625 | 0 | out.x = out.y = out.z = HUGE_VAL; |
626 | 0 | return out; |
627 | 0 | } |
628 | 0 | if (shift.x == HUGE_VAL) |
629 | 0 | return shift; |
630 | | |
631 | 0 | if (direction == PJ_FWD) { |
632 | 0 | PJ_XYZ out = in; |
633 | 0 | out.x += shift.x; |
634 | 0 | out.y += shift.y; |
635 | 0 | out.z += shift.z; |
636 | 0 | return out; |
637 | 0 | } |
638 | | |
639 | 0 | if (isVerticalOnly) { |
640 | 0 | PJ_XYZ out = in; |
641 | 0 | out.z -= shift.z; |
642 | 0 | return out; |
643 | 0 | } |
644 | | |
645 | 0 | PJ_XY guess; |
646 | 0 | guess.x = normalized_in.x - shift.x; |
647 | 0 | guess.y = normalized_in.y - shift.y; |
648 | | |
649 | | // NOAA NCAT transformer tool doesn't do iteration in the reverse path. |
650 | | // Do the same (only for biquadratic, although NCAT applies this logic to |
651 | | // bilinear too) |
652 | | // Cf |
653 | | // https://github.com/noaa-ngs/ncat-lib/blob/77bcff1ce4a78fe06d0312102ada008aefcc2c62/src/gov/noaa/ngs/grid/Transformer.java#L374 |
654 | | // When trying to do iterative reverse path with biquadratic, we can |
655 | | // get convergence failures on points that are close to the boundary of |
656 | | // cells or half-cells. For example with |
657 | | // echo -122.4250009683 37.8286740788 0 | bin/cct -I +proj=gridshift |
658 | | // +grids=tests/us_noaa_nadcon5_nad83_1986_nad83_harn_conus_extract_sanfrancisco.tif |
659 | | // +interpolation=biquadratic |
660 | 0 | if (!biquadraticInterpolationOut) { |
661 | 0 | int i = MAX_ITERATIONS; |
662 | 0 | const double toltol = TOL * TOL; |
663 | 0 | PJ_XY diff; |
664 | 0 | do { |
665 | 0 | shift = grid_interpolate(ctx, type, guess, grid, |
666 | 0 | biquadraticInterpolationOut); |
667 | 0 | if (grid->hasChanged()) { |
668 | 0 | shouldRetry = gridset->reopen(ctx); |
669 | 0 | PJ_XYZ out; |
670 | 0 | out.x = out.y = out.z = HUGE_VAL; |
671 | 0 | return out; |
672 | 0 | } |
673 | | |
674 | | /* We can possibly go outside of the initial guessed grid, so try */ |
675 | | /* to fetch a new grid into which iterate... */ |
676 | 0 | if (shift.x == HUGE_VAL) { |
677 | 0 | PJ_XYZ lp; |
678 | 0 | lp.x = guess.x; |
679 | 0 | lp.y = guess.y; |
680 | 0 | auto newGrid = findGrid(type, lp, gridset); |
681 | 0 | if (newGrid == nullptr || newGrid == grid || |
682 | 0 | newGrid->isNullGrid()) |
683 | 0 | break; |
684 | 0 | pj_log(ctx, PJ_LOG_TRACE, "Switching from grid %s to grid %s", |
685 | 0 | grid->name().c_str(), newGrid->name().c_str()); |
686 | 0 | grid = newGrid; |
687 | 0 | normalized_in = normalizeX(grid, in, extent); |
688 | 0 | diff.x = std::numeric_limits<double>::max(); |
689 | 0 | diff.y = std::numeric_limits<double>::max(); |
690 | 0 | continue; |
691 | 0 | } |
692 | | |
693 | 0 | diff.x = guess.x + shift.x - normalized_in.x; |
694 | 0 | diff.y = guess.y + shift.y - normalized_in.y; |
695 | 0 | guess.x -= diff.x; |
696 | 0 | guess.y -= diff.y; |
697 | |
|
698 | 0 | } while (--i && (diff.x * diff.x + diff.y * diff.y > |
699 | 0 | toltol)); /* prob. slightly faster than hypot() */ |
700 | | |
701 | 0 | if (i == 0) { |
702 | 0 | pj_log(ctx, PJ_LOG_TRACE, |
703 | 0 | "Inverse grid shift iterator failed to converge."); |
704 | 0 | proj_context_errno_set(ctx, PROJ_ERR_COORD_TRANSFM_NO_CONVERGENCE); |
705 | 0 | PJ_XYZ out; |
706 | 0 | out.x = out.y = out.z = HUGE_VAL; |
707 | 0 | return out; |
708 | 0 | } |
709 | | |
710 | 0 | if (shift.x == HUGE_VAL) { |
711 | 0 | pj_log( |
712 | 0 | ctx, PJ_LOG_TRACE, |
713 | 0 | "Inverse grid shift iteration failed, presumably at grid edge. " |
714 | 0 | "Using first approximation."); |
715 | 0 | } |
716 | 0 | } |
717 | | |
718 | 0 | PJ_XYZ out; |
719 | 0 | out.x = extent->isGeographic ? adjlon(guess.x) : guess.x; |
720 | 0 | out.y = guess.y; |
721 | 0 | out.z = in.z - shift.z; |
722 | 0 | return out; |
723 | 0 | } |
724 | | |
725 | | // --------------------------------------------------------------------------- |
726 | | |
727 | 0 | bool gridshiftData::loadGridsIfNeeded(PJ *P) { |
728 | 0 | if (m_error_code_in_defer_grid_opening) { |
729 | 0 | proj_errno_set(P, m_error_code_in_defer_grid_opening); |
730 | 0 | return false; |
731 | 0 | } else if (m_defer_grid_opening) { |
732 | 0 | m_defer_grid_opening = false; |
733 | 0 | m_grids = pj_generic_grid_init(P, "grids"); |
734 | 0 | m_error_code_in_defer_grid_opening = proj_errno(P); |
735 | 0 | if (m_error_code_in_defer_grid_opening) { |
736 | 0 | return false; |
737 | 0 | } |
738 | 0 | bool isProjectedCoord; |
739 | 0 | if (!checkGridTypes(P, isProjectedCoord)) { |
740 | 0 | return false; |
741 | 0 | } |
742 | 0 | } |
743 | 0 | return true; |
744 | 0 | } |
745 | | |
746 | | // --------------------------------------------------------------------------- |
747 | | |
748 | | // --------------------------------------------------------------------------- |
749 | | |
750 | | static const std::string sHORIZONTAL_OFFSET("HORIZONTAL_OFFSET"); |
751 | | |
752 | 0 | PJ_XYZ gridshiftData::apply(PJ *P, PJ_DIRECTION direction, PJ_XYZ xyz) { |
753 | 0 | PJ_XYZ out; |
754 | |
|
755 | 0 | out.x = HUGE_VAL; |
756 | 0 | out.y = HUGE_VAL; |
757 | 0 | out.z = HUGE_VAL; |
758 | |
|
759 | 0 | std::string &type = m_mainGridType; |
760 | 0 | bool bFoundGeog3DOffset = false; |
761 | 0 | while (true) { |
762 | 0 | GenericShiftGridSet *gridset = nullptr; |
763 | 0 | const GenericShiftGrid *grid = findGrid(type, xyz, gridset); |
764 | 0 | if (!grid) { |
765 | 0 | if (m_mainGridTypeIsGeographic3DOffset && m_bHasHorizontalOffset) { |
766 | | // If we have a mix of grids with GEOGRAPHIC_3D_OFFSET |
767 | | // and HORIZONTAL_OFFSET+ELLIPSOIDAL_HEIGHT_OFFSET |
768 | 0 | type = sHORIZONTAL_OFFSET; |
769 | 0 | grid = findGrid(type, xyz, gridset); |
770 | 0 | } |
771 | 0 | if (!grid) { |
772 | 0 | proj_context_errno_set(P->ctx, |
773 | 0 | PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); |
774 | 0 | return out; |
775 | 0 | } |
776 | 0 | } else { |
777 | 0 | if (m_mainGridTypeIsGeographic3DOffset) |
778 | 0 | bFoundGeog3DOffset = true; |
779 | 0 | } |
780 | | |
781 | 0 | if (grid->isNullGrid()) { |
782 | 0 | out = xyz; |
783 | 0 | break; |
784 | 0 | } |
785 | 0 | bool shouldRetry = false; |
786 | 0 | out = grid_apply_internal( |
787 | 0 | P->ctx, type, !(m_bHasGeographic3DOffset || m_bHasHorizontalOffset), |
788 | 0 | xyz, direction, grid, gridset, shouldRetry); |
789 | 0 | if (!shouldRetry) { |
790 | 0 | break; |
791 | 0 | } |
792 | 0 | } |
793 | | |
794 | 0 | if (out.x == HUGE_VAL || out.y == HUGE_VAL) { |
795 | 0 | if (proj_context_errno(P->ctx) == 0) { |
796 | 0 | proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); |
797 | 0 | } |
798 | 0 | return out; |
799 | 0 | } |
800 | | |
801 | | // Second pass to apply vertical transformation, if it is in a |
802 | | // separate grid than lat-lon offsets. |
803 | 0 | if (!bFoundGeog3DOffset && !m_auxGridType.empty()) { |
804 | 0 | xyz = out; |
805 | 0 | while (true) { |
806 | 0 | GenericShiftGridSet *gridset = nullptr; |
807 | 0 | const auto grid = findGrid(m_auxGridType, xyz, gridset); |
808 | 0 | if (!grid) { |
809 | 0 | proj_context_errno_set(P->ctx, |
810 | 0 | PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); |
811 | 0 | return out; |
812 | 0 | } |
813 | 0 | if (grid->isNullGrid()) { |
814 | 0 | break; |
815 | 0 | } |
816 | | |
817 | 0 | bool shouldRetry = false; |
818 | 0 | out = grid_apply_internal(P->ctx, m_auxGridType, true, xyz, |
819 | 0 | direction, grid, gridset, shouldRetry); |
820 | 0 | if (!shouldRetry) { |
821 | 0 | break; |
822 | 0 | } |
823 | 0 | } |
824 | | |
825 | 0 | if (out.x == HUGE_VAL || out.y == HUGE_VAL) { |
826 | 0 | proj_context_errno_set(P->ctx, PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID); |
827 | 0 | return out; |
828 | 0 | } |
829 | 0 | } |
830 | | |
831 | 0 | return out; |
832 | 0 | } |
833 | | |
834 | | } // anonymous namespace |
835 | | |
836 | | // --------------------------------------------------------------------------- |
837 | | |
838 | 0 | static PJ_XYZ pj_gridshift_forward_3d(PJ_LPZ lpz, PJ *P) { |
839 | 0 | auto Q = static_cast<gridshiftData *>(P->opaque); |
840 | |
|
841 | 0 | if (!Q->loadGridsIfNeeded(P)) { |
842 | 0 | return proj_coord_error().xyz; |
843 | 0 | } |
844 | | |
845 | 0 | PJ_XYZ xyz; |
846 | 0 | xyz.x = lpz.lam; |
847 | 0 | xyz.y = lpz.phi; |
848 | 0 | xyz.z = lpz.z; |
849 | |
|
850 | 0 | xyz = Q->apply(P, PJ_FWD, xyz); |
851 | |
|
852 | 0 | xyz.x += Q->m_offsetX; |
853 | 0 | xyz.y += Q->m_offsetY; |
854 | |
|
855 | 0 | return xyz; |
856 | 0 | } |
857 | | |
858 | | // --------------------------------------------------------------------------- |
859 | | |
860 | 0 | static PJ_LPZ pj_gridshift_reverse_3d(PJ_XYZ xyz, PJ *P) { |
861 | 0 | auto Q = static_cast<gridshiftData *>(P->opaque); |
862 | | |
863 | | // Must be done before using m_offsetX ! |
864 | 0 | if (!Q->loadGridsIfNeeded(P)) { |
865 | 0 | return proj_coord_error().lpz; |
866 | 0 | } |
867 | | |
868 | 0 | xyz.x -= Q->m_offsetX; |
869 | 0 | xyz.y -= Q->m_offsetY; |
870 | |
|
871 | 0 | PJ_XYZ xyz_out = Q->apply(P, PJ_INV, xyz); |
872 | 0 | PJ_LPZ lpz; |
873 | 0 | lpz.lam = xyz_out.x; |
874 | 0 | lpz.phi = xyz_out.y; |
875 | 0 | lpz.z = xyz_out.z; |
876 | 0 | return lpz; |
877 | 0 | } |
878 | | |
879 | | // --------------------------------------------------------------------------- |
880 | | |
881 | 790 | static PJ *pj_gridshift_destructor(PJ *P, int errlev) { |
882 | 790 | if (nullptr == P) |
883 | 0 | return nullptr; |
884 | | |
885 | 790 | delete static_cast<struct gridshiftData *>(P->opaque); |
886 | 790 | P->opaque = nullptr; |
887 | | |
888 | 790 | return pj_default_destructor(P, errlev); |
889 | 790 | } |
890 | | |
891 | | // --------------------------------------------------------------------------- |
892 | | |
893 | 0 | static void pj_gridshift_reassign_context(PJ *P, PJ_CONTEXT *ctx) { |
894 | 0 | auto Q = (struct gridshiftData *)P->opaque; |
895 | 0 | for (auto &grid : Q->m_grids) { |
896 | 0 | grid->reassign_context(ctx); |
897 | 0 | } |
898 | 0 | } |
899 | | |
900 | | // --------------------------------------------------------------------------- |
901 | | |
902 | 790 | PJ *PJ_TRANSFORMATION(gridshift, 0) { |
903 | 790 | auto Q = new gridshiftData; |
904 | 790 | P->opaque = (void *)Q; |
905 | 790 | P->destructor = pj_gridshift_destructor; |
906 | 790 | P->reassign_context = pj_gridshift_reassign_context; |
907 | | |
908 | 790 | P->fwd3d = pj_gridshift_forward_3d; |
909 | 790 | P->inv3d = pj_gridshift_reverse_3d; |
910 | 790 | P->fwd = nullptr; |
911 | 790 | P->inv = nullptr; |
912 | | |
913 | 790 | if (0 == pj_param(P->ctx, P->params, "tgrids").i) { |
914 | 11 | proj_log_error(P, _("+grids parameter missing.")); |
915 | 11 | return pj_gridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
916 | 11 | } |
917 | | |
918 | 779 | bool isKnownGrid = false; |
919 | 779 | bool isProjectedCoord = false; |
920 | | |
921 | 779 | if (!P->ctx->defer_grid_opening || |
922 | 779 | !pj_param(P->ctx, P->params, "tcoord_type").i) { |
923 | 779 | const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s; |
924 | 779 | gMutex.lock(); |
925 | 779 | const auto iter = gKnownGrids.find(gridnames); |
926 | 779 | isKnownGrid = iter != gKnownGrids.end(); |
927 | 779 | if (isKnownGrid) { |
928 | 492 | Q->m_defer_grid_opening = true; |
929 | 492 | isProjectedCoord = iter->second; |
930 | 492 | } |
931 | 779 | gMutex.unlock(); |
932 | 779 | } |
933 | | |
934 | 779 | if (P->ctx->defer_grid_opening || isKnownGrid) { |
935 | 492 | Q->m_defer_grid_opening = true; |
936 | 492 | } else { |
937 | 287 | const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s; |
938 | 287 | Q->m_grids = pj_generic_grid_init(P, "grids"); |
939 | | /* Was gridlist compiled properly? */ |
940 | 287 | if (proj_errno(P)) { |
941 | 118 | proj_log_error(P, _("could not find required grid(s).")); |
942 | 118 | return pj_gridshift_destructor( |
943 | 118 | P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
944 | 118 | } |
945 | 169 | if (!Q->checkGridTypes(P, isProjectedCoord)) { |
946 | 11 | return pj_gridshift_destructor( |
947 | 11 | P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
948 | 11 | } |
949 | | |
950 | 158 | gMutex.lock(); |
951 | 158 | gKnownGrids[gridnames] = isProjectedCoord; |
952 | 158 | gMutex.unlock(); |
953 | 158 | } |
954 | | |
955 | 650 | if (pj_param(P->ctx, P->params, "tinterpolation").i) { |
956 | 0 | const char *interpolation = |
957 | 0 | pj_param(P->ctx, P->params, "sinterpolation").s; |
958 | 0 | if (strcmp(interpolation, "bilinear") == 0 || |
959 | 0 | strcmp(interpolation, "biquadratic") == 0) { |
960 | 0 | Q->m_interpolation = interpolation; |
961 | 0 | } else { |
962 | 0 | proj_log_error(P, _("Unsupported value for +interpolation.")); |
963 | 0 | return pj_gridshift_destructor( |
964 | 0 | P, PROJ_ERR_INVALID_OP_ILLEGAL_ARG_VALUE); |
965 | 0 | } |
966 | 0 | } |
967 | | |
968 | 650 | if (pj_param(P->ctx, P->params, "tno_z_transform").i) { |
969 | 142 | Q->m_skip_z_transform = true; |
970 | 142 | } |
971 | | |
972 | | // +coord_type not advertised in documentation on purpose for now. |
973 | | // It is probably useless to do it, as the only potential use case of it |
974 | | // would be for PROJ itself when generating pipelines with deferred grid |
975 | | // opening. |
976 | 650 | if (pj_param(P->ctx, P->params, "tcoord_type").i) { |
977 | | // Check the coordinate type (projected/geographic) from the explicit |
978 | | // +coord_type switch. This is mostly only useful in deferred grid |
979 | | // opening, otherwise we have figured it out above in checkGridTypes() |
980 | 0 | const char *coord_type = pj_param(P->ctx, P->params, "scoord_type").s; |
981 | 0 | if (coord_type) { |
982 | 0 | if (strcmp(coord_type, "projected") == 0) { |
983 | 0 | if (!P->ctx->defer_grid_opening && !isProjectedCoord) { |
984 | 0 | proj_log_error(P, |
985 | 0 | _("+coord_type=projected specified, but the " |
986 | 0 | "grid is known to not be projected")); |
987 | 0 | return pj_gridshift_destructor( |
988 | 0 | P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
989 | 0 | } |
990 | 0 | isProjectedCoord = true; |
991 | 0 | } else if (strcmp(coord_type, "geographic") == 0) { |
992 | 0 | if (!P->ctx->defer_grid_opening && isProjectedCoord) { |
993 | 0 | proj_log_error(P, _("+coord_type=geographic specified, but " |
994 | 0 | "the grid is known to be projected")); |
995 | 0 | return pj_gridshift_destructor( |
996 | 0 | P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
997 | 0 | } |
998 | 0 | } else { |
999 | 0 | proj_log_error(P, _("Unsupported value for +coord_type: valid " |
1000 | 0 | "values are 'geographic' or 'projected'")); |
1001 | 0 | return pj_gridshift_destructor(P, |
1002 | 0 | PROJ_ERR_INVALID_OP_MISSING_ARG); |
1003 | 0 | } |
1004 | 0 | } |
1005 | 0 | } |
1006 | | |
1007 | 650 | if (isKnownGrid || pj_param(P->ctx, P->params, "tcoord_type").i) { |
1008 | 492 | if (isProjectedCoord) { |
1009 | 0 | P->left = PJ_IO_UNITS_PROJECTED; |
1010 | 0 | P->right = PJ_IO_UNITS_PROJECTED; |
1011 | 492 | } else { |
1012 | 492 | P->left = PJ_IO_UNITS_RADIANS; |
1013 | 492 | P->right = PJ_IO_UNITS_RADIANS; |
1014 | 492 | } |
1015 | 492 | } else { |
1016 | 158 | P->left = PJ_IO_UNITS_WHATEVER; |
1017 | 158 | P->right = PJ_IO_UNITS_WHATEVER; |
1018 | 158 | } |
1019 | | |
1020 | 650 | return P; |
1021 | 650 | } |
1022 | | |
1023 | | // --------------------------------------------------------------------------- |
1024 | | |
1025 | 0 | void pj_clear_gridshift_knowngrids_cache() { |
1026 | 0 | std::lock_guard<std::mutex> lock(gMutex); |
1027 | 0 | gKnownGrids.clear(); |
1028 | 0 | } |