Coverage Report

Created: 2026-05-16 07:15

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/PROJ/src/transformations/tinshift_impl.hpp
Line
Count
Source
1
/******************************************************************************
2
 * Project:  PROJ
3
 * Purpose:  Functionality related to TIN based transformations
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 TINSHIFT_NAMESPACE
29
#error "Should be included only by tinshift.hpp"
30
#endif
31
32
#include <algorithm>
33
#include <cmath>
34
#include <limits>
35
36
namespace TINSHIFT_NAMESPACE {
37
38
// ---------------------------------------------------------------------------
39
40
0
static std::string getString(const json &j, const char *key, bool optional) {
41
0
    if (!j.contains(key)) {
42
0
        if (optional) {
43
0
            return std::string();
44
0
        }
45
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
46
0
    }
47
0
    const json v = j[key];
48
0
    if (!v.is_string()) {
49
0
        throw ParsingException(std::string("The value of \"") + key +
50
0
                               "\" should be a string");
51
0
    }
52
0
    return v.get<std::string>();
53
0
}
54
55
0
static std::string getReqString(const json &j, const char *key) {
56
0
    return getString(j, key, false);
57
0
}
58
59
0
static std::string getOptString(const json &j, const char *key) {
60
0
    return getString(j, key, true);
61
0
}
62
63
// ---------------------------------------------------------------------------
64
65
0
static json getArrayMember(const json &j, const char *key) {
66
0
    if (!j.contains(key)) {
67
0
        throw ParsingException(std::string("Missing \"") + key + "\" key");
68
0
    }
69
0
    const json obj = j[key];
70
0
    if (!obj.is_array()) {
71
0
        throw ParsingException(std::string("The value of \"") + key +
72
0
                               "\" should be a array");
73
0
    }
74
0
    return obj;
75
0
}
76
77
// ---------------------------------------------------------------------------
78
79
0
std::unique_ptr<TINShiftFile> TINShiftFile::parse(const std::string &text) {
80
0
    std::unique_ptr<TINShiftFile> tinshiftFile(new TINShiftFile());
81
0
    json j;
82
0
    try {
83
0
        j = json::parse(text, [](int depth, json::parse_event_t, json &) {
84
0
            if (depth >= 128)
85
0
                throw ParsingException("Too deep nesting in JSON content");
86
0
            return true;
87
0
        });
88
0
    } catch (const std::exception &e) {
89
0
        throw ParsingException(e.what());
90
0
    }
91
0
    if (!j.is_object()) {
92
0
        throw ParsingException("Not an object");
93
0
    }
94
0
    tinshiftFile->mFileType = getReqString(j, "file_type");
95
0
    tinshiftFile->mFormatVersion = getReqString(j, "format_version");
96
0
    tinshiftFile->mName = getOptString(j, "name");
97
0
    tinshiftFile->mVersion = getOptString(j, "version");
98
0
    tinshiftFile->mLicense = getOptString(j, "license");
99
0
    tinshiftFile->mDescription = getOptString(j, "description");
100
0
    tinshiftFile->mPublicationDate = getOptString(j, "publication_date");
101
102
0
    tinshiftFile->mFallbackStrategy = FALLBACK_NONE;
103
0
    if (j.contains("fallback_strategy")) {
104
0
        if (tinshiftFile->mFormatVersion != "1.1") {
105
0
            throw ParsingException(
106
0
                "fallback_strategy needs format_version 1.1");
107
0
        }
108
0
        const auto fallback_strategy = getOptString(j, "fallback_strategy");
109
0
        if (fallback_strategy == "nearest_side") {
110
0
            tinshiftFile->mFallbackStrategy = FALLBACK_NEAREST_SIDE;
111
0
        } else if (fallback_strategy == "nearest_centroid") {
112
0
            tinshiftFile->mFallbackStrategy = FALLBACK_NEAREST_CENTROID;
113
0
        } else if (fallback_strategy == "none") {
114
0
            tinshiftFile->mFallbackStrategy = FALLBACK_NONE;
115
0
        } else {
116
0
            throw ParsingException("invalid fallback_strategy");
117
0
        }
118
0
    }
119
120
0
    if (j.contains("authority")) {
121
0
        const json jAuthority = j["authority"];
122
0
        if (!jAuthority.is_object()) {
123
0
            throw ParsingException("authority is not a object");
124
0
        }
125
0
        tinshiftFile->mAuthority.name = getOptString(jAuthority, "name");
126
0
        tinshiftFile->mAuthority.url = getOptString(jAuthority, "url");
127
0
        tinshiftFile->mAuthority.address = getOptString(jAuthority, "address");
128
0
        tinshiftFile->mAuthority.email = getOptString(jAuthority, "email");
129
0
    }
130
131
0
    if (j.contains("links")) {
132
0
        const json jLinks = j["links"];
133
0
        if (!jLinks.is_array()) {
134
0
            throw ParsingException("links is not an array");
135
0
        }
136
0
        for (const json &jLink : jLinks) {
137
0
            if (!jLink.is_object()) {
138
0
                throw ParsingException("links[] item is not an object");
139
0
            }
140
0
            Link link;
141
0
            link.href = getOptString(jLink, "href");
142
0
            link.rel = getOptString(jLink, "rel");
143
0
            link.type = getOptString(jLink, "type");
144
0
            link.title = getOptString(jLink, "title");
145
0
            tinshiftFile->mLinks.emplace_back(std::move(link));
146
0
        }
147
0
    }
148
0
    tinshiftFile->mInputCRS = getOptString(j, "input_crs");
149
0
    tinshiftFile->mOutputCRS = getOptString(j, "output_crs");
150
151
0
    const auto jTransformedComponents =
152
0
        getArrayMember(j, "transformed_components");
153
0
    for (const json &jComp : jTransformedComponents) {
154
0
        if (!jComp.is_string()) {
155
0
            throw ParsingException(
156
0
                "transformed_components[] item is not a string");
157
0
        }
158
0
        const auto jCompStr = jComp.get<std::string>();
159
0
        if (jCompStr == "horizontal") {
160
0
            tinshiftFile->mTransformHorizontalComponent = true;
161
0
        } else if (jCompStr == "vertical") {
162
0
            tinshiftFile->mTransformVerticalComponent = true;
163
0
        } else {
164
0
            throw ParsingException("transformed_components[] = " + jCompStr +
165
0
                                   " is not handled");
166
0
        }
167
0
    }
168
169
0
    const auto jVerticesColumns = getArrayMember(j, "vertices_columns");
170
0
    int sourceXCol = -1;
171
0
    int sourceYCol = -1;
172
0
    int sourceZCol = -1;
173
0
    int targetXCol = -1;
174
0
    int targetYCol = -1;
175
0
    int targetZCol = -1;
176
0
    int offsetZCol = -1;
177
0
    for (size_t i = 0; i < jVerticesColumns.size(); ++i) {
178
0
        const json &jColumn = jVerticesColumns[i];
179
0
        if (!jColumn.is_string()) {
180
0
            throw ParsingException("vertices_columns[] item is not a string");
181
0
        }
182
0
        const auto jColumnStr = jColumn.get<std::string>();
183
0
        if (jColumnStr == "source_x") {
184
0
            sourceXCol = static_cast<int>(i);
185
0
        } else if (jColumnStr == "source_y") {
186
0
            sourceYCol = static_cast<int>(i);
187
0
        } else if (jColumnStr == "source_z") {
188
0
            sourceZCol = static_cast<int>(i);
189
0
        } else if (jColumnStr == "target_x") {
190
0
            targetXCol = static_cast<int>(i);
191
0
        } else if (jColumnStr == "target_y") {
192
0
            targetYCol = static_cast<int>(i);
193
0
        } else if (jColumnStr == "target_z") {
194
0
            targetZCol = static_cast<int>(i);
195
0
        } else if (jColumnStr == "offset_z") {
196
0
            offsetZCol = static_cast<int>(i);
197
0
        }
198
0
    }
199
0
    if (sourceXCol < 0) {
200
0
        throw ParsingException(
201
0
            "source_x must be specified in vertices_columns[]");
202
0
    }
203
0
    if (sourceYCol < 0) {
204
0
        throw ParsingException(
205
0
            "source_y must be specified in vertices_columns[]");
206
0
    }
207
0
    if (tinshiftFile->mTransformHorizontalComponent) {
208
0
        if (targetXCol < 0) {
209
0
            throw ParsingException(
210
0
                "target_x must be specified in vertices_columns[]");
211
0
        }
212
0
        if (targetYCol < 0) {
213
0
            throw ParsingException(
214
0
                "target_y must be specified in vertices_columns[]");
215
0
        }
216
0
    }
217
0
    if (tinshiftFile->mTransformVerticalComponent) {
218
0
        if (offsetZCol >= 0) {
219
            // do nothing
220
0
        } else {
221
0
            if (sourceZCol < 0) {
222
0
                throw ParsingException("source_z or delta_z must be specified "
223
0
                                       "in vertices_columns[]");
224
0
            }
225
0
            if (targetZCol < 0) {
226
0
                throw ParsingException(
227
0
                    "target_z must be specified in vertices_columns[]");
228
0
            }
229
0
        }
230
0
    }
231
232
0
    const auto jTrianglesColumns = getArrayMember(j, "triangles_columns");
233
0
    int idxVertex1Col = -1;
234
0
    int idxVertex2Col = -1;
235
0
    int idxVertex3Col = -1;
236
0
    for (size_t i = 0; i < jTrianglesColumns.size(); ++i) {
237
0
        const json &jColumn = jTrianglesColumns[i];
238
0
        if (!jColumn.is_string()) {
239
0
            throw ParsingException("triangles_columns[] item is not a string");
240
0
        }
241
0
        const auto jColumnStr = jColumn.get<std::string>();
242
0
        if (jColumnStr == "idx_vertex1") {
243
0
            idxVertex1Col = static_cast<int>(i);
244
0
        } else if (jColumnStr == "idx_vertex2") {
245
0
            idxVertex2Col = static_cast<int>(i);
246
0
        } else if (jColumnStr == "idx_vertex3") {
247
0
            idxVertex3Col = static_cast<int>(i);
248
0
        }
249
0
    }
250
0
    if (idxVertex1Col < 0) {
251
0
        throw ParsingException(
252
0
            "idx_vertex1 must be specified in triangles_columns[]");
253
0
    }
254
0
    if (idxVertex2Col < 0) {
255
0
        throw ParsingException(
256
0
            "idx_vertex2 must be specified in triangles_columns[]");
257
0
    }
258
0
    if (idxVertex3Col < 0) {
259
0
        throw ParsingException(
260
0
            "idx_vertex3 must be specified in triangles_columns[]");
261
0
    }
262
263
0
    const auto jVertices = getArrayMember(j, "vertices");
264
0
    tinshiftFile->mVerticesColumnCount = 2;
265
0
    if (tinshiftFile->mTransformHorizontalComponent)
266
0
        tinshiftFile->mVerticesColumnCount += 2;
267
0
    if (tinshiftFile->mTransformVerticalComponent)
268
0
        tinshiftFile->mVerticesColumnCount += 1;
269
270
0
    tinshiftFile->mVertices.reserve(tinshiftFile->mVerticesColumnCount *
271
0
                                    jVertices.size());
272
0
    for (const auto &jVertex : jVertices) {
273
0
        if (!jVertex.is_array()) {
274
0
            throw ParsingException("vertices[] item is not an array");
275
0
        }
276
0
        if (jVertex.size() != jVerticesColumns.size()) {
277
0
            throw ParsingException(
278
0
                "vertices[] item has not expected number of elements");
279
0
        }
280
0
        if (!jVertex[sourceXCol].is_number()) {
281
0
            throw ParsingException("vertices[][] item is not a number");
282
0
        }
283
0
        tinshiftFile->mVertices.push_back(jVertex[sourceXCol].get<double>());
284
0
        if (!jVertex[sourceYCol].is_number()) {
285
0
            throw ParsingException("vertices[][] item is not a number");
286
0
        }
287
0
        tinshiftFile->mVertices.push_back(jVertex[sourceYCol].get<double>());
288
0
        if (tinshiftFile->mTransformHorizontalComponent) {
289
0
            if (!jVertex[targetXCol].is_number()) {
290
0
                throw ParsingException("vertices[][] item is not a number");
291
0
            }
292
0
            tinshiftFile->mVertices.push_back(
293
0
                jVertex[targetXCol].get<double>());
294
0
            if (!jVertex[targetYCol].is_number()) {
295
0
                throw ParsingException("vertices[][] item is not a number");
296
0
            }
297
0
            tinshiftFile->mVertices.push_back(
298
0
                jVertex[targetYCol].get<double>());
299
0
        }
300
0
        if (tinshiftFile->mTransformVerticalComponent) {
301
0
            if (offsetZCol >= 0) {
302
0
                if (!jVertex[offsetZCol].is_number()) {
303
0
                    throw ParsingException("vertices[][] item is not a number");
304
0
                }
305
0
                tinshiftFile->mVertices.push_back(
306
0
                    jVertex[offsetZCol].get<double>());
307
0
            } else {
308
0
                if (!jVertex[sourceZCol].is_number()) {
309
0
                    throw ParsingException("vertices[][] item is not a number");
310
0
                }
311
0
                const double sourceZ = jVertex[sourceZCol].get<double>();
312
0
                if (!jVertex[targetZCol].is_number()) {
313
0
                    throw ParsingException("vertices[][] item is not a number");
314
0
                }
315
0
                const double targetZ = jVertex[targetZCol].get<double>();
316
0
                tinshiftFile->mVertices.push_back(targetZ - sourceZ);
317
0
            }
318
0
        }
319
0
    }
320
321
0
    const auto jTriangles = getArrayMember(j, "triangles");
322
0
    tinshiftFile->mTriangles.reserve(jTriangles.size());
323
0
    for (const auto &jTriangle : jTriangles) {
324
0
        if (!jTriangle.is_array()) {
325
0
            throw ParsingException("triangles[] item is not an array");
326
0
        }
327
0
        if (jTriangle.size() != jTrianglesColumns.size()) {
328
0
            throw ParsingException(
329
0
                "triangles[] item has not expected number of elements");
330
0
        }
331
332
0
        if (jTriangle[idxVertex1Col].type() != json::value_t::number_unsigned) {
333
0
            throw ParsingException("triangles[][] item is not an integer");
334
0
        }
335
0
        const unsigned vertex1 = jTriangle[idxVertex1Col].get<unsigned>();
336
0
        if (vertex1 >= jVertices.size()) {
337
0
            throw ParsingException("Invalid value for a vertex index");
338
0
        }
339
340
0
        if (jTriangle[idxVertex2Col].type() != json::value_t::number_unsigned) {
341
0
            throw ParsingException("triangles[][] item is not an integer");
342
0
        }
343
0
        const unsigned vertex2 = jTriangle[idxVertex2Col].get<unsigned>();
344
0
        if (vertex2 >= jVertices.size()) {
345
0
            throw ParsingException("Invalid value for a vertex index");
346
0
        }
347
348
0
        if (jTriangle[idxVertex3Col].type() != json::value_t::number_unsigned) {
349
0
            throw ParsingException("triangles[][] item is not an integer");
350
0
        }
351
0
        const unsigned vertex3 = jTriangle[idxVertex3Col].get<unsigned>();
352
0
        if (vertex3 >= jVertices.size()) {
353
0
            throw ParsingException("Invalid value for a vertex index");
354
0
        }
355
356
0
        VertexIndices vi;
357
0
        vi.idx1 = vertex1;
358
0
        vi.idx2 = vertex2;
359
0
        vi.idx3 = vertex3;
360
0
        tinshiftFile->mTriangles.push_back(vi);
361
0
    }
362
363
0
    return tinshiftFile;
364
0
}
365
366
// ---------------------------------------------------------------------------
367
368
static NS_PROJ::QuadTree::RectObj GetBounds(const TINShiftFile &file,
369
0
                                            bool forward) {
370
0
    NS_PROJ::QuadTree::RectObj rect;
371
0
    rect.minx = std::numeric_limits<double>::max();
372
0
    rect.miny = std::numeric_limits<double>::max();
373
0
    rect.maxx = -std::numeric_limits<double>::max();
374
0
    rect.maxy = -std::numeric_limits<double>::max();
375
0
    const auto &vertices = file.vertices();
376
0
    const unsigned colCount = file.verticesColumnCount();
377
0
    const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0;
378
0
    const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1;
379
0
    for (size_t i = 0; i + colCount - 1 < vertices.size(); i += colCount) {
380
0
        const double x = vertices[i + idxX];
381
0
        const double y = vertices[i + idxY];
382
0
        rect.minx = std::min(rect.minx, x);
383
0
        rect.miny = std::min(rect.miny, y);
384
0
        rect.maxx = std::max(rect.maxx, x);
385
0
        rect.maxy = std::max(rect.maxy, y);
386
0
    }
387
0
    return rect;
388
0
}
389
390
// ---------------------------------------------------------------------------
391
392
static std::unique_ptr<NS_PROJ::QuadTree::QuadTree<unsigned>>
393
0
BuildQuadTree(const TINShiftFile &file, bool forward) {
394
0
    auto quadtree = std::unique_ptr<NS_PROJ::QuadTree::QuadTree<unsigned>>(
395
0
        new NS_PROJ::QuadTree::QuadTree<unsigned>(GetBounds(file, forward)));
396
0
    const auto &triangles = file.triangles();
397
0
    const auto &vertices = file.vertices();
398
0
    const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0;
399
0
    const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1;
400
0
    const unsigned colCount = file.verticesColumnCount();
401
0
    for (size_t i = 0; i < triangles.size(); ++i) {
402
0
        const unsigned i1 = triangles[i].idx1;
403
0
        const unsigned i2 = triangles[i].idx2;
404
0
        const unsigned i3 = triangles[i].idx3;
405
0
        const double x1 = vertices[i1 * colCount + idxX];
406
0
        const double y1 = vertices[i1 * colCount + idxY];
407
0
        const double x2 = vertices[i2 * colCount + idxX];
408
0
        const double y2 = vertices[i2 * colCount + idxY];
409
0
        const double x3 = vertices[i3 * colCount + idxX];
410
0
        const double y3 = vertices[i3 * colCount + idxY];
411
0
        NS_PROJ::QuadTree::RectObj rect;
412
0
        rect.minx = x1;
413
0
        rect.miny = y1;
414
0
        rect.maxx = x1;
415
0
        rect.maxy = y1;
416
0
        rect.minx = std::min(rect.minx, x2);
417
0
        rect.miny = std::min(rect.miny, y2);
418
0
        rect.maxx = std::max(rect.maxx, x2);
419
0
        rect.maxy = std::max(rect.maxy, y2);
420
0
        rect.minx = std::min(rect.minx, x3);
421
0
        rect.miny = std::min(rect.miny, y3);
422
0
        rect.maxx = std::max(rect.maxx, x3);
423
0
        rect.maxy = std::max(rect.maxy, y3);
424
0
        quadtree->insert(static_cast<unsigned>(i), rect);
425
0
    }
426
427
0
    return quadtree;
428
0
}
429
430
// ---------------------------------------------------------------------------
431
432
Evaluator::Evaluator(std::unique_ptr<TINShiftFile> &&fileIn)
433
0
    : mFile(std::move(fileIn)) {}
434
435
// ---------------------------------------------------------------------------
436
437
0
static inline double sqr(double x) { return x * x; }
438
static inline double squared_distance(double x1, double y1, double x2,
439
0
                                      double y2) {
440
0
    return sqr(x1 - x2) + sqr(y1 - y2);
441
0
}
442
static double distance_point_segment(double x, double y, double x1, double y1,
443
0
                                     double x2, double y2, double dist12) {
444
    // squared distance of point x/y to line segment x1/y1 -- x2/y2
445
0
    double t = ((x - x1) * (x2 - x1) + (y - y1) * (y2 - y1)) / dist12;
446
0
    if (t <= 0.0) {
447
        // closest to x1/y1
448
0
        return squared_distance(x, y, x1, y1);
449
0
    }
450
0
    if (t >= 1.0) {
451
        // closest to y2/y2
452
0
        return squared_distance(x, y, x2, y2);
453
0
    }
454
455
    // closest to line segment x1/y1 -- x2/y2
456
0
    return squared_distance(x, y, x1 + t * (x2 - x1), y1 + t * (y2 - y1));
457
0
}
458
459
static const TINShiftFile::VertexIndices *
460
FindTriangle(const TINShiftFile &file,
461
             const NS_PROJ::QuadTree::QuadTree<unsigned> &quadtree,
462
             std::vector<unsigned> &triangleIndices, double x, double y,
463
0
             bool forward, double &lambda1, double &lambda2, double &lambda3) {
464
0
#define USE_QUADTREE
465
0
#ifdef USE_QUADTREE
466
0
    triangleIndices.clear();
467
0
    quadtree.search(x, y, triangleIndices);
468
0
#endif
469
0
    const auto &triangles = file.triangles();
470
0
    const auto &vertices = file.vertices();
471
0
    constexpr double EPS = 1e-10;
472
0
    const int idxX = file.transformHorizontalComponent() && !forward ? 2 : 0;
473
0
    const int idxY = file.transformHorizontalComponent() && !forward ? 3 : 1;
474
0
    const unsigned colCount = file.verticesColumnCount();
475
0
#ifdef USE_QUADTREE
476
0
    for (unsigned i : triangleIndices)
477
#else
478
    for (size_t i = 0; i < triangles.size(); ++i)
479
#endif
480
0
    {
481
0
        const auto &triangle = triangles[i];
482
0
        const unsigned i1 = triangle.idx1;
483
0
        const unsigned i2 = triangle.idx2;
484
0
        const unsigned i3 = triangle.idx3;
485
0
        const double x1 = vertices[i1 * colCount + idxX];
486
0
        const double y1 = vertices[i1 * colCount + idxY];
487
0
        const double x2 = vertices[i2 * colCount + idxX];
488
0
        const double y2 = vertices[i2 * colCount + idxY];
489
0
        const double x3 = vertices[i3 * colCount + idxX];
490
0
        const double y3 = vertices[i3 * colCount + idxY];
491
0
        const double det_T = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
492
0
        lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det_T;
493
0
        lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det_T;
494
0
        if (lambda1 >= -EPS && lambda1 <= 1 + EPS && lambda2 >= -EPS &&
495
0
            lambda2 <= 1 + EPS) {
496
0
            lambda3 = 1 - lambda1 - lambda2;
497
0
            if (lambda3 >= 0) {
498
0
                return &triangle;
499
0
            }
500
0
        }
501
0
    }
502
0
    if (file.fallbackStrategy() == FALLBACK_NONE) {
503
0
        return nullptr;
504
0
    }
505
    // find triangle with the shortest squared distance
506
    //
507
    // TODO: extend quadtree to support nearest neighbor search
508
0
    double closest_dist = std::numeric_limits<double>::infinity();
509
0
    double closest_dist2 = std::numeric_limits<double>::infinity();
510
0
    size_t closest_i = 0;
511
0
    for (size_t i = 0; i < triangles.size(); ++i) {
512
0
        const auto &triangle = triangles[i];
513
0
        const unsigned i1 = triangle.idx1;
514
0
        const unsigned i2 = triangle.idx2;
515
0
        const unsigned i3 = triangle.idx3;
516
0
        const double x1 = vertices[i1 * colCount + idxX];
517
0
        const double y1 = vertices[i1 * colCount + idxY];
518
0
        const double x2 = vertices[i2 * colCount + idxX];
519
0
        const double y2 = vertices[i2 * colCount + idxY];
520
0
        const double x3 = vertices[i3 * colCount + idxX];
521
0
        const double y3 = vertices[i3 * colCount + idxY];
522
523
        // don't check this triangle if the query point plusminus the
524
        // currently closest found distance is outside the triangle's AABB
525
0
        if (x + closest_dist < std::min(x1, std::min(x2, x3)) ||
526
0
            x - closest_dist > std::max(x1, std::max(x2, x3)) ||
527
0
            y + closest_dist < std::min(y1, std::min(y2, y3)) ||
528
0
            y - closest_dist > std::max(y1, std::max(y2, y3))) {
529
0
            continue;
530
0
        }
531
532
0
        double dist12 = squared_distance(x1, y1, x2, y2);
533
0
        double dist23 = squared_distance(x2, y2, x3, y3);
534
0
        double dist13 = squared_distance(x1, y1, x3, y3);
535
0
        if (dist12 < EPS || dist23 < EPS || dist13 < EPS) {
536
            // do not use degenerate triangles
537
0
            continue;
538
0
        }
539
0
        double dist2;
540
0
        if (file.fallbackStrategy() == FALLBACK_NEAREST_SIDE) {
541
            // we don't know whether the points of the triangle are given
542
            // clockwise or counter-clockwise, so we have to check the distance
543
            // of the point to all three sides of the triangle
544
0
            dist2 = distance_point_segment(x, y, x1, y1, x2, y2, dist12);
545
0
            if (dist2 < closest_dist2) {
546
0
                closest_dist2 = dist2;
547
0
                closest_dist = sqrt(dist2);
548
0
                closest_i = i;
549
0
            }
550
0
            dist2 = distance_point_segment(x, y, x2, y2, x3, y3, dist23);
551
0
            if (dist2 < closest_dist2) {
552
0
                closest_dist2 = dist2;
553
0
                closest_dist = sqrt(dist2);
554
0
                closest_i = i;
555
0
            }
556
0
            dist2 = distance_point_segment(x, y, x1, y1, x3, y3, dist13);
557
0
            if (dist2 < closest_dist2) {
558
0
                closest_dist2 = dist2;
559
0
                closest_dist = sqrt(dist2);
560
0
                closest_i = i;
561
0
            }
562
0
        } else if (file.fallbackStrategy() == FALLBACK_NEAREST_CENTROID) {
563
0
            double c_x = (x1 + x2 + x3) / 3.0;
564
0
            double c_y = (y1 + y2 + y3) / 3.0;
565
0
            dist2 = squared_distance(x, y, c_x, c_y);
566
0
            if (dist2 < closest_dist2) {
567
0
                closest_dist2 = dist2;
568
0
                closest_dist = sqrt(dist2);
569
0
                closest_i = i;
570
0
            }
571
0
        }
572
0
    }
573
0
    if (std::isinf(closest_dist)) {
574
        // nothing was found due to empty triangle list or only degenerate
575
        // triangles
576
0
        return nullptr;
577
0
    }
578
0
    const auto &triangle = triangles[closest_i];
579
0
    const unsigned i1 = triangle.idx1;
580
0
    const unsigned i2 = triangle.idx2;
581
0
    const unsigned i3 = triangle.idx3;
582
0
    const double x1 = vertices[i1 * colCount + idxX];
583
0
    const double y1 = vertices[i1 * colCount + idxY];
584
0
    const double x2 = vertices[i2 * colCount + idxX];
585
0
    const double y2 = vertices[i2 * colCount + idxY];
586
0
    const double x3 = vertices[i3 * colCount + idxX];
587
0
    const double y3 = vertices[i3 * colCount + idxY];
588
0
    const double det_T = (y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3);
589
0
    if (std::fabs(det_T) < EPS) {
590
        // the nearest triangle is degenerate
591
0
        return nullptr;
592
0
    }
593
0
    lambda1 = ((y2 - y3) * (x - x3) + (x3 - x2) * (y - y3)) / det_T;
594
0
    lambda2 = ((y3 - y1) * (x - x3) + (x1 - x3) * (y - y3)) / det_T;
595
0
    lambda3 = 1 - lambda1 - lambda2;
596
0
    return &triangle;
597
0
}
598
599
// ---------------------------------------------------------------------------
600
601
bool Evaluator::forward(double x, double y, double z, double &x_out,
602
0
                        double &y_out, double &z_out) {
603
0
    if (!mQuadTreeForward)
604
0
        mQuadTreeForward = BuildQuadTree(*(mFile.get()), true);
605
606
0
    double lambda1 = 0.0;
607
0
    double lambda2 = 0.0;
608
0
    double lambda3 = 0.0;
609
0
    const auto *triangle =
610
0
        FindTriangle(*mFile, *mQuadTreeForward, mTriangleIndices, x, y, true,
611
0
                     lambda1, lambda2, lambda3);
612
0
    if (!triangle)
613
0
        return false;
614
0
    const auto &vertices = mFile->vertices();
615
0
    const unsigned i1 = triangle->idx1;
616
0
    const unsigned i2 = triangle->idx2;
617
0
    const unsigned i3 = triangle->idx3;
618
0
    const unsigned colCount = mFile->verticesColumnCount();
619
0
    if (mFile->transformHorizontalComponent()) {
620
0
        constexpr unsigned idxTargetColX = 2;
621
0
        constexpr unsigned idxTargetColY = 3;
622
0
        x_out = vertices[i1 * colCount + idxTargetColX] * lambda1 +
623
0
                vertices[i2 * colCount + idxTargetColX] * lambda2 +
624
0
                vertices[i3 * colCount + idxTargetColX] * lambda3;
625
0
        y_out = vertices[i1 * colCount + idxTargetColY] * lambda1 +
626
0
                vertices[i2 * colCount + idxTargetColY] * lambda2 +
627
0
                vertices[i3 * colCount + idxTargetColY] * lambda3;
628
0
    } else {
629
0
        x_out = x;
630
0
        y_out = y;
631
0
    }
632
0
    if (mFile->transformVerticalComponent()) {
633
0
        const int idxCol = mFile->transformHorizontalComponent() ? 4 : 2;
634
0
        z_out = z + (vertices[i1 * colCount + idxCol] * lambda1 +
635
0
                     vertices[i2 * colCount + idxCol] * lambda2 +
636
0
                     vertices[i3 * colCount + idxCol] * lambda3);
637
0
    } else {
638
0
        z_out = z;
639
0
    }
640
0
    return true;
641
0
}
642
643
// ---------------------------------------------------------------------------
644
645
bool Evaluator::inverse(double x, double y, double z, double &x_out,
646
0
                        double &y_out, double &z_out) {
647
0
    NS_PROJ::QuadTree::QuadTree<unsigned> *quadtree;
648
0
    if (!mFile->transformHorizontalComponent() &&
649
0
        mFile->transformVerticalComponent()) {
650
0
        if (!mQuadTreeForward)
651
0
            mQuadTreeForward = BuildQuadTree(*(mFile.get()), true);
652
0
        quadtree = mQuadTreeForward.get();
653
0
    } else {
654
0
        if (!mQuadTreeInverse)
655
0
            mQuadTreeInverse = BuildQuadTree(*(mFile.get()), false);
656
0
        quadtree = mQuadTreeInverse.get();
657
0
    }
658
659
0
    double lambda1 = 0.0;
660
0
    double lambda2 = 0.0;
661
0
    double lambda3 = 0.0;
662
0
    const auto *triangle = FindTriangle(*mFile, *quadtree, mTriangleIndices, x,
663
0
                                        y, false, lambda1, lambda2, lambda3);
664
0
    if (!triangle)
665
0
        return false;
666
0
    const auto &vertices = mFile->vertices();
667
0
    const unsigned i1 = triangle->idx1;
668
0
    const unsigned i2 = triangle->idx2;
669
0
    const unsigned i3 = triangle->idx3;
670
0
    const unsigned colCount = mFile->verticesColumnCount();
671
0
    if (mFile->transformHorizontalComponent()) {
672
0
        constexpr unsigned idxTargetColX = 0;
673
0
        constexpr unsigned idxTargetColY = 1;
674
0
        x_out = vertices[i1 * colCount + idxTargetColX] * lambda1 +
675
0
                vertices[i2 * colCount + idxTargetColX] * lambda2 +
676
0
                vertices[i3 * colCount + idxTargetColX] * lambda3;
677
0
        y_out = vertices[i1 * colCount + idxTargetColY] * lambda1 +
678
0
                vertices[i2 * colCount + idxTargetColY] * lambda2 +
679
0
                vertices[i3 * colCount + idxTargetColY] * lambda3;
680
0
    } else {
681
0
        x_out = x;
682
0
        y_out = y;
683
0
    }
684
0
    if (mFile->transformVerticalComponent()) {
685
0
        const int idxCol = mFile->transformHorizontalComponent() ? 4 : 2;
686
0
        z_out = z - (vertices[i1 * colCount + idxCol] * lambda1 +
687
0
                     vertices[i2 * colCount + idxCol] * lambda2 +
688
0
                     vertices[i3 * colCount + idxCol] * lambda3);
689
0
    } else {
690
0
        z_out = z;
691
0
    }
692
0
    return true;
693
0
}
694
695
} // namespace TINSHIFT_NAMESPACE