/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 ▵ |
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 ▵ |
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 |