Coverage Report

Created: 2026-07-25 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proj/src/transformations/tinshift_gpkg.cpp
Line
Count
Source
1
/******************************************************************************
2
 * Project:  PROJ
3
 * Purpose:  Functionality related to TIN based transformations using a
4
 *GeoPackage file Author:   Even Rouault, <even.rouault at spatialys.com>
5
 *
6
 ******************************************************************************
7
 * Copyright (c) 2025, Even Rouault, <even.rouault at spatialys.com>
8
 *
9
 * SPDX-License-Identifier: MIT
10
 *****************************************************************************/
11
12
#define FROM_PROJ_CPP
13
14
#include "tinshift_gpkg.hpp"
15
#include "proj/internal/include_nlohmann_json.hpp"
16
#include "proj/internal/internal.hpp"
17
18
#ifdef EMBED_RESOURCE_FILES
19
#include "embedded_resources.h"
20
#endif
21
22
#include <algorithm>
23
#include <array>
24
#include <cassert>
25
#include <cmath>
26
#include <cstring>
27
#include <limits>
28
#include <sstream> // std::ostringstream
29
30
#include <sqlite3.h>
31
32
using json = nlohmann::json;
33
using namespace NS_PROJ;
34
35
// ---------------------------------------------------------------------------
36
37
0
const char *TINShiftGeopackageException::what() const noexcept {
38
0
    return msg_.c_str();
39
0
}
40
41
// ---------------------------------------------------------------------------
42
43
std::unique_ptr<TINShiftGeopackageFile>
44
0
TINShiftGeopackageFile::open(PJ_CONTEXT *ctx, const std::string &filename) {
45
0
    std::string sqliteURI(filename);
46
47
0
    auto poTinshift =
48
0
        std::unique_ptr<TINShiftGeopackageFile>(new TINShiftGeopackageFile());
49
0
    if (internal::starts_with(filename, "http://") ||
50
0
        internal::starts_with(filename, "https://")) {
51
0
        poTinshift->sqlite_vfs_ = SQLite3VFS::createNetwork(ctx);
52
0
    }
53
54
0
    while (true) {
55
0
        if (sqlite3_open_v2(sqliteURI.c_str(), &poTinshift->sqlite_handle_,
56
0
                            SQLITE_OPEN_READONLY | SQLITE_OPEN_URI,
57
0
                            poTinshift->sqlite_vfs_
58
0
                                ? poTinshift->sqlite_vfs_->name()
59
0
                                : nullptr) != SQLITE_OK ||
60
0
            !poTinshift->sqlite_handle_) {
61
62
0
#ifdef EMBED_RESOURCE_FILES
63
0
            if (!poTinshift->sqlite_vfs_) {
64
0
                unsigned int size = 0;
65
0
                const unsigned char *in_memory_data =
66
0
                    pj_get_embedded_resource(filename.c_str(), &size);
67
0
                if (!in_memory_data) {
68
0
                    throw TINShiftGeopackageException(std::string("Open of ")
69
0
                                                          .append(filename)
70
0
                                                          .append(" failed"));
71
0
                }
72
73
0
                poTinshift->sqlite_vfs_ =
74
0
                    SQLite3VFS::createMem(in_memory_data, size);
75
0
                if (poTinshift->sqlite_vfs_ == nullptr) {
76
0
                    throw TINShiftGeopackageException(std::string("Open of ")
77
0
                                                          .append(filename)
78
0
                                                          .append(" failed"));
79
0
                }
80
81
0
                std::ostringstream buffer;
82
0
                buffer << "file:/";
83
0
                buffer << filename;
84
0
                buffer << "?immutable=1&ptr=";
85
0
                buffer << reinterpret_cast<uintptr_t>(in_memory_data);
86
0
                buffer << "&sz=";
87
0
                buffer << size;
88
0
                buffer << "&max=";
89
0
                buffer << size;
90
0
                buffer << "&vfs=";
91
0
                buffer << poTinshift->sqlite_vfs_->name();
92
0
                sqliteURI = buffer.str();
93
94
0
                continue;
95
0
            }
96
0
#endif
97
98
0
            throw TINShiftGeopackageException(
99
0
                std::string("Open of ").append(sqliteURI).append(" failed"));
100
0
        } else {
101
0
            break;
102
0
        }
103
0
    }
104
105
0
    poTinshift->getMetadata();
106
0
    poTinshift->prepareDatabaseQuery();
107
108
0
    return poTinshift;
109
0
}
110
111
// ---------------------------------------------------------------------------
112
113
0
TINShiftGeopackageFile::~TINShiftGeopackageFile() {
114
0
    if (select_stmt_)
115
0
        sqlite3_finalize(select_stmt_);
116
0
    if (sqlite_handle_)
117
0
        sqlite3_close(sqlite_handle_);
118
0
}
119
120
// ---------------------------------------------------------------------------
121
122
namespace {
123
124
0
static std::string getString(const json &j, const char *key, bool optional) {
125
0
    if (!j.contains(key)) {
126
0
        if (optional) {
127
0
            return std::string();
128
0
        }
129
0
        throw TINShiftGeopackageException(std::string("Missing \"") + key +
130
0
                                          "\" key");
131
0
    }
132
0
    const json v = j[key];
133
0
    if (!v.is_string()) {
134
0
        throw TINShiftGeopackageException(std::string("The value of \"") + key +
135
0
                                          "\" should be a string");
136
0
    }
137
0
    return v.get<std::string>();
138
0
}
139
140
0
static std::string getReqString(const json &j, const char *key) {
141
0
    return getString(j, key, false);
142
0
}
143
144
0
static double getReqDouble(const json &j, const char *key) {
145
0
    if (!j.contains(key)) {
146
0
        throw TINShiftGeopackageException(std::string("Missing \"") + key +
147
0
                                          "\" key");
148
0
    }
149
150
0
    const json v = j[key];
151
0
    if (!v.is_number()) {
152
0
        throw TINShiftGeopackageException(std::string("The value of \"") + key +
153
0
                                          "\" should be a number");
154
0
    }
155
0
    return v.get<double>();
156
0
}
157
158
0
static int getReqInt(const json &j, const char *key) {
159
0
    if (!j.contains(key)) {
160
0
        throw TINShiftGeopackageException(std::string("Missing \"") + key +
161
0
                                          "\" key");
162
0
    }
163
164
0
    const json v = j[key];
165
0
    if (!v.is_number()) {
166
0
        throw TINShiftGeopackageException(std::string("The value of \"") + key +
167
0
                                          "\" should be an integer");
168
0
    }
169
0
    return v.get<int>();
170
0
}
171
172
// ---------------------------------------------------------------------------
173
174
0
static json getArrayMember(const json &j, const char *key) {
175
0
    if (!j.contains(key)) {
176
0
        throw TINShiftGeopackageException(std::string("Missing \"") + key +
177
0
                                          "\" key");
178
0
    }
179
0
    const json obj = j[key];
180
0
    if (!obj.is_array()) {
181
0
        throw TINShiftGeopackageException(std::string("The value of \"") + key +
182
0
                                          "\" should be a array");
183
0
    }
184
0
    return obj;
185
0
}
186
187
} // namespace
188
// ---------------------------------------------------------------------------
189
190
0
void TINShiftGeopackageFile::getMetadata() {
191
0
    std::string metadata;
192
    // Get JSON metadata entry from gpkg_metadata table
193
0
    {
194
0
        char **papszResult = nullptr;
195
0
        int nRows = 0;
196
0
        char *pszErrMsg = nullptr;
197
0
        if (sqlite3_get_table(sqlite_handle_,
198
0
                              "SELECT metadata FROM gpkg_metadata WHERE "
199
0
                              "md_standard_uri = 'https://proj.org'",
200
0
                              &papszResult, &nRows, nullptr,
201
0
                              &pszErrMsg) != SQLITE_OK) {
202
0
            sqlite3_free_table(papszResult);
203
0
            auto ex = TINShiftGeopackageException(
204
0
                std::string("Cannot get metadata: ").append(pszErrMsg));
205
0
            sqlite3_free(pszErrMsg);
206
0
            throw ex;
207
0
        }
208
0
        if (nRows != 1 || !papszResult[1]) {
209
0
            sqlite3_free_table(papszResult);
210
0
            throw TINShiftGeopackageException(
211
0
                "Cannot get metadata in gpkg_metadata table");
212
0
        }
213
0
        metadata = papszResult[1];
214
0
        sqlite3_free_table(papszResult);
215
0
    }
216
217
0
    json j;
218
0
    try {
219
0
        j = json::parse(metadata);
220
0
    } catch (const std::exception &e) {
221
0
        throw TINShiftGeopackageException(
222
0
            std::string("Cannot parse JSON metadata: ").append(e.what()));
223
0
    }
224
0
    if (!j.is_object()) {
225
0
        throw TINShiftGeopackageException("JSON metadata is not an object");
226
0
    }
227
228
0
    if (getReqString(j, "file_type") != "triangulation_file") {
229
0
        throw TINShiftGeopackageException("File is not of the expected type");
230
0
    }
231
232
0
    const std::string version = getReqString(j, "format_version");
233
0
    if (!internal::starts_with(version, "1.")) {
234
0
        throw TINShiftGeopackageException(std::string("format_version = ")
235
0
                                              .append(version)
236
0
                                              .append(" is not supported"));
237
0
    }
238
239
0
    const auto jTransformedComponents =
240
0
        getArrayMember(j, "transformed_components");
241
0
    for (const json &jComp : jTransformedComponents) {
242
0
        if (!jComp.is_string()) {
243
0
            throw TINShiftGeopackageException(
244
0
                "transformed_components[] item is not a string");
245
0
        }
246
0
        const auto jCompStr = jComp.get<std::string>();
247
0
        if (jCompStr == "horizontal") {
248
0
            horizontalTransformation_ = true;
249
0
        } else if (jCompStr == "vertical") {
250
0
            verticalTransformation_ = true;
251
0
        } else {
252
0
            throw TINShiftGeopackageException(
253
0
                "transformed_components[] = " + jCompStr + " is not handled");
254
0
        }
255
0
    }
256
257
0
    if (horizontalTransformation_) {
258
0
        min_shift_x_ = getReqDouble(j, "min_shift_x");
259
0
        min_shift_y_ = getReqDouble(j, "min_shift_y");
260
0
        max_shift_x_ = getReqDouble(j, "max_shift_x");
261
0
        max_shift_y_ = getReqDouble(j, "max_shift_y");
262
0
    }
263
264
0
    if (j.contains("fallback_strategy")) {
265
0
        const auto fallback_strategy = getReqString(j, "fallback_strategy");
266
0
        if (fallback_strategy == "nearest_side") {
267
0
            fallbackStrategy_ = FALLBACK_NEAREST_SIDE;
268
0
        } else if (fallback_strategy == "nearest_centroid") {
269
0
            fallbackStrategy_ = FALLBACK_NEAREST_CENTROID;
270
0
        } else if (fallback_strategy == "none") {
271
0
            fallbackStrategy_ = FALLBACK_NONE;
272
0
        } else {
273
0
            throw TINShiftGeopackageException("invalid fallback_strategy");
274
0
        }
275
0
    }
276
277
0
    if (fallbackStrategy_ != FALLBACK_NONE) {
278
0
        numVertices_ = getReqInt(j, "num_vertices");
279
0
        if (numVertices_ <= 0) {
280
0
            throw TINShiftGeopackageException("invalid value for num_vertices");
281
0
        }
282
0
    }
283
284
    // Get bounding box of vertices table
285
0
    {
286
0
        char **papszResult = nullptr;
287
0
        int nRows = 0;
288
0
        char *pszErrMsg = nullptr;
289
0
        if (sqlite3_get_table(sqlite_handle_,
290
0
                              "SELECT min_x, min_y, max_x, max_y FROM "
291
0
                              "gpkg_contents WHERE table_name = 'vertices'",
292
0
                              &papszResult, &nRows, nullptr,
293
0
                              &pszErrMsg) != SQLITE_OK) {
294
0
            sqlite3_free_table(papszResult);
295
0
            auto ex = TINShiftGeopackageException(
296
0
                std::string("Cannot get bounding box of vertices table: ")
297
0
                    .append(pszErrMsg));
298
0
            sqlite3_free(pszErrMsg);
299
0
            throw ex;
300
0
        }
301
0
        if (nRows != 1 || !papszResult[4] || !papszResult[5] ||
302
0
            !papszResult[6] || !papszResult[7]) {
303
0
            sqlite3_free_table(papszResult);
304
0
            throw TINShiftGeopackageException(
305
0
                "Cannot get bounding box of vertices table");
306
0
        }
307
0
        min_x_ = internal::c_locale_stod(papszResult[4]);
308
0
        min_y_ = internal::c_locale_stod(papszResult[5]);
309
0
        max_x_ = internal::c_locale_stod(papszResult[6]);
310
0
        max_y_ = internal::c_locale_stod(papszResult[7]);
311
0
        sqlite3_free_table(papszResult);
312
0
        if (!(min_x_ < max_x_) || !(min_y_ < max_y_)) {
313
0
            throw TINShiftGeopackageException(
314
0
                "Invalid bounding box of vertices table");
315
0
        }
316
0
    }
317
0
}
318
319
// ---------------------------------------------------------------------------
320
321
0
void TINShiftGeopackageFile::prepareDatabaseQuery() {
322
    // Get field names of vertices table
323
0
    {
324
0
        char **papszResult = nullptr;
325
0
        int nRows = 0;
326
0
        int nCols = 0;
327
0
        char *pszErrMsg = nullptr;
328
0
        if (sqlite3_get_table(sqlite_handle_, "PRAGMA table_info(vertices)",
329
0
                              &papszResult, &nRows, &nCols,
330
0
                              &pszErrMsg) != SQLITE_OK) {
331
0
            sqlite3_free_table(papszResult);
332
0
            auto ex = TINShiftGeopackageException(
333
0
                std::string("Cannot get definition of table vertices: ")
334
0
                    .append(pszErrMsg));
335
0
            sqlite3_free(pszErrMsg);
336
0
            throw ex;
337
0
        }
338
0
        if (nRows == 0 || nCols != 6) {
339
0
            sqlite3_free_table(papszResult);
340
0
            throw TINShiftGeopackageException(
341
0
                "Cannot get definition of table vertices");
342
0
        }
343
0
        for (int i = 0; i < nRows; ++i) {
344
0
            const char *pszColName = papszResult[(i + 1) * nCols + 1];
345
0
            if (pszColName && strcmp(pszColName, "fid") != 0 &&
346
0
                strcmp(pszColName, "geom") != 0) {
347
0
                std::string osColName(pszColName);
348
0
                if (osColName == "source_z")
349
0
                    sourceZ_ = true;
350
0
                else if (osColName == "target_x")
351
0
                    targetX_ = true;
352
0
                else if (osColName == "target_y")
353
0
                    targetY_ = true;
354
0
                else if (osColName == "target_z")
355
0
                    targetZ_ = true;
356
0
                else if (osColName == "offset_z")
357
0
                    offsetZ_ = true;
358
0
                vertices_cols_.push_back(std::move(osColName));
359
0
            }
360
0
        }
361
0
        sqlite3_free_table(papszResult);
362
0
    }
363
364
0
    if (horizontalTransformation_ && !targetX_)
365
0
        throw TINShiftGeopackageException(
366
0
            "target_x field missing in table vertices");
367
0
    if (horizontalTransformation_ && !targetY_)
368
0
        throw TINShiftGeopackageException(
369
0
            "target_y field missing in table vertices");
370
0
    if (verticalTransformation_ && !((sourceZ_ && targetZ_) || offsetZ_))
371
0
        throw TINShiftGeopackageException("(source_z and target_z) or offset_z "
372
0
                                          "fields missing in table vertices");
373
374
0
    std::vector<std::string> colsToRequest;
375
0
    if (horizontalTransformation_) {
376
0
        colsToRequest.push_back("target_x");
377
0
        colsToRequest.push_back("target_y");
378
0
    }
379
0
    if (verticalTransformation_) {
380
0
        if (sourceZ_) {
381
0
            colsToRequest.push_back("source_z");
382
0
            colsToRequest.push_back("target_z");
383
0
        } else {
384
0
            colsToRequest.push_back("offset_z");
385
0
        }
386
0
    }
387
388
0
    std::string sql(
389
0
        "SELECT v1.geom AS v1_geom, v2.geom AS v2_geom, v3.geom AS v3_geom");
390
0
    for (const std::string &col : colsToRequest) {
391
0
        sql += ", v1.";
392
0
        sql += col;
393
0
        sql += " AS v1_";
394
0
        sql += col;
395
396
0
        sql += ", v2.";
397
0
        sql += col;
398
0
        sql += " AS v2_";
399
0
        sql += col;
400
401
0
        sql += ", v3.";
402
0
        sql += col;
403
0
        sql += " AS v3_";
404
0
        sql += col;
405
0
    }
406
0
    sql += " FROM triangles_def "
407
0
           "LEFT JOIN vertices v1 ON idx_vertex1 = v1.fid "
408
0
           "LEFT JOIN vertices v2 ON idx_vertex2 = v2.fid "
409
0
           "LEFT JOIN vertices v3 ON idx_vertex3 = v3.fid "
410
0
           "WHERE triangles_def.fid IN ("
411
0
           "SELECT id FROM rtree_triangles_geom "
412
0
           "WHERE maxx >= ? AND minx <= ? AND maxy >= ? AND miny <= ?)";
413
414
0
    sqlite3_prepare_v2(sqlite_handle_, sql.c_str(), -1, &select_stmt_, nullptr);
415
0
    if (!select_stmt_) {
416
0
        throw TINShiftGeopackageException(sqlite3_errmsg(sqlite_handle_));
417
0
    }
418
0
}
419
420
// ---------------------------------------------------------------------------
421
422
/************************************************************************/
423
/*                             swap_words()                             */
424
/*                                                                      */
425
/*      Convert the byte order of the given word(s) in place.           */
426
/************************************************************************/
427
428
static const int byte_order_test = 1;
429
#define IS_LSB                                                                 \
430
0
    (1 == (reinterpret_cast<const unsigned char *>(&byte_order_test))[0])
431
432
static void swap_words(void *dataIn, size_t word_size, size_t word_count)
433
434
0
{
435
0
    unsigned char *data = static_cast<unsigned char *>(dataIn);
436
0
    for (size_t word = 0; word < word_count; word++) {
437
0
        for (size_t i = 0; i < word_size / 2; i++) {
438
0
            unsigned char t;
439
440
0
            t = data[i];
441
0
            data[i] = data[word_size - i - 1];
442
0
            data[word_size - i - 1] = t;
443
0
        }
444
445
0
        data += word_size;
446
0
    }
447
0
}
448
449
// ---------------------------------------------------------------------------
450
451
0
static inline double sqr(double x) { return x * x; }
452
static inline double squared_distance(double x1, double y1, double x2,
453
0
                                      double y2) {
454
0
    return sqr(x1 - x2) + sqr(y1 - y2);
455
0
}
456
static double sq_distance_point_segment(double x, double y, double x1,
457
                                        double y1, double x2, double y2,
458
0
                                        double dist12) {
459
    // squared distance of point x/y to line segment x1/y1 -- x2/y2
460
0
    const double t = ((x - x1) * (x2 - x1) + (y - y1) * (y2 - y1)) / dist12;
461
0
    if (t <= 0.0) {
462
        // closest to x1/y1
463
0
        return squared_distance(x, y, x1, y1);
464
0
    }
465
0
    if (t >= 1.0) {
466
        // closest to y2/y2
467
0
        return squared_distance(x, y, x2, y2);
468
0
    }
469
470
    // closest to line segment x1/y1 -- x2/y2
471
0
    return squared_distance(x, y, x1 + t * (x2 - x1), y1 + t * (y2 - y1));
472
0
}
473
474
// ---------------------------------------------------------------------------
475
476
bool TINShiftGeopackageFile::findTriangle(
477
    double x, double y, bool forwardDirection, double &lambda1, double &lambda2,
478
    double &lambda3, std::vector<double> &valsVertex1,
479
0
    std::vector<double> &valsVertex2, std::vector<double> &valsVertex3) {
480
481
0
    const bool useForwardDirection =
482
0
        forwardDirection || !horizontalTransformation_;
483
484
    // Compute barycentric coefficients
485
0
    const auto computeLambdas = [x, y, &lambda1, &lambda2,
486
0
                                 &lambda3](const std::array<double, 3> &vX,
487
0
                                           const std::array<double, 3> &vY,
488
0
                                           bool checkWithinTriangle) {
489
0
        constexpr double EPSILON = 1e-10;
490
0
        const double det_T = (vY[1] - vY[2]) * (vX[0] - vX[2]) +
491
0
                             (vX[2] - vX[1]) * (vY[0] - vY[2]);
492
0
        if (std::fabs(det_T) < EPSILON) {
493
            // Degenerate triangle
494
0
            return false;
495
0
        }
496
0
        lambda1 =
497
0
            ((vY[1] - vY[2]) * (x - vX[2]) + (vX[2] - vX[1]) * (y - vY[2])) /
498
0
            det_T;
499
0
        lambda2 =
500
0
            ((vY[2] - vY[0]) * (x - vX[2]) + (vX[0] - vX[2]) * (y - vY[2])) /
501
0
            det_T;
502
0
        lambda3 = 1 - lambda1 - lambda2;
503
0
        return !checkWithinTriangle ||
504
0
               (lambda1 >= -EPSILON && lambda1 <= 1 + EPSILON &&
505
0
                lambda2 >= -EPSILON && lambda2 <= 1 + EPSILON &&
506
0
                lambda3 >= -EPSILON && lambda3 <= 1 + EPSILON);
507
0
    };
508
509
    // Get the current triangle's vertices coordinates
510
0
    const auto getXY = [this, useForwardDirection](std::array<double, 3> &vX,
511
0
                                                   std::array<double, 3> &vY) {
512
0
        if (useForwardDirection) {
513
0
            const unsigned char *v1_geom = static_cast<const unsigned char *>(
514
0
                sqlite3_column_blob(select_stmt_, 0));
515
0
            const unsigned char *v2_geom = static_cast<const unsigned char *>(
516
0
                sqlite3_column_blob(select_stmt_, 1));
517
0
            const unsigned char *v3_geom = static_cast<const unsigned char *>(
518
0
                sqlite3_column_blob(select_stmt_, 2));
519
0
            constexpr int SIZEOF_GPKG_POINT_PREFIX = 8 + 1 + 4;
520
0
            constexpr int SIZEOF_GPKG_POINT =
521
0
                SIZEOF_GPKG_POINT_PREFIX + 2 * static_cast<int>(sizeof(double));
522
0
            if (sqlite3_column_bytes(select_stmt_, 0) != SIZEOF_GPKG_POINT ||
523
0
                sqlite3_column_bytes(select_stmt_, 1) != SIZEOF_GPKG_POINT ||
524
0
                sqlite3_column_bytes(select_stmt_, 2) != SIZEOF_GPKG_POINT) {
525
0
                return false;
526
0
            }
527
528
0
            memcpy(&vX[0], v1_geom + SIZEOF_GPKG_POINT_PREFIX, sizeof(double));
529
0
            memcpy(&vY[0], v1_geom + SIZEOF_GPKG_POINT_PREFIX + sizeof(double),
530
0
                   sizeof(double));
531
0
            memcpy(&vX[1], v2_geom + SIZEOF_GPKG_POINT_PREFIX, sizeof(double));
532
0
            memcpy(&vY[1], v2_geom + SIZEOF_GPKG_POINT_PREFIX + sizeof(double),
533
0
                   sizeof(double));
534
0
            memcpy(&vX[2], v3_geom + SIZEOF_GPKG_POINT_PREFIX, sizeof(double));
535
0
            memcpy(&vY[2], v3_geom + SIZEOF_GPKG_POINT_PREFIX + sizeof(double),
536
0
                   sizeof(double));
537
538
0
            if (!IS_LSB) {
539
0
                swap_words(vX.data(), sizeof(double), vX.size());
540
0
                swap_words(vY.data(), sizeof(double), vY.size());
541
0
            }
542
0
        } else {
543
0
            vX[0] = sqlite3_column_double(select_stmt_, 3);
544
0
            vX[1] = sqlite3_column_double(select_stmt_, 4);
545
0
            vX[2] = sqlite3_column_double(select_stmt_, 5);
546
0
            vY[0] = sqlite3_column_double(select_stmt_, 6);
547
0
            vY[1] = sqlite3_column_double(select_stmt_, 7);
548
0
            vY[2] = sqlite3_column_double(select_stmt_, 8);
549
0
        }
550
0
        return true;
551
0
    };
552
553
    // Get the values at the vertices of the current triangle
554
0
    const auto collectValsVertex = [this, useForwardDirection, &valsVertex1,
555
0
                                    &valsVertex2, &valsVertex3]() {
556
0
        if (useForwardDirection) {
557
0
            int nextAttrIdx = 3;
558
559
0
            if (horizontalTransformation_) {
560
                // target_x
561
0
                valsVertex1.push_back(sqlite3_column_double(select_stmt_, 3));
562
                // target_y
563
0
                valsVertex1.push_back(sqlite3_column_double(select_stmt_, 6));
564
565
                // target_x
566
0
                valsVertex2.push_back(sqlite3_column_double(select_stmt_, 4));
567
                // target_y
568
0
                valsVertex2.push_back(sqlite3_column_double(select_stmt_, 7));
569
570
                // target_x
571
0
                valsVertex3.push_back(sqlite3_column_double(select_stmt_, 5));
572
                // target_y
573
0
                valsVertex3.push_back(sqlite3_column_double(select_stmt_, 8));
574
575
0
                nextAttrIdx = 9;
576
0
            }
577
578
0
            if (verticalTransformation_) {
579
0
                if (sourceZ_) {
580
                    // source_z
581
0
                    valsVertex1.push_back(
582
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx));
583
                    // target_z
584
0
                    valsVertex1.push_back(
585
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 3));
586
587
                    // source_z
588
0
                    valsVertex2.push_back(
589
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 1));
590
                    // target_z
591
0
                    valsVertex2.push_back(
592
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 4));
593
594
                    // source_z
595
0
                    valsVertex3.push_back(
596
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 2));
597
                    // target_z
598
0
                    valsVertex3.push_back(
599
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 5));
600
0
                } else {
601
                    // offset_z
602
0
                    valsVertex1.push_back(
603
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx));
604
0
                    valsVertex2.push_back(
605
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 1));
606
0
                    valsVertex3.push_back(
607
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 2));
608
0
                }
609
0
            }
610
0
        } else {
611
0
            int nextAttrIdx = 3;
612
613
0
            if (horizontalTransformation_) {
614
615
0
                const unsigned char *v1_geom =
616
0
                    static_cast<const unsigned char *>(
617
0
                        sqlite3_column_blob(select_stmt_, 0));
618
0
                const unsigned char *v2_geom =
619
0
                    static_cast<const unsigned char *>(
620
0
                        sqlite3_column_blob(select_stmt_, 1));
621
0
                const unsigned char *v3_geom =
622
0
                    static_cast<const unsigned char *>(
623
0
                        sqlite3_column_blob(select_stmt_, 2));
624
0
                constexpr int SIZEOF_GPKG_POINT_PREFIX = 8 + 1 + 4;
625
0
                constexpr int SIZEOF_GPKG_POINT =
626
0
                    SIZEOF_GPKG_POINT_PREFIX +
627
0
                    2 * static_cast<int>(sizeof(double));
628
0
                if (sqlite3_column_bytes(select_stmt_, 0) !=
629
0
                        SIZEOF_GPKG_POINT ||
630
0
                    sqlite3_column_bytes(select_stmt_, 1) !=
631
0
                        SIZEOF_GPKG_POINT ||
632
0
                    sqlite3_column_bytes(select_stmt_, 2) !=
633
0
                        SIZEOF_GPKG_POINT) {
634
0
                    return false;
635
0
                }
636
0
                double vX[3], vY[3];
637
0
                memcpy(&vX[0], v1_geom + SIZEOF_GPKG_POINT_PREFIX,
638
0
                       sizeof(double));
639
0
                memcpy(&vY[0],
640
0
                       v1_geom + SIZEOF_GPKG_POINT_PREFIX + sizeof(double),
641
0
                       sizeof(double));
642
0
                memcpy(&vX[1], v2_geom + SIZEOF_GPKG_POINT_PREFIX,
643
0
                       sizeof(double));
644
0
                memcpy(&vY[1],
645
0
                       v2_geom + SIZEOF_GPKG_POINT_PREFIX + sizeof(double),
646
0
                       sizeof(double));
647
0
                memcpy(&vX[2], v3_geom + SIZEOF_GPKG_POINT_PREFIX,
648
0
                       sizeof(double));
649
0
                memcpy(&vY[2],
650
0
                       v3_geom + SIZEOF_GPKG_POINT_PREFIX + sizeof(double),
651
0
                       sizeof(double));
652
653
0
                if (!IS_LSB) {
654
0
                    swap_words(vX, sizeof(double), 3);
655
0
                    swap_words(vY, sizeof(double), 3);
656
0
                }
657
658
                // source_x
659
0
                valsVertex1.push_back(vX[0]);
660
                // source_y
661
0
                valsVertex1.push_back(vY[0]);
662
663
                // source_x
664
0
                valsVertex2.push_back(vX[1]);
665
                // source_y
666
0
                valsVertex2.push_back(vY[1]);
667
668
                // source_x
669
0
                valsVertex3.push_back(vX[2]);
670
                // source_y
671
0
                valsVertex3.push_back(vY[2]);
672
673
0
                nextAttrIdx = 9;
674
0
            }
675
676
0
            if (verticalTransformation_) {
677
0
                if (sourceZ_) {
678
                    // source_z
679
0
                    valsVertex1.push_back(
680
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx));
681
                    // target_z
682
0
                    valsVertex1.push_back(
683
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 3));
684
685
                    // source_z
686
0
                    valsVertex2.push_back(
687
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 1));
688
                    // target_z
689
0
                    valsVertex2.push_back(
690
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 4));
691
692
                    // source_z
693
0
                    valsVertex3.push_back(
694
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 2));
695
                    // target_z
696
0
                    valsVertex3.push_back(
697
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 5));
698
0
                } else {
699
                    // offset_z
700
0
                    valsVertex1.push_back(
701
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx));
702
0
                    valsVertex2.push_back(
703
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 1));
704
0
                    valsVertex3.push_back(
705
0
                        sqlite3_column_double(select_stmt_, nextAttrIdx + 2));
706
0
                }
707
0
            }
708
0
        }
709
0
        return true;
710
0
    };
711
712
    // Try to use last triangle
713
0
    if (cachedForwardDirection_ == forwardDirection &&
714
0
        !cachedValsVertex1_.empty() &&
715
0
        computeLambdas(cachedVerticesX_, cachedVerticesY_,
716
0
                       /* checkWithinTriangle = */ true)) {
717
0
        valsVertex1 = cachedValsVertex1_;
718
0
        valsVertex2 = cachedValsVertex2_;
719
0
        valsVertex3 = cachedValsVertex3_;
720
0
        return true;
721
0
    }
722
723
0
    constexpr double EPSILON = 1e-10;
724
725
0
    sqlite3_reset(select_stmt_);
726
0
    if (useForwardDirection) {
727
0
        sqlite3_bind_double(select_stmt_, 1, x - EPSILON);
728
0
        sqlite3_bind_double(select_stmt_, 2, x + EPSILON);
729
0
        sqlite3_bind_double(select_stmt_, 3, y - EPSILON);
730
0
        sqlite3_bind_double(select_stmt_, 4, y + EPSILON);
731
0
    } else {
732
        // Take into account the shift between source and target horizontal
733
        // coordinates when searching in the reverse direction.
734
0
        sqlite3_bind_double(select_stmt_, 1, x - max_shift_x_ - EPSILON);
735
0
        sqlite3_bind_double(select_stmt_, 2, x - min_shift_x_ + EPSILON);
736
0
        sqlite3_bind_double(select_stmt_, 3, y - max_shift_y_ - EPSILON);
737
0
        sqlite3_bind_double(select_stmt_, 4, y - min_shift_y_ + EPSILON);
738
0
    }
739
740
    // Iterate over triangles whose bounding box intersecs the point of interest
741
0
    while (sqlite3_step(select_stmt_) == SQLITE_ROW) {
742
0
        std::array<double, 3> vX, vY;
743
0
        if (!getXY(vX, vY))
744
0
            return false;
745
746
0
        if (computeLambdas(vX, vY, /* checkWithinTriangle = */ true)) {
747
0
            const bool status = collectValsVertex();
748
0
            if (status) {
749
0
                cachedForwardDirection_ = forwardDirection;
750
0
                cachedVerticesX_ = vX;
751
0
                cachedVerticesY_ = vY;
752
0
                cachedValsVertex1_ = valsVertex1;
753
0
                cachedValsVertex2_ = valsVertex2;
754
0
                cachedValsVertex3_ = valsVertex3;
755
0
            }
756
0
            return status;
757
0
        }
758
0
    }
759
760
0
    if (fallbackStrategy_ == FALLBACK_NONE) {
761
0
        return false;
762
0
    }
763
764
0
    const double x_search = std::clamp(
765
0
        x + (useForwardDirection ? 0 : -(min_shift_x_ + max_shift_x_) * 0.5),
766
0
        min_x_, max_x_);
767
0
    const double y_search = std::clamp(
768
0
        y + (useForwardDirection ? 0 : -(min_shift_y_ + max_shift_y_) * 0.5),
769
0
        min_y_, max_y_);
770
0
    double closest_sq_dist = std::numeric_limits<double>::infinity();
771
0
    double closest_dist = std::numeric_limits<double>::infinity();
772
0
    std::array<double, 3> closest_vX{0, 0, 0};
773
0
    std::array<double, 3> closest_vY{0, 0, 0};
774
775
    // Initiate the search radius from the density of points
776
    // and double it if there is no match.
777
0
    const double bbox_w = max_x_ - min_x_;
778
0
    const double bbox_h = max_y_ - min_y_;
779
0
    double radius = sqrt(bbox_w * bbox_h / numVertices_);
780
0
    constexpr int MAX_ITERS = 20;
781
0
    for (int iter = 0; iter <= MAX_ITERS && radius <= std::max(bbox_w, bbox_h);
782
0
         ++iter) {
783
0
        sqlite3_reset(select_stmt_);
784
0
        sqlite3_bind_double(select_stmt_, 1, x_search - radius);
785
0
        sqlite3_bind_double(select_stmt_, 2, x_search + radius);
786
0
        sqlite3_bind_double(select_stmt_, 3, y_search - radius);
787
0
        sqlite3_bind_double(select_stmt_, 4, y_search + radius);
788
789
        // Iterate over triangles in the current area of search
790
0
        while (sqlite3_step(select_stmt_) == SQLITE_ROW) {
791
0
            std::array<double, 3> vX, vY;
792
0
            if (!getXY(vX, vY))
793
0
                return false;
794
795
            // don't check this triangle if the query point plusminus the
796
            // currently closest found distance is outside the triangle's
797
            // bounding box
798
0
            if (x + closest_dist < std::min(vX[0], std::min(vX[1], vX[2])) ||
799
0
                x - closest_dist > std::max(vX[0], std::max(vX[1], vX[2])) ||
800
0
                y + closest_dist < std::min(vY[0], std::min(vY[1], vY[2])) ||
801
0
                y - closest_dist > std::max(vY[0], std::min(vY[1], vY[2]))) {
802
0
                continue;
803
0
            }
804
0
            const double dist12 = squared_distance(vX[0], vY[0], vX[1], vY[1]);
805
0
            const double dist23 = squared_distance(vX[1], vY[1], vX[2], vY[2]);
806
0
            const double dist13 = squared_distance(vX[0], vY[0], vX[2], vY[2]);
807
0
            if (dist12 < EPSILON || dist23 < EPSILON || dist13 < EPSILON) {
808
                // do not use degenerate triangles
809
0
                continue;
810
0
            }
811
812
0
            bool isClosest = false;
813
0
            if (fallbackStrategy_ == FALLBACK_NEAREST_SIDE) {
814
                // we don't know whether the points of the triangle are given
815
                // clockwise or counter-clockwise, so we have to check the
816
                // distance of the point to all three sides of the triangle
817
0
                double sq_dist = sq_distance_point_segment(
818
0
                    x, y, vX[0], vY[0], vX[1], vY[1], dist12);
819
0
                if (sq_dist < closest_sq_dist) {
820
0
                    closest_sq_dist = sq_dist;
821
0
                    isClosest = true;
822
0
                }
823
0
                sq_dist = sq_distance_point_segment(x, y, vX[1], vY[1], vX[2],
824
0
                                                    vY[2], dist23);
825
0
                if (sq_dist < closest_sq_dist) {
826
0
                    closest_sq_dist = sq_dist;
827
0
                    isClosest = true;
828
0
                }
829
0
                sq_dist = sq_distance_point_segment(x, y, vX[0], vY[0], vX[2],
830
0
                                                    vY[2], dist13);
831
0
                if (sq_dist < closest_sq_dist) {
832
0
                    closest_sq_dist = sq_dist;
833
0
                    isClosest = true;
834
0
                }
835
0
            } else {
836
0
                assert(fallbackStrategy_ == FALLBACK_NEAREST_CENTROID);
837
0
                const double c_x = (vX[0] + vX[1] + vX[2]) / 3.0;
838
0
                const double c_y = (vY[0] + vY[1] + vY[2]) / 3.0;
839
0
                const double sq_dist = squared_distance(x, y, c_x, c_y);
840
0
                if (sq_dist < closest_sq_dist) {
841
0
                    closest_sq_dist = sq_dist;
842
0
                    isClosest = true;
843
0
                }
844
0
            }
845
0
            if (isClosest) {
846
0
                closest_dist = sqrt(closest_sq_dist);
847
0
                closest_vX = vX;
848
0
                closest_vY = vY;
849
0
                valsVertex1.clear();
850
0
                valsVertex2.clear();
851
0
                valsVertex3.clear();
852
0
                if (!collectValsVertex())
853
0
                    return false;
854
0
            }
855
0
        }
856
857
0
        if (std::isinf(closest_sq_dist)) {
858
            // No match: increase search radius
859
0
            radius *= 2;
860
0
        } else {
861
0
            break;
862
0
        }
863
0
    }
864
0
    if (std::isinf(closest_sq_dist)) {
865
        // nothing was found due to empty triangle list or only degenerate
866
        // triangles
867
0
        return false;
868
0
    }
869
870
0
    return computeLambdas(closest_vX, closest_vY,
871
0
                          /* checkWithinTriangle = */ false);
872
0
}
873
874
// ---------------------------------------------------------------------------
875
876
TINShiftGeopackageEvaluator::TINShiftGeopackageEvaluator(
877
    std::unique_ptr<TINShiftGeopackageFile> fileIn)
878
0
    : file_(std::move(fileIn)) {}
879
880
// ---------------------------------------------------------------------------
881
882
bool TINShiftGeopackageEvaluator::transform(bool forwardDirection, double x,
883
                                            double y, double z, double &x_out,
884
0
                                            double &y_out, double &z_out) {
885
0
    double lambda1 = 0;
886
0
    double lambda2 = 0;
887
0
    double lambda3 = 0;
888
0
    std::vector<double> valsVertex1;
889
0
    std::vector<double> valsVertex2;
890
0
    std::vector<double> valsVertex3;
891
0
    if (!file_->findTriangle(x, y, forwardDirection, lambda1, lambda2, lambda3,
892
0
                             valsVertex1, valsVertex2, valsVertex3))
893
0
        return false;
894
895
0
    x_out = x;
896
0
    y_out = y;
897
0
    z_out = z;
898
899
0
    if (file_->hasHorizontalTransformation()) {
900
0
        x_out = lambda1 * valsVertex1[0] + lambda2 * valsVertex2[0] +
901
0
                lambda3 * valsVertex3[0];
902
0
        y_out = lambda1 * valsVertex1[1] + lambda2 * valsVertex2[1] +
903
0
                lambda3 * valsVertex3[1];
904
0
    }
905
0
    if (file_->hasVerticalTransformation()) {
906
0
        const int nextAttrIdx = file_->hasHorizontalTransformation() ? 2 : 0;
907
0
        const double sign = forwardDirection ? 1 : -1;
908
0
        if (file_->hasOffsetZ()) {
909
0
            z_out += sign * (lambda1 * valsVertex1[nextAttrIdx] +
910
0
                             lambda2 * valsVertex2[nextAttrIdx] +
911
0
                             lambda3 * valsVertex3[nextAttrIdx]);
912
0
        } else {
913
0
            const double offset1 =
914
0
                valsVertex1[nextAttrIdx + 1] - valsVertex1[nextAttrIdx];
915
0
            const double offset2 =
916
0
                valsVertex2[nextAttrIdx + 1] - valsVertex2[nextAttrIdx];
917
0
            const double offset3 =
918
0
                valsVertex3[nextAttrIdx + 1] - valsVertex3[nextAttrIdx];
919
0
            z_out += sign * (lambda1 * offset1 + lambda2 * offset2 +
920
0
                             lambda3 * offset3);
921
0
        }
922
0
    }
923
924
0
    return true;
925
0
}
926
927
// ---------------------------------------------------------------------------
928
929
bool TINShiftGeopackageEvaluator::forward(double x, double y, double z,
930
                                          double &x_out, double &y_out,
931
0
                                          double &z_out) {
932
0
    return transform(/* forwardDirection = */ true, x, y, z, x_out, y_out,
933
0
                     z_out);
934
0
}
935
936
// ---------------------------------------------------------------------------
937
938
bool TINShiftGeopackageEvaluator::inverse(double x, double y, double z,
939
                                          double &x_out, double &y_out,
940
0
                                          double &z_out) {
941
0
    return transform(/* forwardDirection = */ false, x, y, z, x_out, y_out,
942
0
                     z_out);
943
0
}
944
945
// ---------------------------------------------------------------------------