/src/PROJ/src/transformations/defmodel.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /****************************************************************************** |
2 | | * Project: PROJ |
3 | | * Purpose: Functionality related to deformation model |
4 | | * Author: Even Rouault, <even.rouault at spatialys.com> |
5 | | * |
6 | | ****************************************************************************** |
7 | | * Copyright (c) 2020, 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 | | #define PROJ_COMPILATION |
29 | | |
30 | | #include "defmodel.hpp" |
31 | | #include "filemanager.hpp" |
32 | | #include "grids.hpp" |
33 | | #include "proj_internal.h" |
34 | | |
35 | | #include <assert.h> |
36 | | |
37 | | #include <map> |
38 | | #include <memory> |
39 | | #include <utility> |
40 | | |
41 | | PROJ_HEAD(defmodel, "Deformation model"); |
42 | | |
43 | | using namespace DeformationModel; |
44 | | |
45 | | namespace { |
46 | | |
47 | | struct Grid : public GridPrototype { |
48 | | PJ_CONTEXT *ctx; |
49 | | const NS_PROJ::GenericShiftGrid *realGrid; |
50 | | mutable bool checkedHorizontal = false; |
51 | | mutable bool checkedVertical = false; |
52 | | mutable int sampleX = 0; |
53 | | mutable int sampleY = 1; |
54 | | mutable int sampleZ = 2; |
55 | | |
56 | | Grid(PJ_CONTEXT *ctxIn, const NS_PROJ::GenericShiftGrid *realGridIn) |
57 | 0 | : ctx(ctxIn), realGrid(realGridIn) { |
58 | 0 | minx = realGridIn->extentAndRes().west; |
59 | 0 | miny = realGridIn->extentAndRes().south; |
60 | 0 | resx = realGridIn->extentAndRes().resX; |
61 | 0 | resy = realGridIn->extentAndRes().resY; |
62 | 0 | width = realGridIn->width(); |
63 | 0 | height = realGridIn->height(); |
64 | 0 | } |
65 | | |
66 | 0 | bool checkHorizontal(const std::string &expectedUnit) const { |
67 | 0 | if (!checkedHorizontal) { |
68 | 0 | const auto samplesPerPixel = realGrid->samplesPerPixel(); |
69 | 0 | if (samplesPerPixel < 2) { |
70 | 0 | pj_log(ctx, PJ_LOG_ERROR, "grid %s has not enough samples", |
71 | 0 | realGrid->name().c_str()); |
72 | 0 | return false; |
73 | 0 | } |
74 | 0 | bool foundDescX = false; |
75 | 0 | bool foundDescY = false; |
76 | 0 | bool foundDesc = false; |
77 | 0 | for (int i = 0; i < samplesPerPixel; i++) { |
78 | 0 | const auto desc = realGrid->description(i); |
79 | 0 | if (desc == "east_offset") { |
80 | 0 | sampleX = i; |
81 | 0 | foundDescX = true; |
82 | 0 | } else if (desc == "north_offset") { |
83 | 0 | sampleY = i; |
84 | 0 | foundDescY = true; |
85 | 0 | } |
86 | 0 | if (!desc.empty()) { |
87 | 0 | foundDesc = true; |
88 | 0 | } |
89 | 0 | } |
90 | 0 | if (foundDesc && (!foundDescX || !foundDescY)) { |
91 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
92 | 0 | "grid %s : Found band description, " |
93 | 0 | "but not the ones expected", |
94 | 0 | realGrid->name().c_str()); |
95 | 0 | return false; |
96 | 0 | } |
97 | 0 | const auto unit = realGrid->unit(sampleX); |
98 | 0 | if (!unit.empty() && unit != expectedUnit) { |
99 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
100 | 0 | "grid %s : Only unit=%s " |
101 | 0 | "currently handled for this mode", |
102 | 0 | realGrid->name().c_str(), expectedUnit.c_str()); |
103 | 0 | return false; |
104 | 0 | } |
105 | 0 | checkedHorizontal = true; |
106 | 0 | } |
107 | 0 | return true; |
108 | 0 | } |
109 | | |
110 | | bool getLongLatOffset(int ix, int iy, double &longOffsetRadian, |
111 | 0 | double &latOffsetRadian) const { |
112 | 0 | if (!checkHorizontal(STR_DEGREE)) { |
113 | 0 | return false; |
114 | 0 | } |
115 | 0 | float longOffsetDeg; |
116 | 0 | float latOffsetDeg; |
117 | 0 | if (!realGrid->valueAt(ix, iy, sampleX, longOffsetDeg) || |
118 | 0 | !realGrid->valueAt(ix, iy, sampleY, latOffsetDeg)) { |
119 | 0 | return false; |
120 | 0 | } |
121 | 0 | longOffsetRadian = longOffsetDeg * DEG_TO_RAD; |
122 | 0 | latOffsetRadian = latOffsetDeg * DEG_TO_RAD; |
123 | 0 | return true; |
124 | 0 | } |
125 | | |
126 | 0 | bool getZOffset(int ix, int iy, double &zOffset) const { |
127 | 0 | if (!checkedVertical) { |
128 | 0 | const auto samplesPerPixel = realGrid->samplesPerPixel(); |
129 | 0 | if (samplesPerPixel == 1) { |
130 | 0 | sampleZ = 0; |
131 | 0 | } else if (samplesPerPixel < 3) { |
132 | 0 | pj_log(ctx, PJ_LOG_ERROR, "grid %s has not enough samples", |
133 | 0 | realGrid->name().c_str()); |
134 | 0 | return false; |
135 | 0 | } |
136 | 0 | bool foundDesc = false; |
137 | 0 | bool foundDescZ = false; |
138 | 0 | for (int i = 0; i < samplesPerPixel; i++) { |
139 | 0 | const auto desc = realGrid->description(i); |
140 | 0 | if (desc == "vertical_offset") { |
141 | 0 | sampleZ = i; |
142 | 0 | foundDescZ = true; |
143 | 0 | } |
144 | 0 | if (!desc.empty()) { |
145 | 0 | foundDesc = true; |
146 | 0 | } |
147 | 0 | } |
148 | 0 | if (foundDesc && !foundDescZ) { |
149 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
150 | 0 | "grid %s : Found band description, " |
151 | 0 | "but not the ones expected", |
152 | 0 | realGrid->name().c_str()); |
153 | 0 | return false; |
154 | 0 | } |
155 | 0 | const auto unit = realGrid->unit(sampleZ); |
156 | 0 | if (!unit.empty() && unit != STR_METRE) { |
157 | 0 | pj_log(ctx, PJ_LOG_ERROR, |
158 | 0 | "grid %s : Only unit=metre currently " |
159 | 0 | "handled for this mode", |
160 | 0 | realGrid->name().c_str()); |
161 | 0 | return false; |
162 | 0 | } |
163 | 0 | checkedVertical = true; |
164 | 0 | } |
165 | 0 | float zOffsetFloat = 0.0f; |
166 | 0 | const bool ret = realGrid->valueAt(ix, iy, sampleZ, zOffsetFloat); |
167 | 0 | zOffset = zOffsetFloat; |
168 | 0 | return ret; |
169 | 0 | } |
170 | | |
171 | | bool getEastingNorthingOffset(int ix, int iy, double &eastingOffset, |
172 | 0 | double &northingOffset) const { |
173 | 0 | if (!checkHorizontal(STR_METRE)) { |
174 | 0 | return false; |
175 | 0 | } |
176 | 0 | float eastingOffsetFloat = 0.0f; |
177 | 0 | float northingOffsetFloat = 0.0f; |
178 | 0 | const bool ret = |
179 | 0 | realGrid->valueAt(ix, iy, sampleX, eastingOffsetFloat) && |
180 | 0 | realGrid->valueAt(ix, iy, sampleY, northingOffsetFloat); |
181 | 0 | eastingOffset = eastingOffsetFloat; |
182 | 0 | northingOffset = northingOffsetFloat; |
183 | 0 | return ret; |
184 | 0 | } |
185 | | |
186 | | bool getLongLatZOffset(int ix, int iy, double &longOffsetRadian, |
187 | 0 | double &latOffsetRadian, double &zOffset) const { |
188 | 0 | return getLongLatOffset(ix, iy, longOffsetRadian, latOffsetRadian) && |
189 | 0 | getZOffset(ix, iy, zOffset); |
190 | 0 | } |
191 | | |
192 | | bool getEastingNorthingZOffset(int ix, int iy, double &eastingOffset, |
193 | | double &northingOffset, |
194 | 0 | double &zOffset) const { |
195 | 0 | return getEastingNorthingOffset(ix, iy, eastingOffset, |
196 | 0 | northingOffset) && |
197 | 0 | getZOffset(ix, iy, zOffset); |
198 | 0 | } |
199 | | |
200 | | #ifdef DEBUG_DEFMODEL |
201 | | std::string name() const { return realGrid->name(); } |
202 | | #endif |
203 | | |
204 | | private: |
205 | | Grid(const Grid &) = delete; |
206 | | Grid &operator=(const Grid &) = delete; |
207 | | }; |
208 | | |
209 | | struct GridSet : public GridSetPrototype<Grid> { |
210 | | |
211 | | PJ_CONTEXT *ctx; |
212 | | std::unique_ptr<NS_PROJ::GenericShiftGridSet> realGridSet; |
213 | | std::map<const NS_PROJ::GenericShiftGrid *, std::unique_ptr<Grid>> |
214 | | mapGrids{}; |
215 | | |
216 | | GridSet(PJ_CONTEXT *ctxIn, |
217 | | std::unique_ptr<NS_PROJ::GenericShiftGridSet> &&realGridSetIn) |
218 | 0 | : ctx(ctxIn), realGridSet(std::move(realGridSetIn)) {} |
219 | | |
220 | 0 | const Grid *gridAt(double x, double y) { |
221 | 0 | const NS_PROJ::GenericShiftGrid *realGrid = realGridSet->gridAt(x, y); |
222 | 0 | if (!realGrid) { |
223 | 0 | return nullptr; |
224 | 0 | } |
225 | 0 | auto iter = mapGrids.find(realGrid); |
226 | 0 | if (iter == mapGrids.end()) { |
227 | 0 | iter = mapGrids |
228 | 0 | .insert(std::pair<const NS_PROJ::GenericShiftGrid *, |
229 | 0 | std::unique_ptr<Grid>>( |
230 | 0 | realGrid, |
231 | 0 | std::unique_ptr<Grid>(new Grid(ctx, realGrid)))) |
232 | 0 | .first; |
233 | 0 | } |
234 | 0 | return iter->second.get(); |
235 | 0 | } |
236 | | |
237 | | private: |
238 | | GridSet(const GridSet &) = delete; |
239 | | GridSet &operator=(const GridSet &) = delete; |
240 | | }; |
241 | | |
242 | | struct EvaluatorIface : public EvaluatorIfacePrototype<Grid, GridSet> { |
243 | | |
244 | | PJ_CONTEXT *ctx; |
245 | | PJ *cart; |
246 | | |
247 | 9 | EvaluatorIface(PJ_CONTEXT *ctxIn, PJ *cartIn) : ctx(ctxIn), cart(cartIn) {} |
248 | | |
249 | 9 | ~EvaluatorIface() { |
250 | 9 | if (cart) |
251 | 9 | cart->destructor(cart, 0); |
252 | 9 | } |
253 | | |
254 | 0 | std::unique_ptr<GridSet> open(const std::string &filename) { |
255 | 0 | auto realGridSet = NS_PROJ::GenericShiftGridSet::open(ctx, filename); |
256 | 0 | if (!realGridSet) { |
257 | 0 | pj_log(ctx, PJ_LOG_ERROR, "cannot open %s", filename.c_str()); |
258 | 0 | return nullptr; |
259 | 0 | } |
260 | 0 | return std::unique_ptr<GridSet>( |
261 | 0 | new GridSet(ctx, std::move(realGridSet))); |
262 | 0 | } |
263 | | |
264 | 0 | bool isGeographicCRS(const std::string &crsDef) { |
265 | 0 | PJ *P = proj_create(ctx, crsDef.c_str()); |
266 | 0 | if (P == nullptr) { |
267 | 0 | return true; // reasonable default value |
268 | 0 | } |
269 | 0 | const auto type = proj_get_type(P); |
270 | 0 | bool ret = (type == PJ_TYPE_GEOGRAPHIC_2D_CRS || |
271 | 0 | type == PJ_TYPE_GEOGRAPHIC_3D_CRS); |
272 | 0 | proj_destroy(P); |
273 | 0 | return ret; |
274 | 0 | } |
275 | | |
276 | | void geographicToGeocentric(double lam, double phi, double height, double a, |
277 | | double b, double /*es*/, double &X, double &Y, |
278 | 0 | double &Z) { |
279 | 0 | (void)a; |
280 | 0 | (void)b; |
281 | 0 | assert(cart->a == a); |
282 | 0 | assert(cart->b == b); |
283 | 0 | PJ_LPZ lpz; |
284 | 0 | lpz.lam = lam; |
285 | 0 | lpz.phi = phi; |
286 | 0 | lpz.z = height; |
287 | 0 | PJ_XYZ xyz = cart->fwd3d(lpz, cart); |
288 | 0 | X = xyz.x; |
289 | 0 | Y = xyz.y; |
290 | 0 | Z = xyz.z; |
291 | 0 | } |
292 | | |
293 | | void geocentricToGeographic(double X, double Y, double Z, double a, |
294 | | double b, double /*es*/, double &lam, |
295 | 0 | double &phi, double &height) { |
296 | 0 | (void)a; |
297 | 0 | (void)b; |
298 | 0 | assert(cart->a == a); |
299 | 0 | assert(cart->b == b); |
300 | 0 | PJ_XYZ xyz; |
301 | 0 | xyz.x = X; |
302 | 0 | xyz.y = Y; |
303 | 0 | xyz.z = Z; |
304 | 0 | PJ_LPZ lpz = cart->inv3d(xyz, cart); |
305 | 0 | lam = lpz.lam; |
306 | 0 | phi = lpz.phi; |
307 | 0 | height = lpz.z; |
308 | 0 | } |
309 | | |
310 | | #ifdef DEBUG_DEFMODEL |
311 | | void log(const std::string &msg) { |
312 | | pj_log(ctx, PJ_LOG_TRACE, "%s", msg.c_str()); |
313 | | } |
314 | | #endif |
315 | | |
316 | | private: |
317 | | EvaluatorIface(const EvaluatorIface &) = delete; |
318 | | EvaluatorIface &operator=(const EvaluatorIface &) = delete; |
319 | | }; |
320 | | |
321 | | struct defmodelData { |
322 | | std::unique_ptr<Evaluator<Grid, GridSet, EvaluatorIface>> evaluator{}; |
323 | | EvaluatorIface evaluatorIface; |
324 | | |
325 | | explicit defmodelData(PJ_CONTEXT *ctx, PJ *cart) |
326 | 9 | : evaluatorIface(ctx, cart) {} |
327 | | |
328 | | defmodelData(const defmodelData &) = delete; |
329 | | defmodelData &operator=(const defmodelData &) = delete; |
330 | | }; |
331 | | |
332 | | } // namespace |
333 | | |
334 | 9 | static PJ *destructor(PJ *P, int errlev) { |
335 | 9 | if (nullptr == P) |
336 | 0 | return nullptr; |
337 | | |
338 | 9 | auto Q = static_cast<struct defmodelData *>(P->opaque); |
339 | 9 | delete Q; |
340 | 9 | P->opaque = nullptr; |
341 | | |
342 | 9 | return pj_default_destructor(P, errlev); |
343 | 9 | } |
344 | | |
345 | 0 | static void forward_4d(PJ_COORD &coo, PJ *P) { |
346 | 0 | auto *Q = (struct defmodelData *)P->opaque; |
347 | |
|
348 | 0 | if (coo.xyzt.t == HUGE_VAL) { |
349 | 0 | coo = proj_coord_error(); |
350 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_MISSING_TIME); |
351 | 0 | return; |
352 | 0 | } |
353 | | |
354 | 0 | if (!Q->evaluator->forward(Q->evaluatorIface, coo.xyzt.x, coo.xyzt.y, |
355 | 0 | coo.xyzt.z, coo.xyzt.t, coo.xyzt.x, coo.xyzt.y, |
356 | 0 | coo.xyzt.z)) { |
357 | 0 | coo = proj_coord_error(); |
358 | 0 | } |
359 | 0 | } |
360 | | |
361 | 0 | static void reverse_4d(PJ_COORD &coo, PJ *P) { |
362 | 0 | auto *Q = (struct defmodelData *)P->opaque; |
363 | |
|
364 | 0 | if (coo.xyzt.t == HUGE_VAL) { |
365 | 0 | coo = proj_coord_error(); |
366 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_MISSING_TIME); |
367 | 0 | return; |
368 | 0 | } |
369 | | |
370 | 0 | if (!Q->evaluator->inverse(Q->evaluatorIface, coo.xyzt.x, coo.xyzt.y, |
371 | 0 | coo.xyzt.z, coo.xyzt.t, coo.xyzt.x, coo.xyzt.y, |
372 | 0 | coo.xyzt.z)) { |
373 | 0 | coo = proj_coord_error(); |
374 | 0 | } |
375 | 0 | } |
376 | | |
377 | | // Function called by proj_assign_context() when a new context is assigned to |
378 | | // an existing PJ object. Mostly to deal with objects being passed between |
379 | | // threads. |
380 | 0 | static void reassign_context(PJ *P, PJ_CONTEXT *ctx) { |
381 | 0 | auto *Q = (struct defmodelData *)P->opaque; |
382 | 0 | if (Q->evaluatorIface.ctx != ctx) { |
383 | 0 | Q->evaluator->clearGridCache(); |
384 | 0 | Q->evaluatorIface.ctx = ctx; |
385 | 0 | } |
386 | 0 | } |
387 | | |
388 | 9 | PJ *PJ_TRANSFORMATION(defmodel, 1) { |
389 | | // Pass a dummy ellipsoid definition that will be overridden just afterwards |
390 | 9 | auto cart = proj_create(P->ctx, "+proj=cart +a=1"); |
391 | 9 | if (cart == nullptr) |
392 | 0 | return destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
393 | | |
394 | | /* inherit ellipsoid definition from P to Q->cart */ |
395 | 9 | pj_inherit_ellipsoid_def(P, cart); |
396 | | |
397 | 9 | auto Q = new defmodelData(P->ctx, cart); |
398 | 9 | P->opaque = (void *)Q; |
399 | 9 | P->destructor = destructor; |
400 | 9 | P->reassign_context = reassign_context; |
401 | | |
402 | 9 | const char *model = pj_param(P->ctx, P->params, "smodel").s; |
403 | 9 | if (!model) { |
404 | 3 | proj_log_error(P, _("+model= should be specified.")); |
405 | 3 | return destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
406 | 3 | } |
407 | | |
408 | 6 | auto file = NS_PROJ::FileManager::open_resource_file(P->ctx, model); |
409 | 6 | if (nullptr == file) { |
410 | 3 | proj_log_error(P, _("Cannot open %s"), model); |
411 | 3 | return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
412 | 3 | } |
413 | 3 | file->seek(0, SEEK_END); |
414 | 3 | unsigned long long size = file->tell(); |
415 | | // Arbitrary threshold to avoid ingesting an arbitrarily large JSON file, |
416 | | // that could be a denial of service risk. 10 MB should be sufficiently |
417 | | // large for any valid use ! |
418 | 3 | if (size > 10 * 1024 * 1024) { |
419 | 0 | proj_log_error(P, _("File %s too large"), model); |
420 | 0 | return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
421 | 0 | } |
422 | 3 | file->seek(0); |
423 | 3 | std::string jsonStr; |
424 | 3 | jsonStr.resize(static_cast<size_t>(size)); |
425 | 3 | if (file->read(&jsonStr[0], jsonStr.size()) != jsonStr.size()) { |
426 | 0 | proj_log_error(P, _("Cannot read %s"), model); |
427 | 0 | return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
428 | 0 | } |
429 | | |
430 | 3 | try { |
431 | 3 | Q->evaluator.reset(new Evaluator<Grid, GridSet, EvaluatorIface>( |
432 | 3 | MasterFile::parse(jsonStr), Q->evaluatorIface, P->a, P->b)); |
433 | 3 | } catch (const std::exception &e) { |
434 | 3 | proj_log_error(P, _("invalid model: %s"), e.what()); |
435 | 3 | return destructor(P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
436 | 3 | } |
437 | | |
438 | 0 | P->fwd4d = forward_4d; |
439 | 0 | P->inv4d = reverse_4d; |
440 | |
|
441 | 0 | if (Q->evaluator->isGeographicCRS()) { |
442 | 0 | P->left = PJ_IO_UNITS_RADIANS; |
443 | 0 | P->right = PJ_IO_UNITS_RADIANS; |
444 | 0 | } else { |
445 | 0 | P->left = PJ_IO_UNITS_PROJECTED; |
446 | 0 | P->right = PJ_IO_UNITS_PROJECTED; |
447 | 0 | } |
448 | |
|
449 | 0 | return P; |
450 | 3 | } |