/src/proj/src/transformations/deformation.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /*********************************************************************** |
2 | | |
3 | | Kinematic datum shifting utilizing a deformation model |
4 | | |
5 | | Kristian Evers, 2017-10-29 |
6 | | |
7 | | ************************************************************************ |
8 | | |
9 | | Perform datum shifts by means of a deformation/velocity model. |
10 | | |
11 | | X_out = X_in + (T_obs - T_epoch) * DX |
12 | | Y_out = Y_in + (T_obs - T_epoch) * DY |
13 | | Z_out = Z_in + (T_obs - T_epoch) * DZ |
14 | | |
15 | | The deformation operation takes cartesian coordinates as input and |
16 | | returns cartesian coordinates as well. |
17 | | |
18 | | Corrections in the gridded model are in east, north, up (ENU) space. |
19 | | Hence the input coordinates need to be converted to ENU-space when |
20 | | searching for corrections in the grid. The corrections are then converted |
21 | | to cartesian PJ_XYZ-space and applied to the input coordinates (also in |
22 | | cartesian space). |
23 | | |
24 | | A full deformation model is preferably represented as a 3 channel Geodetic |
25 | | TIFF Grid, but was historically described by a set of two grids: One for |
26 | | the horizontal components and one for the vertical component. |
27 | | |
28 | | The east and north components are (were) stored using the CTable/CTable2 |
29 | | format, up component is (was) stored in the GTX format. Both grids are |
30 | | (were) expected to contain grid-values in units of mm/year in ENU-space. |
31 | | |
32 | | ************************************************************************ |
33 | | * Copyright (c) 2017, Kristian Evers |
34 | | * |
35 | | * Permission is hereby granted, free of charge, to any person obtaining a |
36 | | * copy of this software and associated documentation files (the "Software"), |
37 | | * to deal in the Software without restriction, including without limitation |
38 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
39 | | * and/or sell copies of the Software, and to permit persons to whom the |
40 | | * Software is furnished to do so, subject to the following conditions: |
41 | | * |
42 | | * The above copyright notice and this permission notice shall be included |
43 | | * in all copies or substantial portions of the Software. |
44 | | * |
45 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
46 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
47 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
48 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
49 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
50 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
51 | | * DEALINGS IN THE SOFTWARE. |
52 | | * |
53 | | ***********************************************************************/ |
54 | | |
55 | | #include "grids.hpp" |
56 | | #include "proj.h" |
57 | | #include "proj_internal.h" |
58 | | #include <errno.h> |
59 | | #include <math.h> |
60 | | |
61 | | #include <algorithm> |
62 | | |
63 | | PROJ_HEAD(deformation, "Kinematic grid shift"); |
64 | | |
65 | 0 | #define TOL 1e-8 |
66 | 0 | #define MAX_ITERATIONS 10 |
67 | | |
68 | | using namespace NS_PROJ; |
69 | | |
70 | | namespace { // anonymous namespace |
71 | | struct deformationData { |
72 | | double dt = 0; |
73 | | double t_epoch = 0; |
74 | | PJ *cart = nullptr; |
75 | | ListOfGenericGrids grids{}; |
76 | | ListOfHGrids hgrids{}; |
77 | | ListOfVGrids vgrids{}; |
78 | | }; |
79 | | } // anonymous namespace |
80 | | |
81 | | // --------------------------------------------------------------------------- |
82 | | |
83 | | static bool pj_deformation_get_grid_values(PJ *P, deformationData *Q, |
84 | | const PJ_LP &lp, double &vx, |
85 | 0 | double &vy, double &vz) { |
86 | 0 | GenericShiftGridSet *gridset = nullptr; |
87 | 0 | auto grid = pj_find_generic_grid(Q->grids, lp, gridset); |
88 | 0 | if (!grid) { |
89 | 0 | return false; |
90 | 0 | } |
91 | 0 | if (grid->isNullGrid()) { |
92 | 0 | vx = 0; |
93 | 0 | vy = 0; |
94 | 0 | vz = 0; |
95 | 0 | return true; |
96 | 0 | } |
97 | 0 | const auto samplesPerPixel = grid->samplesPerPixel(); |
98 | 0 | if (samplesPerPixel < 3) { |
99 | 0 | proj_log_error(P, "grid has not enough samples"); |
100 | 0 | return false; |
101 | 0 | } |
102 | 0 | int sampleE = 0; |
103 | 0 | int sampleN = 1; |
104 | 0 | int sampleU = 2; |
105 | 0 | for (int i = 0; i < samplesPerPixel; i++) { |
106 | 0 | const auto desc = grid->description(i); |
107 | 0 | if (desc == "east_velocity") { |
108 | 0 | sampleE = i; |
109 | 0 | } else if (desc == "north_velocity") { |
110 | 0 | sampleN = i; |
111 | 0 | } else if (desc == "up_velocity") { |
112 | 0 | sampleU = i; |
113 | 0 | } |
114 | 0 | } |
115 | 0 | const auto unit = grid->unit(sampleE); |
116 | 0 | if (!unit.empty() && unit != "millimetres per year") { |
117 | 0 | proj_log_error(P, "Only unit=millimetres per year currently handled"); |
118 | 0 | return false; |
119 | 0 | } |
120 | | |
121 | 0 | bool must_retry = false; |
122 | 0 | if (!pj_bilinear_interpolation_three_samples(P->ctx, grid, lp, sampleE, |
123 | 0 | sampleN, sampleU, vx, vy, vz, |
124 | 0 | must_retry)) { |
125 | 0 | if (must_retry) |
126 | 0 | return pj_deformation_get_grid_values(P, Q, lp, vx, vy, vz); |
127 | 0 | return false; |
128 | 0 | } |
129 | | // divide by 1000 to get m/year |
130 | 0 | vx /= 1000; |
131 | 0 | vy /= 1000; |
132 | 0 | vz /= 1000; |
133 | 0 | return true; |
134 | 0 | } |
135 | | |
136 | | /********************************************************************************/ |
137 | 0 | static PJ_XYZ pj_deformation_get_grid_shift(PJ *P, const PJ_XYZ &cartesian) { |
138 | | /******************************************************************************** |
139 | | Read correction values from grid. The cartesian input coordinates are |
140 | | converted to geodetic coordinates in order look up the correction values |
141 | | in the grid. Once the grid corrections are read we need to convert them |
142 | | from ENU-space to cartesian PJ_XYZ-space. ENU -> PJ_XYZ formula |
143 | | described in: |
144 | | |
145 | | Nørbech, T., et al, 2003(?), "Transformation from a Common Nordic |
146 | | Reference Frame to ETRS89 in Denmark, Finland, Norway, and Sweden – status |
147 | | report" |
148 | | |
149 | | ********************************************************************************/ |
150 | 0 | PJ_COORD geodetic, shift, temp; |
151 | 0 | double sp, cp, sl, cl; |
152 | 0 | int previous_errno = proj_errno_reset(P); |
153 | 0 | auto Q = static_cast<deformationData *>(P->opaque); |
154 | | |
155 | | /* cartesian to geodetic */ |
156 | 0 | geodetic.lpz = pj_inv3d(cartesian, Q->cart); |
157 | | |
158 | | /* look up correction values in grids */ |
159 | 0 | if (!Q->grids.empty()) { |
160 | 0 | double vx = 0; |
161 | 0 | double vy = 0; |
162 | 0 | double vz = 0; |
163 | 0 | if (!pj_deformation_get_grid_values(P, Q, geodetic.lp, vx, vy, vz)) { |
164 | 0 | return proj_coord_error().xyz; |
165 | 0 | } |
166 | 0 | shift.xyz.x = vx; |
167 | 0 | shift.xyz.y = vy; |
168 | 0 | shift.xyz.z = vz; |
169 | 0 | } else { |
170 | 0 | shift.lp = pj_hgrid_value(P, Q->hgrids, geodetic.lp); |
171 | 0 | shift.enu.u = pj_vgrid_value(P, Q->vgrids, geodetic.lp, 1.0); |
172 | |
|
173 | 0 | if (proj_errno(P) == PROJ_ERR_COORD_TRANSFM_OUTSIDE_GRID) |
174 | 0 | proj_log_debug( |
175 | 0 | P, "coordinate (%.3f, %.3f) outside deformation model", |
176 | 0 | proj_todeg(geodetic.lpz.lam), proj_todeg(geodetic.lpz.phi)); |
177 | | |
178 | | /* grid values are stored as mm/yr, we need m/yr */ |
179 | 0 | shift.xyz.x /= 1000; |
180 | 0 | shift.xyz.y /= 1000; |
181 | 0 | shift.xyz.z /= 1000; |
182 | 0 | } |
183 | | |
184 | | /* pre-calc cosines and sines */ |
185 | 0 | sp = sin(geodetic.lpz.phi); |
186 | 0 | cp = cos(geodetic.lpz.phi); |
187 | 0 | sl = sin(geodetic.lpz.lam); |
188 | 0 | cl = cos(geodetic.lpz.lam); |
189 | | |
190 | | /* ENU -> PJ_XYZ */ |
191 | 0 | temp.xyz.x = |
192 | 0 | -sp * cl * shift.enu.n - sl * shift.enu.e + cp * cl * shift.enu.u; |
193 | 0 | temp.xyz.y = |
194 | 0 | -sp * sl * shift.enu.n + cl * shift.enu.e + cp * sl * shift.enu.u; |
195 | 0 | temp.xyz.z = cp * shift.enu.n + sp * shift.enu.u; |
196 | |
|
197 | 0 | shift.xyz = temp.xyz; |
198 | |
|
199 | 0 | proj_errno_restore(P, previous_errno); |
200 | |
|
201 | 0 | return shift.xyz; |
202 | 0 | } |
203 | | |
204 | | /********************************************************************************/ |
205 | | static PJ_XYZ pj_deformation_reverse_shift(PJ *P, const PJ_XYZ &input, |
206 | 0 | double dt) { |
207 | | /******************************************************************************** |
208 | | Iteratively determine the reverse grid shift correction values. |
209 | | *********************************************************************************/ |
210 | 0 | PJ_XYZ out, delta, dif; |
211 | 0 | double z0; |
212 | 0 | int i = MAX_ITERATIONS; |
213 | |
|
214 | 0 | delta = pj_deformation_get_grid_shift(P, input); |
215 | 0 | if (delta.x == HUGE_VAL) { |
216 | 0 | return delta; |
217 | 0 | } |
218 | | |
219 | | /* Store the original z shift for later application */ |
220 | 0 | z0 = delta.z; |
221 | | |
222 | | /* When iterating to find the best horizontal coordinate we also carry */ |
223 | | /* along the z-component, since we need it for the cartesian -> geodetic */ |
224 | | /* conversion. The z-component adjustment is overwritten with z0 after */ |
225 | | /* the loop has finished. */ |
226 | 0 | out.x = input.x - dt * delta.x; |
227 | 0 | out.y = input.y - dt * delta.y; |
228 | 0 | out.z = input.z + dt * delta.z; |
229 | |
|
230 | 0 | do { |
231 | 0 | delta = pj_deformation_get_grid_shift(P, out); |
232 | |
|
233 | 0 | if (delta.x == HUGE_VAL) |
234 | 0 | break; |
235 | | |
236 | 0 | dif.x = out.x + dt * delta.x - input.x; |
237 | 0 | dif.y = out.y + dt * delta.y - input.y; |
238 | 0 | dif.z = out.z - dt * delta.z - input.z; |
239 | 0 | out.x += dif.x; |
240 | 0 | out.y += dif.y; |
241 | 0 | out.z += dif.z; |
242 | |
|
243 | 0 | } while (--i && hypot(dif.x, dif.y) > TOL); |
244 | | |
245 | 0 | out.z = input.z - dt * z0; |
246 | |
|
247 | 0 | return out; |
248 | 0 | } |
249 | | |
250 | 0 | static PJ_XYZ pj_deformation_forward_3d(PJ_LPZ lpz, PJ *P) { |
251 | 0 | struct deformationData *Q = (struct deformationData *)P->opaque; |
252 | 0 | PJ_COORD out, in; |
253 | 0 | PJ_XYZ shift; |
254 | 0 | in.lpz = lpz; |
255 | 0 | out = in; |
256 | |
|
257 | 0 | if (Q->dt == HUGE_VAL) { |
258 | 0 | out = proj_coord_error(); /* in the 3D case +t_obs must be specified */ |
259 | 0 | proj_log_debug(P, "+dt must be specified"); |
260 | 0 | return out.xyz; |
261 | 0 | } |
262 | | |
263 | 0 | shift = pj_deformation_get_grid_shift(P, in.xyz); |
264 | 0 | if (shift.x == HUGE_VAL) { |
265 | 0 | return shift; |
266 | 0 | } |
267 | | |
268 | 0 | out.xyz.x += Q->dt * shift.x; |
269 | 0 | out.xyz.y += Q->dt * shift.y; |
270 | 0 | out.xyz.z += Q->dt * shift.z; |
271 | |
|
272 | 0 | return out.xyz; |
273 | 0 | } |
274 | | |
275 | 0 | static void pj_deformation_forward_4d(PJ_COORD &coo, PJ *P) { |
276 | 0 | struct deformationData *Q = (struct deformationData *)P->opaque; |
277 | 0 | double dt; |
278 | 0 | PJ_XYZ shift; |
279 | |
|
280 | 0 | if (Q->dt != HUGE_VAL) { |
281 | 0 | dt = Q->dt; |
282 | 0 | } else { |
283 | 0 | if (coo.xyzt.t == HUGE_VAL) { |
284 | 0 | coo = proj_coord_error(); |
285 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_MISSING_TIME); |
286 | 0 | return; |
287 | 0 | } |
288 | 0 | dt = coo.xyzt.t - Q->t_epoch; |
289 | 0 | } |
290 | | |
291 | 0 | shift = pj_deformation_get_grid_shift(P, coo.xyz); |
292 | |
|
293 | 0 | coo.xyzt.x += dt * shift.x; |
294 | 0 | coo.xyzt.y += dt * shift.y; |
295 | 0 | coo.xyzt.z += dt * shift.z; |
296 | 0 | } |
297 | | |
298 | 0 | static PJ_LPZ pj_deformation_reverse_3d(PJ_XYZ in, PJ *P) { |
299 | 0 | struct deformationData *Q = (struct deformationData *)P->opaque; |
300 | 0 | PJ_COORD out; |
301 | 0 | out.xyz = in; |
302 | |
|
303 | 0 | if (Q->dt == HUGE_VAL) { |
304 | 0 | out = proj_coord_error(); /* in the 3D case +t_obs must be specified */ |
305 | 0 | proj_log_debug(P, "+dt must be specified"); |
306 | 0 | return out.lpz; |
307 | 0 | } |
308 | | |
309 | 0 | out.xyz = pj_deformation_reverse_shift(P, in, Q->dt); |
310 | |
|
311 | 0 | return out.lpz; |
312 | 0 | } |
313 | | |
314 | 0 | static void pj_deformation_reverse_4d(PJ_COORD &coo, PJ *P) { |
315 | 0 | struct deformationData *Q = (struct deformationData *)P->opaque; |
316 | 0 | double dt; |
317 | |
|
318 | 0 | if (Q->dt != HUGE_VAL) { |
319 | 0 | dt = Q->dt; |
320 | 0 | } else { |
321 | 0 | if (coo.xyzt.t == HUGE_VAL) { |
322 | 0 | coo = proj_coord_error(); |
323 | 0 | proj_errno_set(P, PROJ_ERR_COORD_TRANSFM_MISSING_TIME); |
324 | 0 | return; |
325 | 0 | } |
326 | 0 | dt = coo.xyzt.t - Q->t_epoch; |
327 | 0 | } |
328 | | |
329 | 0 | coo.xyz = pj_deformation_reverse_shift(P, coo.xyz, dt); |
330 | 0 | } |
331 | | |
332 | 84 | static PJ *pj_deformation_destructor(PJ *P, int errlev) { |
333 | 84 | if (nullptr == P) |
334 | 0 | return nullptr; |
335 | | |
336 | 84 | auto Q = static_cast<struct deformationData *>(P->opaque); |
337 | 84 | if (Q) { |
338 | 84 | if (Q->cart) |
339 | 84 | Q->cart->destructor(Q->cart, errlev); |
340 | 84 | delete Q; |
341 | 84 | } |
342 | 84 | P->opaque = nullptr; |
343 | | |
344 | 84 | return pj_default_destructor(P, errlev); |
345 | 84 | } |
346 | | |
347 | 84 | PJ *PJ_TRANSFORMATION(deformation, 1) { |
348 | 84 | auto Q = new deformationData; |
349 | 84 | P->opaque = (void *)Q; |
350 | 84 | P->destructor = pj_deformation_destructor; |
351 | | |
352 | | // Pass a dummy ellipsoid definition that will be overridden just afterwards |
353 | 84 | Q->cart = proj_create(P->ctx, "+proj=cart +a=1"); |
354 | 84 | if (Q->cart == nullptr) |
355 | 0 | return pj_deformation_destructor(P, PROJ_ERR_OTHER /*ENOMEM*/); |
356 | | |
357 | | /* inherit ellipsoid definition from P to Q->cart */ |
358 | 84 | pj_inherit_ellipsoid_def(P, Q->cart); |
359 | | |
360 | 84 | int has_xy_grids = pj_param(P->ctx, P->params, "txy_grids").i; |
361 | 84 | int has_z_grids = pj_param(P->ctx, P->params, "tz_grids").i; |
362 | 84 | int has_grids = pj_param(P->ctx, P->params, "tgrids").i; |
363 | | |
364 | | /* Build gridlists. Both horizontal and vertical grids are mandatory. */ |
365 | 84 | if (!has_grids && (!has_xy_grids || !has_z_grids)) { |
366 | 5 | proj_log_error(P, _("Either +grids or (+xy_grids and +z_grids) should " |
367 | 5 | "be specified.")); |
368 | 5 | return pj_deformation_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
369 | 5 | } |
370 | | |
371 | 79 | if (has_grids) { |
372 | 79 | Q->grids = pj_generic_grid_init(P, "grids"); |
373 | | /* Was gridlist compiled properly? */ |
374 | 79 | if (proj_errno(P)) { |
375 | 3 | proj_log_error(P, _("could not find required grid(s).)")); |
376 | 3 | return pj_deformation_destructor( |
377 | 3 | P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
378 | 3 | } |
379 | 79 | } else { |
380 | 0 | Q->hgrids = pj_hgrid_init(P, "xy_grids"); |
381 | 0 | if (proj_errno(P)) { |
382 | 0 | proj_log_error(P, _("could not find requested xy_grid(s).")); |
383 | 0 | return pj_deformation_destructor( |
384 | 0 | P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
385 | 0 | } |
386 | | |
387 | 0 | Q->vgrids = pj_vgrid_init(P, "z_grids"); |
388 | 0 | if (proj_errno(P)) { |
389 | 0 | proj_log_error(P, _("could not find requested z_grid(s).")); |
390 | 0 | return pj_deformation_destructor( |
391 | 0 | P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
392 | 0 | } |
393 | 0 | } |
394 | | |
395 | 76 | Q->dt = HUGE_VAL; |
396 | 76 | if (pj_param(P->ctx, P->params, "tdt").i) { |
397 | 68 | Q->dt = pj_param(P->ctx, P->params, "ddt").f; |
398 | 68 | } |
399 | | |
400 | 76 | if (pj_param_exists(P->params, "t_obs")) { |
401 | 0 | proj_log_error(P, |
402 | 0 | _("+t_obs parameter is deprecated. Use +dt instead.")); |
403 | 0 | return pj_deformation_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
404 | 0 | } |
405 | | |
406 | 76 | Q->t_epoch = HUGE_VAL; |
407 | 76 | if (pj_param(P->ctx, P->params, "tt_epoch").i) { |
408 | 2 | Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f; |
409 | 2 | } |
410 | | |
411 | 76 | if (Q->dt == HUGE_VAL && Q->t_epoch == HUGE_VAL) { |
412 | 6 | proj_log_error(P, _("either +dt or +t_epoch needs to be set.")); |
413 | 6 | return pj_deformation_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
414 | 6 | } |
415 | | |
416 | 70 | if (Q->dt != HUGE_VALL && Q->t_epoch != HUGE_VALL) { |
417 | 0 | proj_log_error(P, _("+dt or +t_epoch are mutually exclusive.")); |
418 | 0 | return pj_deformation_destructor( |
419 | 0 | P, PROJ_ERR_INVALID_OP_MUTUALLY_EXCLUSIVE_ARGS); |
420 | 0 | } |
421 | | |
422 | 70 | P->fwd4d = pj_deformation_forward_4d; |
423 | 70 | P->inv4d = pj_deformation_reverse_4d; |
424 | 70 | P->fwd3d = pj_deformation_forward_3d; |
425 | 70 | P->inv3d = pj_deformation_reverse_3d; |
426 | 70 | P->fwd = nullptr; |
427 | 70 | P->inv = nullptr; |
428 | | |
429 | 70 | P->left = PJ_IO_UNITS_CARTESIAN; |
430 | 70 | P->right = PJ_IO_UNITS_CARTESIAN; |
431 | | |
432 | 70 | return P; |
433 | 70 | } |
434 | | |
435 | | #undef TOL |
436 | | #undef MAX_ITERATIONS |