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