/src/PROJ/src/transformations/defmodel_impl.hpp
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 | | #ifndef DEFORMATON_MODEL_NAMESPACE |
29 | | #error "Should be included only by defmodel.hpp" |
30 | | #endif |
31 | | |
32 | | #include <algorithm> |
33 | | #include <limits> |
34 | | |
35 | | namespace DEFORMATON_MODEL_NAMESPACE { |
36 | | |
37 | | // --------------------------------------------------------------------------- |
38 | | |
39 | | static const std::string STR_DEGREE("degree"); |
40 | | static const std::string STR_METRE("metre"); |
41 | | |
42 | | static const std::string STR_ADDITION("addition"); |
43 | | static const std::string STR_GEOCENTRIC("geocentric"); |
44 | | |
45 | | static const std::string STR_BILINEAR("bilinear"); |
46 | | static const std::string STR_GEOCENTRIC_BILINEAR("geocentric_bilinear"); |
47 | | |
48 | | static const std::string STR_NONE("none"); |
49 | | static const std::string STR_HORIZONTAL("horizontal"); |
50 | | static const std::string STR_VERTICAL("vertical"); |
51 | | static const std::string STR_3D("3d"); |
52 | | |
53 | | constexpr double DEFMODEL_PI = 3.14159265358979323846; |
54 | | constexpr double DEG_TO_RAD_CONSTANT = 3.14159265358979323846 / 180.; |
55 | 0 | inline constexpr double DegToRad(double d) { return d * DEG_TO_RAD_CONSTANT; } |
56 | | |
57 | | // --------------------------------------------------------------------------- |
58 | | |
59 | | enum class DisplacementType { NONE, HORIZONTAL, VERTICAL, THREE_D }; |
60 | | |
61 | | // --------------------------------------------------------------------------- |
62 | | |
63 | | /** Internal class to offer caching services over a Grid */ |
64 | | template <class Grid> struct GridEx { |
65 | | const Grid *grid; |
66 | | |
67 | | bool smallResx; // lesser than one degree |
68 | | |
69 | | double sinhalfresx; |
70 | | double coshalfresx; |
71 | | |
72 | | double sinresy; |
73 | | double cosresy; |
74 | | |
75 | | int last_ix0 = -1; |
76 | | int last_iy0 = -1; |
77 | | double dX00 = 0; |
78 | | double dY00 = 0; |
79 | | double dZ00 = 0; |
80 | | double dX01 = 0; |
81 | | double dY01 = 0; |
82 | | double dZ01 = 0; |
83 | | double dX10 = 0; |
84 | | double dY10 = 0; |
85 | | double dZ10 = 0; |
86 | | double dX11 = 0; |
87 | | double dY11 = 0; |
88 | | double dZ11 = 0; |
89 | | double sinphi0 = 0; |
90 | | double cosphi0 = 0; |
91 | | double sinphi1 = 0; |
92 | | double cosphi1 = 0; |
93 | | |
94 | | explicit GridEx(const Grid *gridIn) |
95 | 0 | : grid(gridIn), smallResx(grid->resx < DegToRad(1)), |
96 | 0 | sinhalfresx(sin(grid->resx / 2)), coshalfresx(cos(grid->resx / 2)), |
97 | 0 | sinresy(sin(grid->resy)), cosresy(cos(grid->resy)) {} |
98 | | |
99 | | // Return geocentric offset (dX, dY, dZ) relative to a point |
100 | | // where x0 = -resx / 2 |
101 | | inline void getBilinearGeocentric(int ix0, int iy0, double de00, |
102 | | double dn00, double de01, double dn01, |
103 | | double de10, double dn10, double de11, |
104 | | double dn11, double m00, double m01, |
105 | | double m10, double m11, double &dX, |
106 | 0 | double &dY, double &dZ) { |
107 | | // If interpolating in the same cell as before, then we can skip |
108 | | // the recomputation of dXij, dYij and dZij |
109 | 0 | if (ix0 != last_ix0 || iy0 != last_iy0) { |
110 | |
|
111 | 0 | last_ix0 = ix0; |
112 | 0 | if (iy0 != last_iy0) { |
113 | 0 | const double y0 = grid->miny + iy0 * grid->resy; |
114 | 0 | sinphi0 = sin(y0); |
115 | 0 | cosphi0 = cos(y0); |
116 | | |
117 | | // Use trigonometric formulas to avoid new calls to sin/cos |
118 | 0 | sinphi1 = |
119 | 0 | /* sin(y0+grid->resyRad) = */ sinphi0 * cosresy + |
120 | 0 | cosphi0 * sinresy; |
121 | 0 | cosphi1 = |
122 | 0 | /* cos(y0+grid->resyRad) = */ cosphi0 * cosresy - |
123 | 0 | sinphi0 * sinresy; |
124 | |
|
125 | 0 | last_iy0 = iy0; |
126 | 0 | } |
127 | | |
128 | | // Using "traditional" formulas to convert from easting, northing |
129 | | // offsets to geocentric offsets |
130 | 0 | const double sinlam00 = -sinhalfresx; |
131 | 0 | const double coslam00 = coshalfresx; |
132 | 0 | const double sinphi00 = sinphi0; |
133 | 0 | const double cosphi00 = cosphi0; |
134 | 0 | const double dn00sinphi00 = dn00 * sinphi00; |
135 | 0 | dX00 = -de00 * sinlam00 - dn00sinphi00 * coslam00; |
136 | 0 | dY00 = de00 * coslam00 - dn00sinphi00 * sinlam00; |
137 | 0 | dZ00 = dn00 * cosphi00; |
138 | |
|
139 | 0 | const double sinlam01 = -sinhalfresx; |
140 | 0 | const double coslam01 = coshalfresx; |
141 | 0 | const double sinphi01 = sinphi1; |
142 | 0 | const double cosphi01 = cosphi1; |
143 | 0 | const double dn01sinphi01 = dn01 * sinphi01; |
144 | 0 | dX01 = -de01 * sinlam01 - dn01sinphi01 * coslam01; |
145 | 0 | dY01 = de01 * coslam01 - dn01sinphi01 * sinlam01; |
146 | 0 | dZ01 = dn01 * cosphi01; |
147 | |
|
148 | 0 | const double sinlam10 = sinhalfresx; |
149 | 0 | const double coslam10 = coshalfresx; |
150 | 0 | const double sinphi10 = sinphi0; |
151 | 0 | const double cosphi10 = cosphi0; |
152 | 0 | const double dn10sinphi10 = dn10 * sinphi10; |
153 | 0 | dX10 = -de10 * sinlam10 - dn10sinphi10 * coslam10; |
154 | 0 | dY10 = de10 * coslam10 - dn10sinphi10 * sinlam10; |
155 | 0 | dZ10 = dn10 * cosphi10; |
156 | |
|
157 | 0 | const double sinlam11 = sinhalfresx; |
158 | 0 | const double coslam11 = coshalfresx; |
159 | 0 | const double sinphi11 = sinphi1; |
160 | 0 | const double cosphi11 = cosphi1; |
161 | 0 | const double dn11sinphi11 = dn11 * sinphi11; |
162 | 0 | dX11 = -de11 * sinlam11 - dn11sinphi11 * coslam11; |
163 | 0 | dY11 = de11 * coslam11 - dn11sinphi11 * sinlam11; |
164 | 0 | dZ11 = dn11 * cosphi11; |
165 | 0 | } |
166 | |
|
167 | 0 | dX = m00 * dX00 + m01 * dX01 + m10 * dX10 + m11 * dX11; |
168 | 0 | dY = m00 * dY00 + m01 * dY01 + m10 * dY10 + m11 * dY11; |
169 | 0 | dZ = m00 * dZ00 + m01 * dZ01 + m10 * dZ10 + m11 * dZ11; |
170 | 0 | } |
171 | | }; |
172 | | |
173 | | // --------------------------------------------------------------------------- |
174 | | |
175 | | /** Internal class to offer caching services over a Component */ |
176 | | template <class Grid, class GridSet> struct ComponentEx { |
177 | | const Component &component; |
178 | | |
179 | | const bool isBilinearInterpolation; /* bilinear vs geocentric_bilinear */ |
180 | | |
181 | | const DisplacementType displacementType; |
182 | | |
183 | | // Cache |
184 | | std::unique_ptr<GridSet> gridSet{}; |
185 | | std::map<const Grid *, GridEx<Grid>> mapGrids{}; |
186 | | |
187 | | private: |
188 | | mutable double mCachedDt = 0; |
189 | | mutable double mCachedValue = 0; |
190 | | |
191 | 0 | static DisplacementType getDisplacementType(const std::string &s) { |
192 | 0 | if (s == STR_HORIZONTAL) |
193 | 0 | return DisplacementType::HORIZONTAL; |
194 | 0 | if (s == STR_VERTICAL) |
195 | 0 | return DisplacementType::VERTICAL; |
196 | 0 | if (s == STR_3D) |
197 | 0 | return DisplacementType::THREE_D; |
198 | 0 | return DisplacementType::NONE; |
199 | 0 | } |
200 | | |
201 | | public: |
202 | | explicit ComponentEx(const Component &componentIn) |
203 | 0 | : component(componentIn), |
204 | | isBilinearInterpolation( |
205 | 0 | componentIn.spatialModel().interpolationMethod == STR_BILINEAR), |
206 | 0 | displacementType(getDisplacementType(component.displacementType())) {} |
207 | | |
208 | 0 | double evaluateAt(double dt) const { |
209 | 0 | if (dt == mCachedDt) |
210 | 0 | return mCachedValue; |
211 | 0 | mCachedDt = dt; |
212 | 0 | mCachedValue = component.timeFunction()->evaluateAt(dt); |
213 | 0 | return mCachedValue; |
214 | 0 | } |
215 | | |
216 | 0 | void clearGridCache() { |
217 | 0 | gridSet.reset(); |
218 | 0 | mapGrids.clear(); |
219 | 0 | } |
220 | | }; |
221 | | |
222 | | // --------------------------------------------------------------------------- |
223 | | |
224 | | /** Converts a ISO8601 date-time string, formatted as "YYYY-MM-DDTHH:MM:SSZ", |
225 | | * into a decimal year. |
226 | | * Leap years are taken into account, but not leap seconds. |
227 | | */ |
228 | 0 | static double ISO8601ToDecimalYear(const std::string &dt) { |
229 | 0 | int year, month, day, hour, min, sec; |
230 | 0 | if (sscanf(dt.c_str(), "%04d-%02d-%02dT%02d:%02d:%02dZ", &year, &month, |
231 | 0 | &day, &hour, &min, &sec) != 6 || |
232 | 0 | year < 1582 || // Start of Gregorian calendar |
233 | 0 | month < 1 || month > 12 || day < 1 || day > 31 || hour < 0 || |
234 | 0 | hour >= 24 || min < 0 || min >= 60 || sec < 0 || sec >= 61) { |
235 | 0 | throw ParsingException("Wrong formatting / invalid date-time for " + |
236 | 0 | dt); |
237 | 0 | } |
238 | 0 | const bool isLeapYear = |
239 | 0 | (((year % 4) == 0 && (year % 100) != 0) || (year % 400) == 0); |
240 | | // Given the intended use, we omit leap seconds... |
241 | 0 | const int month_table[2][12] = { |
242 | 0 | {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}, |
243 | 0 | {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}}; |
244 | 0 | int dayInYear = day - 1; |
245 | 0 | for (int m = 1; m < month; m++) { |
246 | 0 | dayInYear += month_table[isLeapYear ? 1 : 0][m - 1]; |
247 | 0 | } |
248 | 0 | if (day > month_table[isLeapYear ? 1 : 0][month - 1]) { |
249 | 0 | throw ParsingException("Wrong formatting / invalid date-time for " + |
250 | 0 | dt); |
251 | 0 | } |
252 | 0 | return year + (dayInYear * 86400 + hour * 3600 + min * 60 + sec) / |
253 | 0 | (isLeapYear ? 86400. * 366 : 86400. * 365); |
254 | 0 | } |
255 | | |
256 | | // --------------------------------------------------------------------------- |
257 | | |
258 | 8 | Epoch::Epoch(const std::string &dt) : mDt(dt) { |
259 | 8 | if (!dt.empty()) { |
260 | 0 | mDecimalYear = ISO8601ToDecimalYear(dt); |
261 | 0 | } |
262 | 8 | } |
263 | | |
264 | | // --------------------------------------------------------------------------- |
265 | | |
266 | 0 | double Epoch::toDecimalYear() const { return mDecimalYear; } |
267 | | |
268 | | // --------------------------------------------------------------------------- |
269 | | |
270 | 0 | static std::string getString(const json &j, const char *key, bool optional) { |
271 | 0 | if (!j.contains(key)) { |
272 | 0 | if (optional) { |
273 | 0 | return std::string(); |
274 | 0 | } |
275 | 0 | throw ParsingException(std::string("Missing \"") + key + "\" key"); |
276 | 0 | } |
277 | 0 | const json v = j[key]; |
278 | 0 | if (!v.is_string()) { |
279 | 0 | throw ParsingException(std::string("The value of \"") + key + |
280 | 0 | "\" should be a string"); |
281 | 0 | } |
282 | 0 | return v.get<std::string>(); |
283 | 0 | } |
284 | | |
285 | 0 | static std::string getReqString(const json &j, const char *key) { |
286 | 0 | return getString(j, key, false); |
287 | 0 | } |
288 | | |
289 | 0 | static std::string getOptString(const json &j, const char *key) { |
290 | 0 | return getString(j, key, true); |
291 | 0 | } |
292 | | |
293 | | // --------------------------------------------------------------------------- |
294 | | |
295 | 0 | static double getDouble(const json &j, const char *key, bool optional) { |
296 | 0 | if (!j.contains(key)) { |
297 | 0 | if (optional) { |
298 | 0 | return std::numeric_limits<double>::quiet_NaN(); |
299 | 0 | } |
300 | 0 | throw ParsingException(std::string("Missing \"") + key + "\" key"); |
301 | 0 | } |
302 | 0 | const json v = j[key]; |
303 | 0 | if (!v.is_number()) { |
304 | 0 | throw ParsingException(std::string("The value of \"") + key + |
305 | 0 | "\" should be a number"); |
306 | 0 | } |
307 | 0 | return v.get<double>(); |
308 | 0 | } |
309 | | |
310 | 0 | static double getReqDouble(const json &j, const char *key) { |
311 | 0 | return getDouble(j, key, false); |
312 | 0 | } |
313 | 0 | static double getOptDouble(const json &j, const char *key) { |
314 | 0 | return getDouble(j, key, true); |
315 | 0 | } |
316 | | |
317 | | // --------------------------------------------------------------------------- |
318 | | |
319 | 0 | static json getObjectMember(const json &j, const char *key) { |
320 | 0 | if (!j.contains(key)) { |
321 | 0 | throw ParsingException(std::string("Missing \"") + key + "\" key"); |
322 | 0 | } |
323 | 0 | const json obj = j[key]; |
324 | 0 | if (!obj.is_object()) { |
325 | 0 | throw ParsingException(std::string("The value of \"") + key + |
326 | 0 | "\" should be a object"); |
327 | 0 | } |
328 | 0 | return obj; |
329 | 0 | } |
330 | | |
331 | | // --------------------------------------------------------------------------- |
332 | | |
333 | 0 | static json getArrayMember(const json &j, const char *key) { |
334 | 0 | if (!j.contains(key)) { |
335 | 0 | throw ParsingException(std::string("Missing \"") + key + "\" key"); |
336 | 0 | } |
337 | 0 | const json obj = j[key]; |
338 | 0 | if (!obj.is_array()) { |
339 | 0 | throw ParsingException(std::string("The value of \"") + key + |
340 | 0 | "\" should be a array"); |
341 | 0 | } |
342 | 0 | return obj; |
343 | 0 | } |
344 | | |
345 | | // --------------------------------------------------------------------------- |
346 | | |
347 | 4 | std::unique_ptr<MasterFile> MasterFile::parse(const std::string &text) { |
348 | 4 | std::unique_ptr<MasterFile> dmmf(new MasterFile()); |
349 | 4 | json j; |
350 | 4 | try { |
351 | 4 | j = json::parse(text); |
352 | 4 | } catch (const std::exception &e) { |
353 | 4 | throw ParsingException(e.what()); |
354 | 4 | } |
355 | 0 | if (!j.is_object()) { |
356 | 0 | throw ParsingException("Not an object"); |
357 | 0 | } |
358 | 0 | dmmf->mFileType = getReqString(j, "file_type"); |
359 | 0 | dmmf->mFormatVersion = getReqString(j, "format_version"); |
360 | 0 | dmmf->mName = getOptString(j, "name"); |
361 | 0 | dmmf->mVersion = getOptString(j, "version"); |
362 | 0 | dmmf->mLicense = getOptString(j, "license"); |
363 | 0 | dmmf->mDescription = getOptString(j, "description"); |
364 | 0 | dmmf->mPublicationDate = getOptString(j, "publication_date"); |
365 | |
|
366 | 0 | if (j.contains("authority")) { |
367 | 0 | const json jAuthority = j["authority"]; |
368 | 0 | if (!jAuthority.is_object()) { |
369 | 0 | throw ParsingException("authority is not a object"); |
370 | 0 | } |
371 | 0 | dmmf->mAuthority.name = getOptString(jAuthority, "name"); |
372 | 0 | dmmf->mAuthority.url = getOptString(jAuthority, "url"); |
373 | 0 | dmmf->mAuthority.address = getOptString(jAuthority, "address"); |
374 | 0 | dmmf->mAuthority.email = getOptString(jAuthority, "email"); |
375 | 0 | } |
376 | | |
377 | 0 | if (j.contains("links")) { |
378 | 0 | const json jLinks = j["links"]; |
379 | 0 | if (!jLinks.is_array()) { |
380 | 0 | throw ParsingException("links is not an array"); |
381 | 0 | } |
382 | 0 | for (const json &jLink : jLinks) { |
383 | 0 | if (!jLink.is_object()) { |
384 | 0 | throw ParsingException("links[] item is not an object"); |
385 | 0 | } |
386 | 0 | Link link; |
387 | 0 | link.href = getOptString(jLink, "href"); |
388 | 0 | link.rel = getOptString(jLink, "rel"); |
389 | 0 | link.type = getOptString(jLink, "type"); |
390 | 0 | link.title = getOptString(jLink, "title"); |
391 | 0 | dmmf->mLinks.emplace_back(std::move(link)); |
392 | 0 | } |
393 | 0 | } |
394 | 0 | dmmf->mSourceCRS = getReqString(j, "source_crs"); |
395 | 0 | dmmf->mTargetCRS = getReqString(j, "target_crs"); |
396 | 0 | dmmf->mDefinitionCRS = getReqString(j, "definition_crs"); |
397 | 0 | if (dmmf->mSourceCRS != dmmf->mDefinitionCRS) { |
398 | 0 | throw ParsingException( |
399 | 0 | "source_crs != definition_crs not currently supported"); |
400 | 0 | } |
401 | 0 | dmmf->mReferenceEpoch = getOptString(j, "reference_epoch"); |
402 | 0 | dmmf->mUncertaintyReferenceEpoch = |
403 | 0 | getOptString(j, "uncertainty_reference_epoch"); |
404 | 0 | dmmf->mHorizontalOffsetUnit = getOptString(j, "horizontal_offset_unit"); |
405 | 0 | if (!dmmf->mHorizontalOffsetUnit.empty() && |
406 | 0 | dmmf->mHorizontalOffsetUnit != STR_METRE && |
407 | 0 | dmmf->mHorizontalOffsetUnit != STR_DEGREE) { |
408 | 0 | throw ParsingException("Unsupported value for horizontal_offset_unit"); |
409 | 0 | } |
410 | 0 | dmmf->mVerticalOffsetUnit = getOptString(j, "vertical_offset_unit"); |
411 | 0 | if (!dmmf->mVerticalOffsetUnit.empty() && |
412 | 0 | dmmf->mVerticalOffsetUnit != STR_METRE) { |
413 | 0 | throw ParsingException("Unsupported value for vertical_offset_unit"); |
414 | 0 | } |
415 | 0 | dmmf->mHorizontalUncertaintyType = |
416 | 0 | getOptString(j, "horizontal_uncertainty_type"); |
417 | 0 | dmmf->mHorizontalUncertaintyUnit = |
418 | 0 | getOptString(j, "horizontal_uncertainty_unit"); |
419 | 0 | dmmf->mVerticalUncertaintyType = |
420 | 0 | getOptString(j, "vertical_uncertainty_type"); |
421 | 0 | dmmf->mVerticalUncertaintyUnit = |
422 | 0 | getOptString(j, "vertical_uncertainty_unit"); |
423 | 0 | dmmf->mHorizontalOffsetMethod = getOptString(j, "horizontal_offset_method"); |
424 | 0 | if (!dmmf->mHorizontalOffsetMethod.empty() && |
425 | 0 | dmmf->mHorizontalOffsetMethod != STR_ADDITION && |
426 | 0 | dmmf->mHorizontalOffsetMethod != STR_GEOCENTRIC) { |
427 | 0 | throw ParsingException( |
428 | 0 | "Unsupported value for horizontal_offset_method"); |
429 | 0 | } |
430 | 0 | dmmf->mSpatialExtent = SpatialExtent::parse(getObjectMember(j, "extent")); |
431 | |
|
432 | 0 | const json jTimeExtent = getObjectMember(j, "time_extent"); |
433 | 0 | dmmf->mTimeExtent.first = Epoch(getReqString(jTimeExtent, "first")); |
434 | 0 | dmmf->mTimeExtent.last = Epoch(getReqString(jTimeExtent, "last")); |
435 | |
|
436 | 0 | const json jComponents = getArrayMember(j, "components"); |
437 | 0 | for (const json &jComponent : jComponents) { |
438 | 0 | dmmf->mComponents.emplace_back(Component::parse(jComponent)); |
439 | 0 | const auto &comp = dmmf->mComponents.back(); |
440 | 0 | if (comp.displacementType() == STR_HORIZONTAL || |
441 | 0 | comp.displacementType() == STR_3D) { |
442 | 0 | if (dmmf->mHorizontalOffsetUnit.empty()) { |
443 | 0 | throw ParsingException("horizontal_offset_unit should be " |
444 | 0 | "defined as there is a component with " |
445 | 0 | "displacement_type = horizontal/3d"); |
446 | 0 | } |
447 | 0 | if (dmmf->mHorizontalOffsetMethod.empty()) { |
448 | 0 | throw ParsingException("horizontal_offset_method should be " |
449 | 0 | "defined as there is a component with " |
450 | 0 | "displacement_type = horizontal/3d"); |
451 | 0 | } |
452 | 0 | } |
453 | 0 | if (comp.displacementType() == STR_VERTICAL || |
454 | 0 | comp.displacementType() == STR_3D) { |
455 | 0 | if (dmmf->mVerticalOffsetUnit.empty()) { |
456 | 0 | throw ParsingException("vertical_offset_unit should be defined " |
457 | 0 | "as there is a component with " |
458 | 0 | "displacement_type = vertical/3d"); |
459 | 0 | } |
460 | 0 | } |
461 | 0 | if (dmmf->mHorizontalOffsetUnit == STR_DEGREE && |
462 | 0 | comp.spatialModel().interpolationMethod != STR_BILINEAR) { |
463 | 0 | throw ParsingException("horizontal_offset_unit = degree can only " |
464 | 0 | "be used with interpolation_method = " |
465 | 0 | "bilinear"); |
466 | 0 | } |
467 | 0 | } |
468 | | |
469 | 0 | if (dmmf->mHorizontalOffsetUnit == STR_DEGREE && |
470 | 0 | dmmf->mHorizontalOffsetMethod != STR_ADDITION) { |
471 | 0 | throw ParsingException("horizontal_offset_unit = degree can only be " |
472 | 0 | "used with horizontal_offset_method = addition"); |
473 | 0 | } |
474 | | |
475 | 0 | return dmmf; |
476 | 0 | } |
477 | | |
478 | | // --------------------------------------------------------------------------- |
479 | | |
480 | 0 | SpatialExtent SpatialExtent::parse(const json &j) { |
481 | 0 | SpatialExtent ex; |
482 | |
|
483 | 0 | const std::string type = getReqString(j, "type"); |
484 | 0 | if (type != "bbox") { |
485 | 0 | throw ParsingException("unsupported type of extent"); |
486 | 0 | } |
487 | | |
488 | 0 | const json jParameter = getObjectMember(j, "parameters"); |
489 | 0 | const json jBbox = getArrayMember(jParameter, "bbox"); |
490 | 0 | if (jBbox.size() != 4) { |
491 | 0 | throw ParsingException("bbox is not an array of 4 numeric elements"); |
492 | 0 | } |
493 | 0 | for (int i = 0; i < 4; i++) { |
494 | 0 | if (!jBbox[i].is_number()) { |
495 | 0 | throw ParsingException( |
496 | 0 | "bbox is not an array of 4 numeric elements"); |
497 | 0 | } |
498 | 0 | } |
499 | 0 | ex.mMinx = jBbox[0].get<double>(); |
500 | 0 | ex.mMiny = jBbox[1].get<double>(); |
501 | 0 | ex.mMaxx = jBbox[2].get<double>(); |
502 | 0 | ex.mMaxy = jBbox[3].get<double>(); |
503 | |
|
504 | 0 | ex.mMinxRad = DegToRad(ex.mMinx); |
505 | 0 | ex.mMinyRad = DegToRad(ex.mMiny); |
506 | 0 | ex.mMaxxRad = DegToRad(ex.mMaxx); |
507 | 0 | ex.mMaxyRad = DegToRad(ex.mMaxy); |
508 | |
|
509 | 0 | return ex; |
510 | 0 | } |
511 | | |
512 | | // --------------------------------------------------------------------------- |
513 | | |
514 | 0 | Component Component::parse(const json &j) { |
515 | 0 | Component comp; |
516 | 0 | if (!j.is_object()) { |
517 | 0 | throw ParsingException("component is not an object"); |
518 | 0 | } |
519 | 0 | comp.mDescription = getOptString(j, "description"); |
520 | 0 | comp.mSpatialExtent = SpatialExtent::parse(getObjectMember(j, "extent")); |
521 | 0 | comp.mDisplacementType = getReqString(j, "displacement_type"); |
522 | 0 | if (comp.mDisplacementType != STR_NONE && |
523 | 0 | comp.mDisplacementType != STR_HORIZONTAL && |
524 | 0 | comp.mDisplacementType != STR_VERTICAL && |
525 | 0 | comp.mDisplacementType != STR_3D) { |
526 | 0 | throw ParsingException("Unsupported value for displacement_type"); |
527 | 0 | } |
528 | 0 | comp.mUncertaintyType = getReqString(j, "uncertainty_type"); |
529 | 0 | comp.mHorizontalUncertainty = getOptDouble(j, "horizontal_uncertainty"); |
530 | 0 | comp.mVerticalUncertainty = getOptDouble(j, "vertical_uncertainty"); |
531 | |
|
532 | 0 | const json jSpatialModel = getObjectMember(j, "spatial_model"); |
533 | 0 | comp.mSpatialModel.type = getReqString(jSpatialModel, "type"); |
534 | 0 | comp.mSpatialModel.interpolationMethod = |
535 | 0 | getReqString(jSpatialModel, "interpolation_method"); |
536 | 0 | if (comp.mSpatialModel.interpolationMethod != STR_BILINEAR && |
537 | 0 | comp.mSpatialModel.interpolationMethod != STR_GEOCENTRIC_BILINEAR) { |
538 | 0 | throw ParsingException("Unsupported value for interpolation_method"); |
539 | 0 | } |
540 | 0 | comp.mSpatialModel.filename = getReqString(jSpatialModel, "filename"); |
541 | 0 | comp.mSpatialModel.md5Checksum = |
542 | 0 | getOptString(jSpatialModel, "md5_checksum"); |
543 | |
|
544 | 0 | const json jTimeFunction = getObjectMember(j, "time_function"); |
545 | 0 | std::string timeFunctionType = getReqString(jTimeFunction, "type"); |
546 | 0 | const json jParameters = timeFunctionType == "constant" |
547 | 0 | ? json() |
548 | 0 | : getObjectMember(jTimeFunction, "parameters"); |
549 | |
|
550 | 0 | if (timeFunctionType == "constant") { |
551 | 0 | std::unique_ptr<ConstantTimeFunction> tf(new ConstantTimeFunction()); |
552 | 0 | tf->type = std::move(timeFunctionType); |
553 | 0 | comp.mTimeFunction = std::move(tf); |
554 | 0 | } else if (timeFunctionType == "velocity") { |
555 | 0 | std::unique_ptr<VelocityTimeFunction> tf(new VelocityTimeFunction()); |
556 | 0 | tf->type = std::move(timeFunctionType); |
557 | 0 | tf->referenceEpoch = |
558 | 0 | Epoch(getReqString(jParameters, "reference_epoch")); |
559 | 0 | comp.mTimeFunction = std::move(tf); |
560 | 0 | } else if (timeFunctionType == "step") { |
561 | 0 | std::unique_ptr<StepTimeFunction> tf(new StepTimeFunction()); |
562 | 0 | tf->type = std::move(timeFunctionType); |
563 | 0 | tf->stepEpoch = Epoch(getReqString(jParameters, "step_epoch")); |
564 | 0 | comp.mTimeFunction = std::move(tf); |
565 | 0 | } else if (timeFunctionType == "reverse_step") { |
566 | 0 | std::unique_ptr<ReverseStepTimeFunction> tf( |
567 | 0 | new ReverseStepTimeFunction()); |
568 | 0 | tf->type = std::move(timeFunctionType); |
569 | 0 | tf->stepEpoch = Epoch(getReqString(jParameters, "step_epoch")); |
570 | 0 | comp.mTimeFunction = std::move(tf); |
571 | 0 | } else if (timeFunctionType == "piecewise") { |
572 | 0 | std::unique_ptr<PiecewiseTimeFunction> tf(new PiecewiseTimeFunction()); |
573 | 0 | tf->type = std::move(timeFunctionType); |
574 | 0 | tf->beforeFirst = getReqString(jParameters, "before_first"); |
575 | 0 | if (tf->beforeFirst != "zero" && tf->beforeFirst != "constant" && |
576 | 0 | tf->beforeFirst != "linear") { |
577 | 0 | throw ParsingException("Unsupported value for before_first"); |
578 | 0 | } |
579 | 0 | tf->afterLast = getReqString(jParameters, "after_last"); |
580 | 0 | if (tf->afterLast != "zero" && tf->afterLast != "constant" && |
581 | 0 | tf->afterLast != "linear") { |
582 | 0 | throw ParsingException("Unsupported value for afterLast"); |
583 | 0 | } |
584 | 0 | const json jModel = getArrayMember(jParameters, "model"); |
585 | 0 | for (const json &jModelElt : jModel) { |
586 | 0 | if (!jModelElt.is_object()) { |
587 | 0 | throw ParsingException("model[] element is not an object"); |
588 | 0 | } |
589 | 0 | PiecewiseTimeFunction::EpochScaleFactorTuple tuple; |
590 | 0 | tuple.epoch = Epoch(getReqString(jModelElt, "epoch")); |
591 | 0 | tuple.scaleFactor = getReqDouble(jModelElt, "scale_factor"); |
592 | 0 | tf->model.emplace_back(std::move(tuple)); |
593 | 0 | } |
594 | | |
595 | 0 | comp.mTimeFunction = std::move(tf); |
596 | 0 | } else if (timeFunctionType == "exponential") { |
597 | 0 | std::unique_ptr<ExponentialTimeFunction> tf( |
598 | 0 | new ExponentialTimeFunction()); |
599 | 0 | tf->type = std::move(timeFunctionType); |
600 | 0 | tf->referenceEpoch = |
601 | 0 | Epoch(getReqString(jParameters, "reference_epoch")); |
602 | 0 | tf->endEpoch = Epoch(getOptString(jParameters, "end_epoch")); |
603 | 0 | tf->relaxationConstant = |
604 | 0 | getReqDouble(jParameters, "relaxation_constant"); |
605 | 0 | if (tf->relaxationConstant <= 0.0) { |
606 | 0 | throw ParsingException("Invalid value for relaxation_constant"); |
607 | 0 | } |
608 | 0 | tf->beforeScaleFactor = |
609 | 0 | getReqDouble(jParameters, "before_scale_factor"); |
610 | 0 | tf->initialScaleFactor = |
611 | 0 | getReqDouble(jParameters, "initial_scale_factor"); |
612 | 0 | tf->finalScaleFactor = getReqDouble(jParameters, "final_scale_factor"); |
613 | 0 | comp.mTimeFunction = std::move(tf); |
614 | 0 | } else { |
615 | 0 | throw ParsingException("Unsupported type of time function: " + |
616 | 0 | timeFunctionType); |
617 | 0 | } |
618 | | |
619 | 0 | return comp; |
620 | 0 | } |
621 | | |
622 | | // --------------------------------------------------------------------------- |
623 | | |
624 | 0 | double Component::ConstantTimeFunction::evaluateAt(double) const { return 1.0; } |
625 | | |
626 | | // --------------------------------------------------------------------------- |
627 | | |
628 | 0 | double Component::VelocityTimeFunction::evaluateAt(double dt) const { |
629 | 0 | return dt - referenceEpoch.toDecimalYear(); |
630 | 0 | } |
631 | | |
632 | | // --------------------------------------------------------------------------- |
633 | | |
634 | 0 | double Component::StepTimeFunction::evaluateAt(double dt) const { |
635 | 0 | if (dt < stepEpoch.toDecimalYear()) |
636 | 0 | return 0.0; |
637 | 0 | return 1.0; |
638 | 0 | } |
639 | | |
640 | | // --------------------------------------------------------------------------- |
641 | | |
642 | 0 | double Component::ReverseStepTimeFunction::evaluateAt(double dt) const { |
643 | 0 | if (dt < stepEpoch.toDecimalYear()) |
644 | 0 | return -1.0; |
645 | 0 | return 0.0; |
646 | 0 | } |
647 | | |
648 | | // --------------------------------------------------------------------------- |
649 | | |
650 | 0 | double Component::PiecewiseTimeFunction::evaluateAt(double dt) const { |
651 | 0 | if (model.empty()) { |
652 | 0 | return 0.0; |
653 | 0 | } |
654 | | |
655 | 0 | const double dt1 = model[0].epoch.toDecimalYear(); |
656 | 0 | if (dt < dt1) { |
657 | 0 | if (beforeFirst == "zero") |
658 | 0 | return 0.0; |
659 | 0 | if (beforeFirst == "constant" || model.size() == 1) |
660 | 0 | return model[0].scaleFactor; |
661 | | |
662 | | // linear |
663 | 0 | const double f1 = model[0].scaleFactor; |
664 | 0 | const double dt2 = model[1].epoch.toDecimalYear(); |
665 | 0 | const double f2 = model[1].scaleFactor; |
666 | 0 | if (dt1 == dt2) |
667 | 0 | return f1; |
668 | 0 | return (f1 * (dt2 - dt) + f2 * (dt - dt1)) / (dt2 - dt1); |
669 | 0 | } |
670 | 0 | for (size_t i = 1; i < model.size(); i++) { |
671 | 0 | const double dtip1 = model[i].epoch.toDecimalYear(); |
672 | 0 | if (dt < dtip1) { |
673 | 0 | const double dti = model[i - 1].epoch.toDecimalYear(); |
674 | 0 | const double fip1 = model[i].scaleFactor; |
675 | 0 | const double fi = model[i - 1].scaleFactor; |
676 | 0 | return (fi * (dtip1 - dt) + fip1 * (dt - dti)) / (dtip1 - dti); |
677 | 0 | } |
678 | 0 | } |
679 | 0 | if (afterLast == "zero") { |
680 | 0 | return 0.0; |
681 | 0 | } |
682 | 0 | if (afterLast == "constant" || model.size() == 1) |
683 | 0 | return model.back().scaleFactor; |
684 | | |
685 | | // linear |
686 | 0 | const double dtnm1 = model[model.size() - 2].epoch.toDecimalYear(); |
687 | 0 | const double fnm1 = model[model.size() - 2].scaleFactor; |
688 | 0 | const double dtn = model.back().epoch.toDecimalYear(); |
689 | 0 | const double fn = model.back().scaleFactor; |
690 | 0 | if (dtnm1 == dtn) |
691 | 0 | return fn; |
692 | 0 | return (fnm1 * (dtn - dt) + fn * (dt - dtnm1)) / (dtn - dtnm1); |
693 | 0 | } |
694 | | |
695 | | // --------------------------------------------------------------------------- |
696 | | |
697 | 0 | double Component::ExponentialTimeFunction::evaluateAt(double dt) const { |
698 | 0 | const double t0 = referenceEpoch.toDecimalYear(); |
699 | 0 | if (dt < t0) |
700 | 0 | return beforeScaleFactor; |
701 | 0 | if (!endEpoch.toString().empty()) { |
702 | 0 | dt = std::min(dt, endEpoch.toDecimalYear()); |
703 | 0 | } |
704 | 0 | return initialScaleFactor + |
705 | 0 | (finalScaleFactor - initialScaleFactor) * |
706 | 0 | (1.0 - std::exp(-(dt - t0) / relaxationConstant)); |
707 | 0 | } |
708 | | |
709 | | // --------------------------------------------------------------------------- |
710 | | |
711 | | inline void DeltaEastingNorthingToLongLat(double cosphi, double de, double dn, |
712 | | double a, double b, double es, |
713 | 0 | double &dlam, double &dphi) { |
714 | 0 | const double oneMinuX = es * (1 - cosphi * cosphi); |
715 | 0 | const double X = 1 - oneMinuX; |
716 | 0 | const double sqrtX = sqrt(X); |
717 | | #if 0 |
718 | | // With es of Earth, absolute/relative error is at most 2e-8 |
719 | | // const double sqrtX = 1 - 0.5 * oneMinuX * (1 + 0.25 * oneMinuX); |
720 | | #endif |
721 | 0 | dlam = de * sqrtX / (a * cosphi); |
722 | 0 | dphi = dn * a * sqrtX * X / (b * b); |
723 | 0 | } |
724 | | |
725 | | // --------------------------------------------------------------------------- |
726 | | |
727 | | template <class Grid, class GridSet, class EvaluatorIface> |
728 | | Evaluator<Grid, GridSet, EvaluatorIface>::Evaluator( |
729 | | std::unique_ptr<MasterFile> &&model, EvaluatorIface &iface, double a, |
730 | | double b) |
731 | 0 | : mModel(std::move(model)), mA(a), mB(b), mEs(1 - (b * b) / (a * a)), |
732 | 0 | mIsHorizontalUnitDegree(mModel->horizontalOffsetUnit() == STR_DEGREE), |
733 | 0 | mIsAddition(mModel->horizontalOffsetMethod() == STR_ADDITION), |
734 | 0 | mIsGeographicCRS(iface.isGeographicCRS(mModel->definitionCRS())) { |
735 | 0 | if (!mIsGeographicCRS && mIsHorizontalUnitDegree) { |
736 | 0 | throw EvaluatorException( |
737 | 0 | "definition_crs = projected CRS and " |
738 | 0 | "horizontal_offset_unit = degree are incompatible"); |
739 | 0 | } |
740 | 0 | if (!mIsGeographicCRS && !mIsAddition) { |
741 | 0 | throw EvaluatorException( |
742 | 0 | "definition_crs = projected CRS and " |
743 | 0 | "horizontal_offset_method = geocentric are incompatible"); |
744 | 0 | } |
745 | 0 | mComponents.reserve(mModel->components().size()); |
746 | 0 | for (const auto &comp : mModel->components()) { |
747 | 0 | mComponents.emplace_back(std::unique_ptr<ComponentEx<Grid, GridSet>>( |
748 | 0 | new ComponentEx<Grid, GridSet>(comp))); |
749 | 0 | if (!mIsGeographicCRS && !mComponents.back()->isBilinearInterpolation) { |
750 | 0 | throw EvaluatorException( |
751 | 0 | "definition_crs = projected CRS and " |
752 | 0 | "interpolation_method = geocentric_bilinear are incompatible"); |
753 | 0 | } |
754 | 0 | } |
755 | 0 | } |
756 | | |
757 | | // --------------------------------------------------------------------------- |
758 | | |
759 | | template <class Grid, class GridSet, class EvaluatorIface> |
760 | 0 | void Evaluator<Grid, GridSet, EvaluatorIface>::clearGridCache() { |
761 | 0 | for (auto &comp : mComponents) { |
762 | 0 | comp->clearGridCache(); |
763 | 0 | } |
764 | 0 | } |
765 | | |
766 | | // --------------------------------------------------------------------------- |
767 | | |
768 | | #ifdef DEBUG_DEFMODEL |
769 | | |
770 | | static std::string shortName(const Component &comp) { |
771 | | const auto &desc = comp.description(); |
772 | | return desc.substr(0, desc.find('\n')) + " (" + |
773 | | comp.spatialModel().filename + ")"; |
774 | | } |
775 | | |
776 | | static std::string toString(double val) { |
777 | | char buffer[32]; |
778 | | snprintf(buffer, sizeof(buffer), "%.9g", val); |
779 | | return buffer; |
780 | | } |
781 | | |
782 | | #endif |
783 | | |
784 | | // --------------------------------------------------------------------------- |
785 | | |
786 | | static bool bboxCheck(double &x, double &y, bool forInverseComputation, |
787 | | const double minx, const double miny, const double maxx, |
788 | | const double maxy, const double EPS, |
789 | 0 | const double extraMarginForInverse) { |
790 | 0 | if (x < minx - EPS || x > maxx + EPS || y < miny - EPS || y > maxy + EPS) { |
791 | 0 | if (!forInverseComputation) { |
792 | 0 | return false; |
793 | 0 | } |
794 | | // In case of iterative computation for inverse, allow to be a |
795 | | // slightly bit outside of the grid and clamp to the edges |
796 | 0 | bool xOk = false; |
797 | 0 | if (x >= minx - EPS && x <= maxx + EPS) { |
798 | 0 | xOk = true; |
799 | 0 | } else if (x > minx - extraMarginForInverse && x < minx) { |
800 | 0 | x = minx; |
801 | 0 | xOk = true; |
802 | 0 | } else if (x < maxx + extraMarginForInverse && x > maxx) { |
803 | 0 | x = maxx; |
804 | 0 | xOk = true; |
805 | 0 | } |
806 | |
|
807 | 0 | bool yOk = false; |
808 | 0 | if (y >= miny - EPS && y <= maxy + EPS) { |
809 | 0 | yOk = true; |
810 | 0 | } else if (y > miny - extraMarginForInverse && y < miny) { |
811 | 0 | y = miny; |
812 | 0 | yOk = true; |
813 | 0 | } else if (y < maxy + extraMarginForInverse && y > maxy) { |
814 | 0 | y = maxy; |
815 | 0 | yOk = true; |
816 | 0 | } |
817 | |
|
818 | 0 | return xOk && yOk; |
819 | 0 | } |
820 | 0 | return true; |
821 | 0 | } |
822 | | |
823 | | // --------------------------------------------------------------------------- |
824 | | |
825 | | template <class Grid, class GridSet, class EvaluatorIface> |
826 | | bool Evaluator<Grid, GridSet, EvaluatorIface>::forward( |
827 | | EvaluatorIface &iface, double x, double y, double z, double t, |
828 | | bool forInverseComputation, double &x_out, double &y_out, double &z_out) |
829 | | |
830 | 0 | { |
831 | 0 | x_out = x; |
832 | 0 | y_out = y; |
833 | 0 | z_out = z; |
834 | |
|
835 | 0 | const double EPS = mIsGeographicCRS ? 1e-10 : 1e-5; |
836 | | |
837 | | // Check against global model spatial extent, potentially wrapping |
838 | | // longitude to match |
839 | 0 | { |
840 | 0 | const auto &extent = mModel->extent(); |
841 | 0 | const double minx = extent.minxNormalized(mIsGeographicCRS); |
842 | 0 | const double maxx = extent.maxxNormalized(mIsGeographicCRS); |
843 | 0 | if (mIsGeographicCRS) { |
844 | 0 | while (x < minx - EPS) { |
845 | 0 | x += 2.0 * DEFMODEL_PI; |
846 | 0 | } |
847 | 0 | while (x > maxx + EPS) { |
848 | 0 | x -= 2.0 * DEFMODEL_PI; |
849 | 0 | } |
850 | 0 | } |
851 | 0 | const double miny = extent.minyNormalized(mIsGeographicCRS); |
852 | 0 | const double maxy = extent.maxyNormalized(mIsGeographicCRS); |
853 | 0 | const double extraMarginForInverse = |
854 | 0 | mIsGeographicCRS ? DegToRad(0.1) : 10000; |
855 | 0 | if (!bboxCheck(x, y, forInverseComputation, minx, miny, maxx, maxy, EPS, |
856 | 0 | extraMarginForInverse)) { |
857 | | #ifdef DEBUG_DEFMODEL |
858 | | iface.log("Calculation point " + toString(x) + "," + toString(y) + |
859 | | " is outside the extents of the deformation model"); |
860 | | #endif |
861 | 0 | return false; |
862 | 0 | } |
863 | 0 | } |
864 | | |
865 | | // Check against global model temporal extent |
866 | 0 | { |
867 | 0 | const auto &timeExtent = mModel->timeExtent(); |
868 | 0 | if (t < timeExtent.first.toDecimalYear() || |
869 | 0 | t > timeExtent.last.toDecimalYear()) { |
870 | | #ifdef DEBUG_DEFMODEL |
871 | | iface.log("Calculation epoch " + toString(t) + |
872 | | " is not valid for the deformation model"); |
873 | | #endif |
874 | 0 | return false; |
875 | 0 | } |
876 | 0 | } |
877 | | |
878 | | // For mIsHorizontalUnitDegree |
879 | 0 | double dlam = 0; |
880 | 0 | double dphi = 0; |
881 | | |
882 | | // For !mIsHorizontalUnitDegree |
883 | 0 | double de = 0; |
884 | 0 | double dn = 0; |
885 | |
|
886 | 0 | double dz = 0; |
887 | |
|
888 | 0 | bool sincosphiInitialized = false; |
889 | 0 | double sinphi = 0; |
890 | 0 | double cosphi = 0; |
891 | |
|
892 | 0 | for (auto &compEx : mComponents) { |
893 | 0 | const auto &comp = compEx->component; |
894 | 0 | if (compEx->displacementType == DisplacementType::NONE) { |
895 | 0 | continue; |
896 | 0 | } |
897 | 0 | const auto &extent = comp.extent(); |
898 | 0 | double xForGrid = x; |
899 | 0 | double yForGrid = y; |
900 | 0 | const double minx = extent.minxNormalized(mIsGeographicCRS); |
901 | 0 | const double maxx = extent.maxxNormalized(mIsGeographicCRS); |
902 | 0 | const double miny = extent.minyNormalized(mIsGeographicCRS); |
903 | 0 | const double maxy = extent.maxyNormalized(mIsGeographicCRS); |
904 | 0 | const double extraMarginForInverse = 0; |
905 | 0 | if (!bboxCheck(xForGrid, yForGrid, forInverseComputation, minx, miny, |
906 | 0 | maxx, maxy, EPS, extraMarginForInverse)) { |
907 | | #ifdef DEBUG_DEFMODEL |
908 | | iface.log( |
909 | | "Skipping component " + shortName(comp) + |
910 | | " due to point being outside of its declared spatial extent."); |
911 | | #endif |
912 | 0 | continue; |
913 | 0 | } |
914 | 0 | xForGrid = std::max(xForGrid, minx); |
915 | 0 | yForGrid = std::max(yForGrid, miny); |
916 | 0 | xForGrid = std::min(xForGrid, maxx); |
917 | 0 | yForGrid = std::min(yForGrid, maxy); |
918 | 0 | const auto tfactor = compEx->evaluateAt(t); |
919 | 0 | if (tfactor == 0.0) { |
920 | | #ifdef DEBUG_DEFMODEL |
921 | | iface.log("Skipping component " + shortName(comp) + |
922 | | " due to time function evaluating to 0."); |
923 | | #endif |
924 | 0 | continue; |
925 | 0 | } |
926 | | |
927 | | #ifdef DEBUG_DEFMODEL |
928 | | iface.log("Entering component " + shortName(comp) + |
929 | | " with time function evaluating to " + toString(tfactor) + |
930 | | "."); |
931 | | #endif |
932 | | |
933 | 0 | if (compEx->gridSet == nullptr) { |
934 | 0 | compEx->gridSet = iface.open(comp.spatialModel().filename); |
935 | 0 | if (compEx->gridSet == nullptr) { |
936 | 0 | return false; |
937 | 0 | } |
938 | 0 | } |
939 | 0 | const Grid *grid = compEx->gridSet->gridAt(xForGrid, yForGrid); |
940 | 0 | if (grid == nullptr) { |
941 | | #ifdef DEBUG_DEFMODEL |
942 | | iface.log("Skipping component " + shortName(comp) + |
943 | | " due to no grid found for this point in the grid set."); |
944 | | #endif |
945 | 0 | continue; |
946 | 0 | } |
947 | 0 | if (grid->width < 2 || grid->height < 2) { |
948 | 0 | return false; |
949 | 0 | } |
950 | 0 | const double ix_d = (xForGrid - grid->minx) / grid->resx; |
951 | 0 | const double iy_d = (yForGrid - grid->miny) / grid->resy; |
952 | 0 | if (ix_d < -EPS || iy_d < -EPS || ix_d + 1 >= grid->width + EPS || |
953 | 0 | iy_d + 1 >= grid->height + EPS) { |
954 | | #ifdef DEBUG_DEFMODEL |
955 | | iface.log("Skipping component " + shortName(comp) + |
956 | | " due to point being outside of actual spatial extent of " |
957 | | "grid " + |
958 | | grid->name() + "."); |
959 | | #endif |
960 | 0 | continue; |
961 | 0 | } |
962 | 0 | const int ix0 = std::min(static_cast<int>(ix_d), grid->width - 2); |
963 | 0 | const int iy0 = std::min(static_cast<int>(iy_d), grid->height - 2); |
964 | 0 | const int ix1 = ix0 + 1; |
965 | 0 | const int iy1 = iy0 + 1; |
966 | 0 | const double frct_x = ix_d - ix0; |
967 | 0 | const double frct_y = iy_d - iy0; |
968 | 0 | const double one_minus_frct_x = 1. - frct_x; |
969 | 0 | const double one_minus_frct_y = 1. - frct_y; |
970 | 0 | const double m00 = one_minus_frct_x * one_minus_frct_y; |
971 | 0 | const double m10 = frct_x * one_minus_frct_y; |
972 | 0 | const double m01 = one_minus_frct_x * frct_y; |
973 | 0 | const double m11 = frct_x * frct_y; |
974 | |
|
975 | 0 | if (compEx->displacementType == DisplacementType::VERTICAL) { |
976 | 0 | double dz00 = 0; |
977 | 0 | double dz01 = 0; |
978 | 0 | double dz10 = 0; |
979 | 0 | double dz11 = 0; |
980 | 0 | if (!grid->getZOffset(ix0, iy0, dz00) || |
981 | 0 | !grid->getZOffset(ix1, iy0, dz10) || |
982 | 0 | !grid->getZOffset(ix0, iy1, dz01) || |
983 | 0 | !grid->getZOffset(ix1, iy1, dz11)) { |
984 | 0 | return false; |
985 | 0 | } |
986 | 0 | const double dzInterp = |
987 | 0 | dz00 * m00 + dz01 * m01 + dz10 * m10 + dz11 * m11; |
988 | | #ifdef DEBUG_DEFMODEL |
989 | | iface.log("tfactor * dzInterp = " + toString(tfactor) + " * " + |
990 | | toString(dzInterp) + "."); |
991 | | #endif |
992 | 0 | dz += tfactor * dzInterp; |
993 | 0 | } else if (mIsHorizontalUnitDegree) { |
994 | 0 | double dx00 = 0; |
995 | 0 | double dy00 = 0; |
996 | 0 | double dx01 = 0; |
997 | 0 | double dy01 = 0; |
998 | 0 | double dx10 = 0; |
999 | 0 | double dy10 = 0; |
1000 | 0 | double dx11 = 0; |
1001 | 0 | double dy11 = 0; |
1002 | 0 | if (compEx->displacementType == DisplacementType::HORIZONTAL) { |
1003 | 0 | if (!grid->getLongLatOffset(ix0, iy0, dx00, dy00) || |
1004 | 0 | !grid->getLongLatOffset(ix1, iy0, dx10, dy10) || |
1005 | 0 | !grid->getLongLatOffset(ix0, iy1, dx01, dy01) || |
1006 | 0 | !grid->getLongLatOffset(ix1, iy1, dx11, dy11)) { |
1007 | 0 | return false; |
1008 | 0 | } |
1009 | 0 | } else /* if (compEx->displacementType == DisplacementType::THREE_D) |
1010 | | */ |
1011 | 0 | { |
1012 | 0 | double dz00 = 0; |
1013 | 0 | double dz01 = 0; |
1014 | 0 | double dz10 = 0; |
1015 | 0 | double dz11 = 0; |
1016 | 0 | if (!grid->getLongLatZOffset(ix0, iy0, dx00, dy00, dz00) || |
1017 | 0 | !grid->getLongLatZOffset(ix1, iy0, dx10, dy10, dz10) || |
1018 | 0 | !grid->getLongLatZOffset(ix0, iy1, dx01, dy01, dz01) || |
1019 | 0 | !grid->getLongLatZOffset(ix1, iy1, dx11, dy11, dz11)) { |
1020 | 0 | return false; |
1021 | 0 | } |
1022 | 0 | const double dzInterp = |
1023 | 0 | dz00 * m00 + dz01 * m01 + dz10 * m10 + dz11 * m11; |
1024 | | #ifdef DEBUG_DEFMODEL |
1025 | | iface.log("tfactor * dzInterp = " + toString(tfactor) + " * " + |
1026 | | toString(dzInterp) + "."); |
1027 | | #endif |
1028 | 0 | dz += tfactor * dzInterp; |
1029 | 0 | } |
1030 | 0 | const double dlamInterp = |
1031 | 0 | dx00 * m00 + dx01 * m01 + dx10 * m10 + dx11 * m11; |
1032 | 0 | const double dphiInterp = |
1033 | 0 | dy00 * m00 + dy01 * m01 + dy10 * m10 + dy11 * m11; |
1034 | | #ifdef DEBUG_DEFMODEL |
1035 | | iface.log("tfactor * dlamInterp = " + toString(tfactor) + " * " + |
1036 | | toString(dlamInterp) + "."); |
1037 | | iface.log("tfactor * dphiInterp = " + toString(tfactor) + " * " + |
1038 | | toString(dphiInterp) + "."); |
1039 | | #endif |
1040 | 0 | dlam += tfactor * dlamInterp; |
1041 | 0 | dphi += tfactor * dphiInterp; |
1042 | 0 | } else /* horizontal unit is metre */ { |
1043 | 0 | double de00 = 0; |
1044 | 0 | double dn00 = 0; |
1045 | 0 | double de01 = 0; |
1046 | 0 | double dn01 = 0; |
1047 | 0 | double de10 = 0; |
1048 | 0 | double dn10 = 0; |
1049 | 0 | double de11 = 0; |
1050 | 0 | double dn11 = 0; |
1051 | 0 | if (compEx->displacementType == DisplacementType::HORIZONTAL) { |
1052 | 0 | if (!grid->getEastingNorthingOffset(ix0, iy0, de00, dn00) || |
1053 | 0 | !grid->getEastingNorthingOffset(ix1, iy0, de10, dn10) || |
1054 | 0 | !grid->getEastingNorthingOffset(ix0, iy1, de01, dn01) || |
1055 | 0 | !grid->getEastingNorthingOffset(ix1, iy1, de11, dn11)) { |
1056 | 0 | return false; |
1057 | 0 | } |
1058 | 0 | } else /* if (compEx->displacementType == DisplacementType::THREE_D) |
1059 | | */ |
1060 | 0 | { |
1061 | 0 | double dz00 = 0; |
1062 | 0 | double dz01 = 0; |
1063 | 0 | double dz10 = 0; |
1064 | 0 | double dz11 = 0; |
1065 | 0 | if (!grid->getEastingNorthingZOffset(ix0, iy0, de00, dn00, |
1066 | 0 | dz00) || |
1067 | 0 | !grid->getEastingNorthingZOffset(ix1, iy0, de10, dn10, |
1068 | 0 | dz10) || |
1069 | 0 | !grid->getEastingNorthingZOffset(ix0, iy1, de01, dn01, |
1070 | 0 | dz01) || |
1071 | 0 | !grid->getEastingNorthingZOffset(ix1, iy1, de11, dn11, |
1072 | 0 | dz11)) { |
1073 | 0 | return false; |
1074 | 0 | } |
1075 | 0 | const double dzInterp = |
1076 | 0 | dz00 * m00 + dz01 * m01 + dz10 * m10 + dz11 * m11; |
1077 | | #ifdef DEBUG_DEFMODEL |
1078 | | iface.log("tfactor * dzInterp = " + toString(tfactor) + " * " + |
1079 | | toString(dzInterp) + "."); |
1080 | | #endif |
1081 | 0 | dz += tfactor * dzInterp; |
1082 | 0 | } |
1083 | 0 | if (compEx->isBilinearInterpolation) { |
1084 | 0 | const double deInterp = |
1085 | 0 | de00 * m00 + de01 * m01 + de10 * m10 + de11 * m11; |
1086 | 0 | const double dnInterp = |
1087 | 0 | dn00 * m00 + dn01 * m01 + dn10 * m10 + dn11 * m11; |
1088 | | #ifdef DEBUG_DEFMODEL |
1089 | | iface.log("tfactor * deInterp = " + toString(tfactor) + " * " + |
1090 | | toString(deInterp) + "."); |
1091 | | iface.log("tfactor * dnInterp = " + toString(tfactor) + " * " + |
1092 | | toString(dnInterp) + "."); |
1093 | | #endif |
1094 | 0 | de += tfactor * deInterp; |
1095 | 0 | dn += tfactor * dnInterp; |
1096 | 0 | } else /* geocentric_bilinear */ { |
1097 | 0 | double dX; |
1098 | 0 | double dY; |
1099 | 0 | double dZ; |
1100 | |
|
1101 | 0 | auto iter = compEx->mapGrids.find(grid); |
1102 | 0 | if (iter == compEx->mapGrids.end()) { |
1103 | 0 | GridEx<Grid> gridWithCache(grid); |
1104 | 0 | iter = compEx->mapGrids |
1105 | 0 | .insert(std::pair<const Grid *, GridEx<Grid>>( |
1106 | 0 | grid, std::move(gridWithCache))) |
1107 | 0 | .first; |
1108 | 0 | } |
1109 | 0 | GridEx<Grid> &gridwithCacheRef = iter->second; |
1110 | |
|
1111 | 0 | gridwithCacheRef.getBilinearGeocentric( |
1112 | 0 | ix0, iy0, de00, dn00, de01, dn01, de10, dn10, de11, dn11, |
1113 | 0 | m00, m01, m10, m11, dX, dY, dZ); |
1114 | 0 | if (!sincosphiInitialized) { |
1115 | 0 | sincosphiInitialized = true; |
1116 | 0 | sinphi = sin(y); |
1117 | 0 | cosphi = cos(y); |
1118 | 0 | } |
1119 | 0 | const double lam_rel_to_cell_center = |
1120 | 0 | (frct_x - 0.5) * grid->resx; |
1121 | | // Use small-angle approximation of sin/cos when reasonable |
1122 | | // Max abs/rel error on cos is 3.9e-9 and on sin 1.3e-11 |
1123 | 0 | const double sinlam = |
1124 | 0 | gridwithCacheRef.smallResx |
1125 | 0 | ? lam_rel_to_cell_center * |
1126 | 0 | (1 - (1. / 6) * (lam_rel_to_cell_center * |
1127 | 0 | lam_rel_to_cell_center)) |
1128 | 0 | : sin(lam_rel_to_cell_center); |
1129 | 0 | const double coslam = gridwithCacheRef.smallResx |
1130 | 0 | ? (1 - 0.5 * (lam_rel_to_cell_center * |
1131 | 0 | lam_rel_to_cell_center)) |
1132 | 0 | : cos(lam_rel_to_cell_center); |
1133 | | |
1134 | | // Convert back from geocentric deltas to easting, northing |
1135 | | // deltas |
1136 | 0 | const double deInterp = -dX * sinlam + dY * coslam; |
1137 | 0 | const double dnInterp = |
1138 | 0 | (-dX * coslam - dY * sinlam) * sinphi + dZ * cosphi; |
1139 | | #ifdef DEBUG_DEFMODEL |
1140 | | iface.log("After geocentric_bilinear interpolation: tfactor * " |
1141 | | "deInterp = " + |
1142 | | toString(tfactor) + " * " + toString(deInterp) + "."); |
1143 | | iface.log("After geocentric_bilinear interpolation: tfactor * " |
1144 | | "dnInterp = " + |
1145 | | toString(tfactor) + " * " + toString(dnInterp) + "."); |
1146 | | #endif |
1147 | 0 | de += tfactor * deInterp; |
1148 | 0 | dn += tfactor * dnInterp; |
1149 | 0 | } |
1150 | 0 | } |
1151 | 0 | } |
1152 | | |
1153 | | // Apply shifts depending on horizontal_offset_unit and |
1154 | | // horizontal_offset_method |
1155 | 0 | if (mIsHorizontalUnitDegree) { |
1156 | 0 | x_out += dlam; |
1157 | 0 | y_out += dphi; |
1158 | 0 | } else { |
1159 | | #ifdef DEBUG_DEFMODEL |
1160 | | iface.log("Total sum of de: " + toString(de)); |
1161 | | iface.log("Total sum of dn: " + toString(dn)); |
1162 | | #endif |
1163 | 0 | if (mIsAddition && !mIsGeographicCRS) { |
1164 | 0 | x_out += de; |
1165 | 0 | y_out += dn; |
1166 | 0 | } else if (mIsAddition) { |
1167 | | // Simple way of adding the offset |
1168 | 0 | if (!sincosphiInitialized) { |
1169 | 0 | cosphi = cos(y); |
1170 | 0 | } |
1171 | 0 | DeltaEastingNorthingToLongLat(cosphi, de, dn, mA, mB, mEs, dlam, |
1172 | 0 | dphi); |
1173 | | #ifdef DEBUG_DEFMODEL |
1174 | | iface.log("Result dlam: " + toString(dlam)); |
1175 | | iface.log("Result dphi: " + toString(dphi)); |
1176 | | #endif |
1177 | 0 | x_out += dlam; |
1178 | 0 | y_out += dphi; |
1179 | 0 | } else { |
1180 | | // Geocentric way of adding the offset |
1181 | 0 | if (!sincosphiInitialized) { |
1182 | 0 | sinphi = sin(y); |
1183 | 0 | cosphi = cos(y); |
1184 | 0 | } |
1185 | 0 | const double sinlam = sin(x); |
1186 | 0 | const double coslam = cos(x); |
1187 | 0 | const double dnsinphi = dn * sinphi; |
1188 | 0 | const double dX = -de * sinlam - dnsinphi * coslam; |
1189 | 0 | const double dY = de * coslam - dnsinphi * sinlam; |
1190 | 0 | const double dZ = dn * cosphi; |
1191 | |
|
1192 | 0 | double X; |
1193 | 0 | double Y; |
1194 | 0 | double Z; |
1195 | 0 | iface.geographicToGeocentric(x, y, 0, mA, mB, mEs, X, Y, Z); |
1196 | | #ifdef DEBUG_DEFMODEL |
1197 | | iface.log("Geocentric coordinate before: " + toString(X) + "," + |
1198 | | toString(Y) + "," + toString(Z)); |
1199 | | iface.log("Geocentric shift: " + toString(dX) + "," + toString(dY) + |
1200 | | "," + toString(dZ)); |
1201 | | #endif |
1202 | 0 | X += dX; |
1203 | 0 | Y += dY; |
1204 | 0 | Z += dZ; |
1205 | | #ifdef DEBUG_DEFMODEL |
1206 | | iface.log("Geocentric coordinate after: " + toString(X) + "," + |
1207 | | toString(Y) + "," + toString(Z)); |
1208 | | #endif |
1209 | |
|
1210 | 0 | double h_out_ignored; |
1211 | 0 | iface.geocentricToGeographic(X, Y, Z, mA, mB, mEs, x_out, y_out, |
1212 | 0 | h_out_ignored); |
1213 | 0 | } |
1214 | 0 | } |
1215 | | #ifdef DEBUG_DEFMODEL |
1216 | | iface.log("Total sum of dz: " + toString(dz)); |
1217 | | #endif |
1218 | 0 | z_out += dz; |
1219 | |
|
1220 | 0 | return true; |
1221 | 0 | } |
1222 | | |
1223 | | // --------------------------------------------------------------------------- |
1224 | | |
1225 | | template <class Grid, class GridSet, class EvaluatorIface> |
1226 | | bool Evaluator<Grid, GridSet, EvaluatorIface>::inverse( |
1227 | | EvaluatorIface &iface, double x, double y, double z, double t, |
1228 | | double &x_out, double &y_out, double &z_out) |
1229 | | |
1230 | 0 | { |
1231 | 0 | x_out = x; |
1232 | 0 | y_out = y; |
1233 | 0 | z_out = z; |
1234 | 0 | constexpr double EPS_HORIZ = 1e-12; |
1235 | 0 | constexpr double EPS_VERT = 1e-3; |
1236 | 0 | constexpr bool forInverseComputation = true; |
1237 | 0 | for (int i = 0; i < 10; i++) { |
1238 | | #ifdef DEBUG_DEFMODEL |
1239 | | iface.log("Iteration " + std::to_string(i) + ": before forward: x=" + |
1240 | | toString(x_out) + ", y=" + toString(y_out)); |
1241 | | #endif |
1242 | 0 | double x_new; |
1243 | 0 | double y_new; |
1244 | 0 | double z_new; |
1245 | 0 | if (!forward(iface, x_out, y_out, z_out, t, forInverseComputation, |
1246 | 0 | x_new, y_new, z_new)) { |
1247 | 0 | return false; |
1248 | 0 | } |
1249 | | #ifdef DEBUG_DEFMODEL |
1250 | | iface.log("After forward: x=" + toString(x_new) + |
1251 | | ", y=" + toString(y_new)); |
1252 | | #endif |
1253 | 0 | const double dx = x_new - x; |
1254 | 0 | const double dy = y_new - y; |
1255 | 0 | const double dz = z_new - z; |
1256 | 0 | x_out -= dx; |
1257 | 0 | y_out -= dy; |
1258 | 0 | z_out -= dz; |
1259 | 0 | if (std::max(std::fabs(dx), std::fabs(dy)) < EPS_HORIZ && |
1260 | 0 | std::fabs(dz) < EPS_VERT) { |
1261 | 0 | return true; |
1262 | 0 | } |
1263 | 0 | } |
1264 | 0 | return false; |
1265 | 0 | } |
1266 | | |
1267 | | // --------------------------------------------------------------------------- |
1268 | | |
1269 | | } // namespace DEFORMATON_MODEL_NAMESPACE |