/src/PROJ/src/transformations/vgridshift.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | |
2 | | |
3 | | #include <errno.h> |
4 | | #include <mutex> |
5 | | #include <stddef.h> |
6 | | #include <string.h> |
7 | | #include <time.h> |
8 | | |
9 | | #include "grids.hpp" |
10 | | #include "proj_internal.h" |
11 | | |
12 | | PROJ_HEAD(vgridshift, "Vertical grid shift"); |
13 | | |
14 | | static std::mutex gMutexVGridShift{}; |
15 | | static std::set<std::string> gKnownGridsVGridShift{}; |
16 | | |
17 | | using namespace NS_PROJ; |
18 | | |
19 | | namespace { // anonymous namespace |
20 | | struct vgridshiftData { |
21 | | double t_final = 0; |
22 | | double t_epoch = 0; |
23 | | double forward_multiplier = 0; |
24 | | ListOfVGrids grids{}; |
25 | | bool defer_grid_opening = false; |
26 | | }; |
27 | | } // anonymous namespace |
28 | | |
29 | 0 | static void deal_with_vertcon_gtx_hack(PJ *P) { |
30 | 0 | struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; |
31 | | // The .gtx VERTCON files stored millimeters, but the .tif files |
32 | | // are in metres. |
33 | 0 | if (Q->forward_multiplier != 0.001) { |
34 | 0 | return; |
35 | 0 | } |
36 | 0 | const char *gridname = pj_param(P->ctx, P->params, "sgrids").s; |
37 | 0 | if (!gridname) { |
38 | 0 | return; |
39 | 0 | } |
40 | 0 | if (strcmp(gridname, "vertconw.gtx") != 0 && |
41 | 0 | strcmp(gridname, "vertconc.gtx") != 0 && |
42 | 0 | strcmp(gridname, "vertcone.gtx") != 0) { |
43 | 0 | return; |
44 | 0 | } |
45 | 0 | if (Q->grids.empty()) { |
46 | 0 | return; |
47 | 0 | } |
48 | 0 | const auto &grids = Q->grids[0]->grids(); |
49 | 0 | if (!grids.empty() && grids[0]->name().find(".tif") != std::string::npos) { |
50 | 0 | Q->forward_multiplier = 1.0; |
51 | 0 | } |
52 | 0 | } |
53 | | |
54 | 0 | static PJ_XYZ pj_vgridshift_forward_3d(PJ_LPZ lpz, PJ *P) { |
55 | 0 | struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; |
56 | 0 | PJ_COORD point = {{0, 0, 0, 0}}; |
57 | 0 | point.lpz = lpz; |
58 | |
|
59 | 0 | if (Q->defer_grid_opening) { |
60 | 0 | Q->defer_grid_opening = false; |
61 | 0 | Q->grids = pj_vgrid_init(P, "grids"); |
62 | 0 | deal_with_vertcon_gtx_hack(P); |
63 | 0 | if (proj_errno(P)) { |
64 | 0 | return proj_coord_error().xyz; |
65 | 0 | } |
66 | 0 | } |
67 | | |
68 | 0 | if (!Q->grids.empty()) { |
69 | | /* Only try the gridshift if at least one grid is loaded, |
70 | | * otherwise just pass the coordinate through unchanged. */ |
71 | 0 | point.xyz.z += |
72 | 0 | pj_vgrid_value(P, Q->grids, point.lp, Q->forward_multiplier); |
73 | 0 | } |
74 | |
|
75 | 0 | return point.xyz; |
76 | 0 | } |
77 | | |
78 | 0 | static PJ_LPZ pj_vgridshift_reverse_3d(PJ_XYZ xyz, PJ *P) { |
79 | 0 | struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; |
80 | 0 | PJ_COORD point = {{0, 0, 0, 0}}; |
81 | 0 | point.xyz = xyz; |
82 | |
|
83 | 0 | if (Q->defer_grid_opening) { |
84 | 0 | Q->defer_grid_opening = false; |
85 | 0 | Q->grids = pj_vgrid_init(P, "grids"); |
86 | 0 | deal_with_vertcon_gtx_hack(P); |
87 | 0 | if (proj_errno(P)) { |
88 | 0 | return proj_coord_error().lpz; |
89 | 0 | } |
90 | 0 | } |
91 | | |
92 | 0 | if (!Q->grids.empty()) { |
93 | | /* Only try the gridshift if at least one grid is loaded, |
94 | | * otherwise just pass the coordinate through unchanged. */ |
95 | 0 | point.xyz.z -= |
96 | 0 | pj_vgrid_value(P, Q->grids, point.lp, Q->forward_multiplier); |
97 | 0 | } |
98 | |
|
99 | 0 | return point.lpz; |
100 | 0 | } |
101 | | |
102 | 0 | static void pj_vgridshift_forward_4d(PJ_COORD &coo, PJ *P) { |
103 | 0 | struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; |
104 | | |
105 | | /* If transformation is not time restricted, we always call it */ |
106 | 0 | if (Q->t_final == 0 || Q->t_epoch == 0) { |
107 | | // Assigning in 2 steps avoids cppcheck warning |
108 | | // "Overlapping read/write of union is undefined behavior" |
109 | | // Cf |
110 | | // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 |
111 | 0 | const auto xyz = pj_vgridshift_forward_3d(coo.lpz, P); |
112 | 0 | coo.xyz = xyz; |
113 | 0 | return; |
114 | 0 | } |
115 | | |
116 | | /* Time restricted - only apply transform if within time bracket */ |
117 | 0 | if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) { |
118 | | // Assigning in 2 steps avoids cppcheck warning |
119 | | // "Overlapping read/write of union is undefined behavior" |
120 | | // Cf |
121 | | // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 |
122 | 0 | const auto xyz = pj_vgridshift_forward_3d(coo.lpz, P); |
123 | 0 | coo.xyz = xyz; |
124 | 0 | } |
125 | 0 | } |
126 | | |
127 | 0 | static void pj_vgridshift_reverse_4d(PJ_COORD &coo, PJ *P) { |
128 | 0 | struct vgridshiftData *Q = (struct vgridshiftData *)P->opaque; |
129 | | |
130 | | /* If transformation is not time restricted, we always call it */ |
131 | 0 | if (Q->t_final == 0 || Q->t_epoch == 0) { |
132 | | // Assigning in 2 steps avoids cppcheck warning |
133 | | // "Overlapping read/write of union is undefined behavior" |
134 | | // Cf |
135 | | // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 |
136 | 0 | const auto lpz = pj_vgridshift_reverse_3d(coo.xyz, P); |
137 | 0 | coo.lpz = lpz; |
138 | 0 | return; |
139 | 0 | } |
140 | | |
141 | | /* Time restricted - only apply transform if within time bracket */ |
142 | 0 | if (coo.lpzt.t < Q->t_epoch && Q->t_final > Q->t_epoch) { |
143 | | // Assigning in 2 steps avoids cppcheck warning |
144 | | // "Overlapping read/write of union is undefined behavior" |
145 | | // Cf |
146 | | // https://github.com/OSGeo/PROJ/pull/3527#pullrequestreview-1233332710 |
147 | 0 | const auto lpz = pj_vgridshift_reverse_3d(coo.xyz, P); |
148 | 0 | coo.lpz = lpz; |
149 | 0 | } |
150 | 0 | } |
151 | | |
152 | 3.82k | static PJ *pj_vgridshift_destructor(PJ *P, int errlev) { |
153 | 3.82k | if (nullptr == P) |
154 | 0 | return nullptr; |
155 | | |
156 | 3.82k | delete static_cast<struct vgridshiftData *>(P->opaque); |
157 | 3.82k | P->opaque = nullptr; |
158 | | |
159 | 3.82k | return pj_default_destructor(P, errlev); |
160 | 3.82k | } |
161 | | |
162 | 0 | static void pj_vgridshift_reassign_context(PJ *P, PJ_CONTEXT *ctx) { |
163 | 0 | auto Q = (struct vgridshiftData *)P->opaque; |
164 | 0 | for (auto &grid : Q->grids) { |
165 | 0 | grid->reassign_context(ctx); |
166 | 0 | } |
167 | 0 | } |
168 | | |
169 | 3.82k | PJ *PJ_TRANSFORMATION(vgridshift, 0) { |
170 | 3.82k | auto Q = new vgridshiftData; |
171 | 3.82k | P->opaque = (void *)Q; |
172 | 3.82k | P->destructor = pj_vgridshift_destructor; |
173 | 3.82k | P->reassign_context = pj_vgridshift_reassign_context; |
174 | | |
175 | 3.82k | if (!pj_param(P->ctx, P->params, "tgrids").i) { |
176 | 11 | proj_log_error(P, _("+grids parameter missing.")); |
177 | 11 | return pj_vgridshift_destructor(P, PROJ_ERR_INVALID_OP_MISSING_ARG); |
178 | 11 | } |
179 | | |
180 | | /* TODO: Refactor into shared function that can be used */ |
181 | | /* by both vgridshift and hgridshift */ |
182 | 3.81k | if (pj_param(P->ctx, P->params, "tt_final").i) { |
183 | 213 | Q->t_final = pj_param(P->ctx, P->params, "dt_final").f; |
184 | 213 | if (Q->t_final == 0) { |
185 | | /* a number wasn't passed to +t_final, let's see if it was "now" */ |
186 | | /* and set the time accordingly. */ |
187 | 147 | if (!strcmp("now", pj_param(P->ctx, P->params, "st_final").s)) { |
188 | 66 | time_t now; |
189 | 66 | struct tm *date; |
190 | 66 | time(&now); |
191 | 66 | date = localtime(&now); |
192 | 66 | Q->t_final = 1900.0 + date->tm_year + date->tm_yday / 365.0; |
193 | 66 | } |
194 | 147 | } |
195 | 213 | } |
196 | | |
197 | 3.81k | if (pj_param(P->ctx, P->params, "tt_epoch").i) |
198 | 99 | Q->t_epoch = pj_param(P->ctx, P->params, "dt_epoch").f; |
199 | | |
200 | | /* historical: the forward direction subtracts the grid offset. */ |
201 | 3.81k | Q->forward_multiplier = -1.0; |
202 | 3.81k | if (pj_param(P->ctx, P->params, "tmultiplier").i) { |
203 | 2.35k | Q->forward_multiplier = pj_param(P->ctx, P->params, "dmultiplier").f; |
204 | 2.35k | } |
205 | | |
206 | 3.81k | if (P->ctx->defer_grid_opening) { |
207 | 0 | Q->defer_grid_opening = true; |
208 | 3.81k | } else { |
209 | 3.81k | const char *gridnames = pj_param(P->ctx, P->params, "sgrids").s; |
210 | 3.81k | gMutexVGridShift.lock(); |
211 | 3.81k | const bool isKnownGrid = gKnownGridsVGridShift.find(gridnames) != |
212 | 3.81k | gKnownGridsVGridShift.end(); |
213 | 3.81k | gMutexVGridShift.unlock(); |
214 | | |
215 | 3.81k | if (isKnownGrid) { |
216 | 620 | Q->defer_grid_opening = true; |
217 | 3.19k | } else { |
218 | | /* Build gridlist. P->vgridlist_geoid can be empty if +grids only |
219 | | * ask for optional grids. */ |
220 | 3.19k | Q->grids = pj_vgrid_init(P, "grids"); |
221 | | |
222 | | /* Was gridlist compiled properly? */ |
223 | 3.19k | if (proj_errno(P)) { |
224 | 2.92k | proj_log_error(P, _("could not find required grid(s).")); |
225 | 2.92k | return pj_vgridshift_destructor( |
226 | 2.92k | P, PROJ_ERR_INVALID_OP_FILE_NOT_FOUND_OR_INVALID); |
227 | 2.92k | } |
228 | | |
229 | 271 | gMutexVGridShift.lock(); |
230 | 271 | gKnownGridsVGridShift.insert(gridnames); |
231 | 271 | gMutexVGridShift.unlock(); |
232 | 271 | } |
233 | 3.81k | } |
234 | | |
235 | 891 | P->fwd4d = pj_vgridshift_forward_4d; |
236 | 891 | P->inv4d = pj_vgridshift_reverse_4d; |
237 | 891 | P->fwd3d = pj_vgridshift_forward_3d; |
238 | 891 | P->inv3d = pj_vgridshift_reverse_3d; |
239 | 891 | P->fwd = nullptr; |
240 | 891 | P->inv = nullptr; |
241 | | |
242 | 891 | P->left = PJ_IO_UNITS_RADIANS; |
243 | 891 | P->right = PJ_IO_UNITS_RADIANS; |
244 | | |
245 | 891 | return P; |
246 | 3.81k | } |
247 | | |
248 | 28.9k | void pj_clear_vgridshift_knowngrids_cache() { |
249 | 28.9k | std::lock_guard<std::mutex> lock(gMutexVGridShift); |
250 | 28.9k | gKnownGridsVGridShift.clear(); |
251 | 28.9k | } |