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