Coverage Report

Created: 2025-07-11 06:33

/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