/src/s2geometry/src/s2/s2validation_query.h
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2022 Google Inc. All Rights Reserved. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS-IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | // |
15 | | |
16 | | // Author: smcallis@google.com (Sean McAllister) |
17 | | |
18 | | #ifndef S2_S2VALIDATION_QUERY_H_ |
19 | | #define S2_S2VALIDATION_QUERY_H_ |
20 | | |
21 | | #include <algorithm> |
22 | | #include <cmath> |
23 | | #include <cstdint> |
24 | | #include <cstdlib> |
25 | | #include <limits> |
26 | | #include <utility> |
27 | | #include <vector> |
28 | | |
29 | | #include "absl/algorithm/container.h" |
30 | | #include "absl/container/flat_hash_set.h" |
31 | | #include "absl/container/inlined_vector.h" |
32 | | #include "absl/log/absl_check.h" |
33 | | #include "absl/strings/str_format.h" |
34 | | #include "absl/types/span.h" |
35 | | #include "s2/_fp_contract_off.h" // IWYU pragma: keep |
36 | | #include "s2/internal/s2disjoint_set.h" |
37 | | #include "s2/internal/s2incident_edge_tracker.h" |
38 | | #include "s2/internal/s2index_cell_data.h" |
39 | | #include "s2/s2cell_id.h" |
40 | | #include "s2/s2contains_point_query.h" |
41 | | #include "s2/s2contains_vertex_query.h" |
42 | | #include "s2/s2edge_crosser.h" |
43 | | #include "s2/s2error.h" |
44 | | #include "s2/s2point.h" |
45 | | #include "s2/s2pointutil.h" |
46 | | #include "s2/s2predicates.h" |
47 | | #include "s2/s2shape.h" |
48 | | #include "s2/s2shape_index.h" |
49 | | #include "s2/s2shapeutil_edge_wrap.h" |
50 | | |
51 | | // This header defines the following queries which each have slightly different |
52 | | // correctness semantics for their particular domain. |
53 | | |
54 | | // clang-format off |
55 | | template <typename IndexType> class S2ValidQuery; |
56 | | template <typename IndexType> class S2LegacyValidQuery; |
57 | | // clang-format on |
58 | | |
59 | | // Base class for validating geometry contained in an index according to a |
60 | | // configurable model of correctness. There are several different notions of |
61 | | // "valid" geometry that we have to address, including the basic requirements |
62 | | // for S2BooleanOperation, S2Polygon specific checks, OGC simple geometry |
63 | | // standards, and STLib's requirements. |
64 | | // |
65 | | // Classes of geometry can be thought of as sets of all shapes that have certain |
66 | | // invariants (such as interior-on-left or no-degenerate-edges). Classes then |
67 | | // naturally build on other classes by adding more rules to further restrict the |
68 | | // allowed shapes. |
69 | | // |
70 | | // This extends-upon structure naturally lends itself to an inheritance based |
71 | | // implementation. Beginning with the most permissive class of geometry which |
72 | | // just meets the criteria for S2BooleanOperation (S2ValidQuery), we can then |
73 | | // build subsequent classes of geometry by inheriting from and extending the |
74 | | // checks that are performed. |
75 | | // |
76 | | // Validation queries should be templated over the exact index type and can |
77 | | // overload the virtual methods in the subclass API below. |
78 | | // |
79 | | // The Validate() driver function will call these functions in this order: |
80 | | // |
81 | | // - Start() |
82 | | // - CheckShape() |
83 | | // - StartCell() |
84 | | // - StartShape() |
85 | | // - CheckEdge() |
86 | | // - FinishShape() |
87 | | // - Finish() |
88 | | // |
89 | | // Hoisting the loops into the base class like this allows us to fuse all the |
90 | | // inner loops of the various queries so that we only have to iterate over |
91 | | // the index and its geometry once. |
92 | | // |
93 | | // Several protected member functions are defined to give subclasses access to |
94 | | // the data being validated: |
95 | | // |
96 | | // const IndexType& Index(); |
97 | | // -- Returns a reference to the current index being operated on. |
98 | | // |
99 | | // const S2IndexCellData& CurrentCell(); |
100 | | // -- Returns a reference to the decoded data for the current cell. |
101 | | // |
102 | | // const IncidentEdgeSet& IncidentEdges(); |
103 | | // -- Returns a reference to the current incident edge set. We promise that |
104 | | // this set is updated with the current cell's edges before StartCell() is |
105 | | // called. |
106 | | // |
107 | | // A query then has a `bool Validate(const IndexType& index, S2Error* error)` |
108 | | // method which validates the index and returns true if it's valid, otherwise |
109 | | // false, with the validation failure details provided through the error |
110 | | // parameter. |
111 | | // |
112 | | // This example validates an index as containing valid geometry for |
113 | | // use with S2Polygon/S2Polyline: |
114 | | // |
115 | | // S2LegacyValidQuery<MutableS2ShapeIndex> query; |
116 | | // if (!query.Validate(index, error)) { |
117 | | // ... |
118 | | // } |
119 | | // |
120 | | template <typename IndexType> |
121 | | class S2ValidationQueryBase { |
122 | | public: |
123 | | using IncidentEdgeSet = internal::IncidentEdgeSet; |
124 | | using EdgeAndIdChain = typename internal::S2IndexCellData::EdgeAndIdChain; |
125 | | using S2IndexCellData = internal::S2IndexCellData; |
126 | | |
127 | 0 | S2ValidationQueryBase() = default; |
128 | 0 | virtual ~S2ValidationQueryBase() = default; |
129 | | |
130 | | // Validate the index by calling the hooks in the derived class. |
131 | | bool Validate(const IndexType& index, S2Error* error); |
132 | | |
133 | | protected: |
134 | | using Iterator = typename IndexType::Iterator; |
135 | | |
136 | | // Subclass API |
137 | | // Starts the validation process; called once per query. |
138 | 0 | virtual bool Start(S2Error*) { return true; } |
139 | | |
140 | | // Validates each individual shape in the index; called once per shape. |
141 | | // |
142 | | // A reference to the current iterator state is passed in. The function may |
143 | | // reposition the iterator in order to do shape checking. |
144 | | virtual bool CheckShape(Iterator& iter, const S2Shape& shape, int shape_id, |
145 | 0 | S2Error*) { |
146 | 0 | return true; |
147 | 0 | } |
148 | | |
149 | | // Starts processing of a cell in the index; called once per cell. |
150 | 0 | virtual bool StartCell(S2Error*) { return true; } |
151 | | |
152 | | // Marks start of a clipped shape; called once per clipped shape in a cell. |
153 | 0 | virtual bool StartShape(const S2Shape&, const S2ClippedShape&, S2Error*) { |
154 | 0 | return true; |
155 | 0 | } |
156 | | |
157 | | // Validates a single edge of a given shape; called at least once per edge of |
158 | | // each shape. |
159 | | virtual bool CheckEdge(const S2Shape&, const S2ClippedShape&, |
160 | 0 | const EdgeAndIdChain&, S2Error*) { |
161 | 0 | return true; |
162 | 0 | } |
163 | | |
164 | | // Marks end of a clipped shape; called once per clipped shape in a cell. |
165 | 0 | virtual bool FinishShape(const S2Shape&, const S2ClippedShape&, S2Error*) { |
166 | 0 | return true; |
167 | 0 | } |
168 | | |
169 | | // Marks end of validation; called once per query. |
170 | 0 | virtual bool Finish(S2Error* error) { return true; } |
171 | | |
172 | | // Returns a reference to the index we're validating. |
173 | 0 | const IndexType& Index() const { |
174 | 0 | ABSL_DCHECK(index_ != nullptr); |
175 | 0 | return *index_; |
176 | 0 | } |
177 | | |
178 | | // Returns current incident edge map. |
179 | 0 | const IncidentEdgeSet& IncidentEdges() const { |
180 | 0 | return incident_edge_tracker_.IncidentEdges(); |
181 | 0 | } |
182 | | |
183 | | // Returns a reference to the data for the current cell. |
184 | 0 | const S2IndexCellData& CurrentCell() const { return cell_buffer_; } |
185 | | |
186 | | private: |
187 | | // Disallow copying and assignment through an abstract reference. |
188 | | S2ValidationQueryBase(const S2ValidationQueryBase&) = delete; |
189 | | S2ValidationQueryBase& operator=(const S2ValidationQueryBase&) = delete; |
190 | | |
191 | | // Sets the current index we're operating on, accessible through Index(); |
192 | 0 | void SetIndex(const IndexType* index) { index_ = index; } |
193 | | |
194 | | // Sets the current cell that we're operating on and loads its data. |
195 | 0 | void SetCurrentCell(const Iterator& iter) { |
196 | 0 | cell_buffer_.LoadCell(index_, iter.id(), &iter.cell()); |
197 | 0 | } |
198 | | |
199 | | S2IndexCellData cell_buffer_; |
200 | | internal::S2IncidentEdgeTracker incident_edge_tracker_; |
201 | | const IndexType* index_ = nullptr; |
202 | | }; |
203 | | |
204 | | // This class represents the least strict class of geometry and corresponds to |
205 | | // the requirements for compatibility with S2BooleanOperation, with these |
206 | | // specific constraints: |
207 | | // |
208 | | // == General == |
209 | | // * Points must be unit magnitude (according to S2::IsUnitLength()). |
210 | | // * Points must be finite (neither inf or nan). |
211 | | // * Edges must not have antipodal vertices. |
212 | | // * Degenerate edges of the form {A,A} are allowed by default. |
213 | | // * Reverse-duplicate edges of the form {A,B},{B,A} are allowed by default. |
214 | | // * Shape chains must have at least one edge, excluding the full and empty |
215 | | // polygon shapes, which may have at most one empty chain. |
216 | | // |
217 | | // == Polygons == |
218 | | // * Polygon interiors must be disjoint from all other geometry. |
219 | | // * Polygon edges thus cannot cross any other edges, including at vertices. |
220 | | // * No geometry may test as contained in another polygon. |
221 | | // * Polygons can never have duplicate edges even among different polygons. |
222 | | // * Polygon edges must be connected and form a closed loop per chain. |
223 | | // * Polygon chains must oriented so that the interior is always on the left. |
224 | | // |
225 | | // == Polylines == |
226 | | // * Polyline edges can cross by default. |
227 | | // * Polylines can have duplicate edges by default. |
228 | | // |
229 | | template <typename IndexType> |
230 | | class S2ValidQuery : public S2ValidationQueryBase<IndexType> { |
231 | | protected: |
232 | | // Protected because options are only for subclasses to configure behavior. |
233 | | class Options { |
234 | | public: |
235 | | // Types of single-vertex touches allowed between shapes. This is |
236 | | // configurable for each combination of dimension. E.g. we can configure |
237 | | // polyline-polyline (1D-1D) and polyline-polygon (1D-2D) touches |
238 | | // separately. |
239 | | enum TouchType { |
240 | | kTouchNone = 0b00, // Allow no touches between shapes. |
241 | | kTouchInterior = 0b01, // Interior point may touch the other shape. |
242 | | kTouchBoundary = 0b10, // Boundary point may touch the other shape. |
243 | | kTouchAny = 0b11 // Allow any touches between shapes. |
244 | | }; |
245 | | |
246 | 0 | Options() { |
247 | | // Default is to allow any touches between geometry. |
248 | 0 | for (int i = 0; i < 3; ++i) { |
249 | 0 | for (int j = 0; j < 3; ++j) { |
250 | 0 | set_allowed_touches(i, j, std::make_pair(kTouchAny, kTouchAny)); |
251 | 0 | } |
252 | 0 | } |
253 | 0 | } |
254 | | |
255 | | // Returns a TouchType with fields set indicating what types of vertex from |
256 | | // dimension A are allowed to touch vertices from dimension B. |
257 | 0 | std::pair<TouchType, TouchType> allowed_touches(int dima, int dimb) const { |
258 | 0 | ABSL_DCHECK_GE(dima, 0); |
259 | 0 | ABSL_DCHECK_LE(dima, 2); |
260 | 0 | ABSL_DCHECK_GE(dimb, 0); |
261 | 0 | ABSL_DCHECK_LE(dimb, 2); |
262 | | |
263 | | // Just get the top half of the matrix. |
264 | 0 | if (dima > dimb) { |
265 | 0 | std::swap(dima, dimb); |
266 | 0 | } |
267 | |
|
268 | 0 | return allowed_touches_[dima][dimb]; |
269 | 0 | } |
270 | | |
271 | | // Set the allowed touches based on geometry dimension. We require that the |
272 | | // matrix of combinations be symmetric, so set_allowed_touches(1, 2, X, Y) |
273 | | // and set_allowed_touches(2, 1, Y, X) are equivalent. |
274 | | Options& set_allowed_touches( // |
275 | 0 | int dima, int dimb, std::pair<TouchType, TouchType> types) { |
276 | 0 | ABSL_DCHECK(0 <= dima && dima <= 2); |
277 | 0 | ABSL_DCHECK(0 <= dimb && dimb <= 2); |
278 | | |
279 | | // Just set the top half of the matrix. |
280 | 0 | if (dima > dimb) { |
281 | 0 | std::swap(dima, dimb); |
282 | 0 | } |
283 | |
|
284 | 0 | allowed_touches_[dima][dimb] = types; |
285 | 0 | return *this; |
286 | 0 | } |
287 | | |
288 | | // Configures whether polylines can have duplicate edges. |
289 | 0 | bool allow_duplicate_polyline_edges() const { |
290 | 0 | return allow_duplicate_polyline_edges_; |
291 | 0 | } |
292 | | |
293 | | Options& set_allow_duplicate_polyline_edges(bool flag) { |
294 | | allow_duplicate_polyline_edges_ = flag; |
295 | | return *this; |
296 | | } |
297 | | |
298 | | // Configures whether polyline edges can cross. |
299 | 0 | bool allow_polyline_interior_crossings() const { |
300 | 0 | return allow_polyline_interior_crossings_; |
301 | 0 | } |
302 | | |
303 | | Options& set_allow_polyline_interior_crossings(bool flag) { |
304 | | allow_polyline_interior_crossings_ = flag; |
305 | | return *this; |
306 | | } |
307 | | |
308 | | // Configures whether reverse duplicate edges are allowed. |
309 | 0 | bool allow_reverse_duplicates() const { return allow_reverse_duplicates_; } |
310 | | |
311 | 0 | Options& set_allow_reverse_duplicates(bool flag) { |
312 | 0 | allow_reverse_duplicates_ = flag; |
313 | 0 | return *this; |
314 | 0 | } |
315 | | |
316 | | // Configures whether degenerate (zero-length) edges are allowed. |
317 | 0 | bool allow_degenerate_edges() const { return allow_degenerate_edges_; } |
318 | | |
319 | 0 | Options& set_allow_degenerate_edges(bool flag) { |
320 | 0 | allow_degenerate_edges_ = flag; |
321 | 0 | return *this; |
322 | 0 | } |
323 | | |
324 | | private: |
325 | | bool allow_degenerate_edges_ = true; |
326 | | bool allow_duplicate_polyline_edges_ = true; |
327 | | bool allow_reverse_duplicates_ = true; |
328 | | bool allow_polyline_interior_crossings_ = true; |
329 | | |
330 | | std::pair<TouchType, TouchType> allowed_touches_[3][3]; |
331 | | }; |
332 | | |
333 | 0 | const Options& options() const { return options_; } |
334 | 0 | Options& mutable_options() { return options_; } |
335 | | |
336 | | // We have to include unqualified names manually due to template inheritance. |
337 | | using Base = S2ValidationQueryBase<IndexType>; |
338 | | using Base::CurrentCell; |
339 | | using Base::IncidentEdges; |
340 | | using Base::Index; |
341 | | using typename Base::EdgeAndIdChain; |
342 | | using typename Base::Iterator; |
343 | | using typename Base::S2IndexCellData; |
344 | | |
345 | | bool CheckShape(Iterator& iter, const S2Shape& shape, int shape_id, |
346 | | S2Error*) override; |
347 | | bool StartCell(S2Error*) override; |
348 | | bool CheckEdge(const S2Shape& shape, const S2ClippedShape& clipped, |
349 | | const EdgeAndIdChain& edge, S2Error*) override; |
350 | | bool Finish(S2Error* error) override; |
351 | | |
352 | | private: |
353 | | // Returns true if the given S2Point is valid, meaning none of its components |
354 | | // are inf or NaN. |
355 | 0 | static inline bool ValidPoint(const S2Point& p) { |
356 | 0 | return std::isfinite(p.x()) && std::isfinite(p.y()) && std::isfinite(p.z()); |
357 | 0 | } |
358 | | |
359 | | // Checks that a point is fully outside any polygons. |
360 | | // |
361 | | // Returns false if a point is not interior to any polygons, true otherwise |
362 | | // with error populated. |
363 | | bool PointContained(S2CellId, int shape_id, const S2Point&, S2Error*); |
364 | | |
365 | | // Checks if any edge in the current cell is a duplicate (or reverse |
366 | | // duplicate). |
367 | | bool CheckForDuplicateEdges(S2Error* error) const; |
368 | | |
369 | | // Checks if any edges in the current cell have an interior crossing. |
370 | | bool CheckForInteriorCrossings(S2Error* error) const; |
371 | | |
372 | | // Checks that a given chain of a polygon is oriented properly relative to one |
373 | | // cell center in the index. We only need to check one center because we |
374 | | // assume that the transition between cell indices is consistent, thus we just |
375 | | // need to make sure that the interior state isn't flipped. |
376 | | // |
377 | | // Returns false when the chain isn't properly oriented with S2Error set with |
378 | | // the details, true otherwise. |
379 | | // |
380 | | // REQUIRES: Shape must be two dimensional. |
381 | | // REQUIRES: Chain must be closed. |
382 | | // REQUIRES: Chain edges must be connected (no gaps). |
383 | | // REQUIRES: Chain must have at least three distinct points (cover some area). |
384 | | bool CheckChainOrientation( // |
385 | | Iterator&, const S2Shape&, int shape_id, int chain_id, S2Error*); |
386 | | |
387 | | // Information on one vertex of an edge, including whether it's on the |
388 | | // boundary of its shape. This is used by CheckTouchesAreValid() when we need |
389 | | // to check that vertex touches are valid. |
390 | | struct TestVertex { |
391 | | S2Point vertex; |
392 | | int32_t edge_id; |
393 | | int32_t shape_id; |
394 | | int32_t dim; |
395 | | bool on_boundary; |
396 | | }; |
397 | | std::vector<TestVertex> test_vertices_; |
398 | | |
399 | | // Checks the shapes in the current cell to ensure that any touch points are |
400 | | // allowed under the configured semantics. |
401 | | // |
402 | | // Returns false if any vertices touch at an invalid point with the error |
403 | | // value set, true otherwise. |
404 | | bool CheckTouchesAreValid(S2Error*); |
405 | | |
406 | | Options options_; |
407 | | }; |
408 | | |
409 | | ////////////////// Utility Implementation //////////////////// |
410 | | |
411 | | // Sorts a container of S2Shape::Edges in counter-clockwise order around an |
412 | | // origin point. By itself, ordering this way is ambiguous in terms of the |
413 | | // starting point, so we take an edge to form a reference point to form a total |
414 | | // ordering. |
415 | | // |
416 | | // Reverse duplicate edges are ordered so that the one with origin as v0 comes |
417 | | // before the other. |
418 | | // |
419 | | // The edges and first edge must all contain origin as one of their vertices. |
420 | | // |
421 | | // Can sort any container with elements that are convertible to an S2Shape::Edge |
422 | | // reference. Both the origin point and first edge are taken by value so that |
423 | | // it's safe to call using elements of the container for those values. |
424 | | // |
425 | | // Takes a Container to the start and end of the container. std::sort requires |
426 | | // random iterators and thus so do we. |
427 | | template <typename Container> |
428 | 0 | void SortEdgesCcw(S2Point origin, S2Shape::Edge first, Container& data) { |
429 | 0 | ABSL_DCHECK(first.v0 == origin || first.v1 == origin); |
430 | 0 | const S2Point& first_vertex = (first.v0 == origin) ? first.v1 : first.v0; |
431 | 0 | ABSL_DCHECK(first_vertex != origin); |
432 | |
|
433 | 0 | absl::c_sort( // |
434 | 0 | data, // |
435 | 0 | [&](const S2Shape::Edge& a, const S2Shape::Edge& b) { |
436 | 0 | ABSL_DCHECK(a.v0 == origin || a.v1 == origin); |
437 | 0 | ABSL_DCHECK(b.v0 == origin || b.v1 == origin); |
438 | | |
439 | | // `OrderedCCW` will return `true` if `a == b`, which will violate the |
440 | | // irreflexivity requirement of a strict weak ordering. |
441 | 0 | if (a == b) { |
442 | 0 | return false; |
443 | 0 | } |
444 | | |
445 | | // Order reverse duplicates so that the one with edge.v0 == |
446 | | // origin is first. |
447 | 0 | if (a == b.Reversed()) { |
448 | 0 | return a.v0 == origin; |
449 | 0 | } |
450 | | |
451 | 0 | if (a == first || b == first) { |
452 | | // If either edge is the first edge, compare so that the |
453 | | // first edge always comes first in the sorted output. |
454 | 0 | return a == first; |
455 | 0 | } |
456 | | |
457 | | // Otherwise check orientation of vertices. |
458 | 0 | S2Point apnt = (a.v0 == origin) ? a.v1 : a.v0; |
459 | 0 | S2Point bpnt = (b.v0 == origin) ? b.v1 : b.v0; |
460 | 0 | return s2pred::OrderedCCW(first_vertex, apnt, bpnt, origin); |
461 | 0 | }); |
462 | 0 | } |
463 | | |
464 | | template <typename IndexType> |
465 | | bool S2ValidationQueryBase<IndexType>::Validate(const IndexType& index, |
466 | 0 | S2Error* error) { |
467 | 0 | SetIndex(&index); |
468 | 0 | incident_edge_tracker_.Reset(); |
469 | 0 | cell_buffer_.Reset(); |
470 | |
|
471 | 0 | if (!Start(error)) { |
472 | 0 | return false; |
473 | 0 | } |
474 | | |
475 | | // Run basic checks on individual shapes in the index. |
476 | 0 | Iterator iter(&index, S2ShapeIndex::BEGIN); |
477 | 0 | for (int shape_id = 0; shape_id < index.num_shape_ids(); ++shape_id) { |
478 | 0 | const S2Shape* shape = index.shape(shape_id); |
479 | 0 | if (shape != nullptr) { |
480 | 0 | if (!CheckShape(iter, *shape, shape_id, error)) { |
481 | 0 | return false; |
482 | 0 | } |
483 | 0 | } |
484 | 0 | } |
485 | | |
486 | 0 | for (iter.Begin(); !iter.done(); iter.Next()) { |
487 | 0 | SetCurrentCell(iter); |
488 | | |
489 | | // Add two dimensional shape edges to the incident edge tracker to support |
490 | | // checks for things like crossing polygon chains and split interiors. |
491 | 0 | for (const S2ClippedShape& clipped : CurrentCell().clipped_shapes()) { |
492 | 0 | const int shape_id = clipped.shape_id(); |
493 | 0 | const S2Shape& shape = CurrentCell().shape(clipped); |
494 | 0 | if (shape.dimension() < 2) { |
495 | 0 | continue; |
496 | 0 | } |
497 | | |
498 | 0 | incident_edge_tracker_.StartShape(shape_id); |
499 | 0 | for (const auto& edge : CurrentCell().shape_edges(shape_id)) { |
500 | 0 | incident_edge_tracker_.AddEdge(edge.id, edge); |
501 | 0 | } |
502 | 0 | incident_edge_tracker_.FinishShape(); |
503 | 0 | } |
504 | | |
505 | | // Now notify that we're starting this cell. |
506 | 0 | if (!StartCell(error)) { |
507 | 0 | return false; |
508 | 0 | } |
509 | | |
510 | | // Iterate the shapes and edges of the cell. |
511 | 0 | for (const S2ClippedShape& clipped : CurrentCell().clipped_shapes()) { |
512 | 0 | const int shape_id = clipped.shape_id(); |
513 | 0 | const S2Shape& shape = CurrentCell().shape(clipped); |
514 | 0 | if (!StartShape(shape, clipped, error)) { |
515 | 0 | return false; |
516 | 0 | } |
517 | | |
518 | 0 | for (const auto& edge : CurrentCell().shape_edges(shape_id)) { |
519 | 0 | if (!CheckEdge(shape, clipped, edge, error)) { |
520 | 0 | return false; |
521 | 0 | } |
522 | 0 | } |
523 | | |
524 | 0 | if (!FinishShape(shape, clipped, error)) { |
525 | 0 | return false; |
526 | 0 | } |
527 | 0 | } |
528 | 0 | } |
529 | | |
530 | | // Run any final checks and finish validation |
531 | 0 | return Finish(error); |
532 | 0 | } |
533 | | |
534 | | // This class represents a semantic class of geometry that's compatible with the |
535 | | // requirements of the S2Polygon and S2Polyline IsValid() methods. It extends |
536 | | // the S2ValidQuery requirements, specifically: |
537 | | // |
538 | | // == General == |
539 | | // * Degenerate edges of the form {A,A} are not allowed. |
540 | | // * All the shapes in an S2ShapeIndex must be the same dimensionality. |
541 | | // * Duplicate vertices in a chain are not allowed. |
542 | | // I.e. a chain cannot touch itself even at one point. |
543 | | // * Different chains may touch, but only in such a way they don't create |
544 | | // duplicate edges. |
545 | | // |
546 | | template <typename IndexType> |
547 | | class S2LegacyValidQuery final : public S2ValidQuery<IndexType> { |
548 | | public: |
549 | | S2LegacyValidQuery(); |
550 | | |
551 | | protected: |
552 | | // We have to include unqualified names manually due to template inheritance. |
553 | | using Base = S2ValidQuery<IndexType>; |
554 | | using Base::CurrentCell; |
555 | | using Base::IncidentEdges; |
556 | | using Base::Index; |
557 | | using typename Base::EdgeAndIdChain; |
558 | | using typename Base::Iterator; |
559 | | using typename Base::S2IndexCellData; |
560 | | |
561 | | bool Start(S2Error*) final; |
562 | | bool CheckShape(Iterator&, const S2Shape& shape, int shape_id, |
563 | | S2Error*) final; |
564 | | bool StartCell(S2Error*) final; |
565 | | bool CheckEdge(const S2Shape& shape, const S2ClippedShape& clipped, |
566 | | const EdgeAndIdChain& edge, S2Error*) override; |
567 | | |
568 | | private: |
569 | | // Tuple of (shape, chain, vertex) for detecting duplicate vertices in the |
570 | | // same chain. |
571 | | struct ShapeChainVertex { |
572 | | // Constructor just to twiddle order of fields. |
573 | | ShapeChainVertex(int shape, int chain, S2Point vertex) |
574 | | : vertex(vertex), chain(chain), shape(shape) {} |
575 | | |
576 | | S2Point vertex; |
577 | | int chain; |
578 | | int shape; |
579 | | |
580 | | // Makes type hashable for use in Abseil containers. |
581 | | template <typename H> |
582 | | friend H AbslHashValue(H h, const ShapeChainVertex& a) { |
583 | | return H::combine(std::move(h), a.vertex, a.shape, a.chain); |
584 | | } |
585 | | }; |
586 | | }; |
587 | | |
588 | | ////////////////// S2ValidQuery Implementation //////////////////// |
589 | | |
590 | | template <typename IndexType> |
591 | | bool S2ValidQuery<IndexType>::CheckShape(Iterator& iter, const S2Shape& shape, |
592 | 0 | int shape_id, S2Error* error) { |
593 | | // Verify that shape isn't outright lying to us about its dimension. |
594 | 0 | const int dim = shape.dimension(); |
595 | 0 | if (dim < 0 || dim > 2) { |
596 | 0 | *error = S2Error( |
597 | 0 | S2Error::INVALID_DIMENSION, |
598 | 0 | absl::StrFormat("Shape %d has invalid dimension: %d", shape_id, dim)); |
599 | 0 | return false; |
600 | 0 | } |
601 | | |
602 | 0 | absl::InlinedVector<int, 4> chains_to_check; |
603 | 0 | for (int chain_id = 0; chain_id < shape.num_chains(); ++chain_id) { |
604 | 0 | const S2Shape::Chain& chain = shape.chain(chain_id); |
605 | | |
606 | | // Check that the first and last edges in a polygon chain connect to close |
607 | | // the chain. This is true even for degenerate chains with one edge that's |
608 | | // a point, or two edges that are reverse duplicates. Both are considered |
609 | | // closed. |
610 | 0 | if (dim == 2 && chain.length > 0) { |
611 | 0 | int edge_id = chain.start; |
612 | 0 | int prev_id = s2shapeutil::PrevEdgeWrap(shape, edge_id); |
613 | |
|
614 | 0 | if (shape.edge(prev_id).v1 != shape.edge(edge_id).v0) { |
615 | 0 | *error = S2Error(S2Error::LOOP_NOT_ENOUGH_VERTICES, |
616 | 0 | absl::StrFormat("Chain %d of shape %d isn't closed", |
617 | 0 | chain_id, shape_id)); |
618 | 0 | return false; |
619 | 0 | } |
620 | 0 | } |
621 | | |
622 | 0 | for (int offset = 0; offset < chain.length; ++offset) { |
623 | 0 | const S2Shape::Edge& edge = shape.chain_edge(chain_id, offset); |
624 | | |
625 | | // Check that coordinates aren't inf/nan. |
626 | 0 | if (!ValidPoint(edge.v0) || !ValidPoint(edge.v1)) { |
627 | 0 | *error = S2Error( |
628 | 0 | S2Error::INVALID_VERTEX, |
629 | 0 | absl::StrFormat("Shape %d has invalid coordinates", shape_id)); |
630 | 0 | return false; |
631 | 0 | } |
632 | | |
633 | | // Check that vertices are unit length. |
634 | 0 | if (!S2::IsUnitLength(edge.v0) || !S2::IsUnitLength(edge.v1)) { |
635 | 0 | *error = S2Error( |
636 | 0 | S2Error::NOT_UNIT_LENGTH, |
637 | 0 | absl::StrFormat("Shape %d has non-unit length vertices", shape_id)); |
638 | 0 | return false; |
639 | 0 | } |
640 | | |
641 | | // (Optional) check polyline and polygon edges for degeneracy. |
642 | 0 | if (dim > 0 && !options().allow_degenerate_edges()) { |
643 | 0 | if (edge.IsDegenerate()) { |
644 | 0 | *error = S2Error( |
645 | 0 | S2Error::DUPLICATE_VERTICES, |
646 | 0 | absl::StrFormat("Shape %d: chain %d, edge %d is degenerate", |
647 | 0 | shape_id, chain_id, chain.start + offset)); |
648 | 0 | return false; |
649 | 0 | } |
650 | 0 | } |
651 | | |
652 | | // Check that edge doesn't have antipodal vertices. |
653 | 0 | if (edge.v0 == -edge.v1) { |
654 | 0 | *error = |
655 | 0 | S2Error(S2Error::ANTIPODAL_VERTICES, |
656 | 0 | absl::StrFormat("Shape %d has adjacent antipodal vertices", |
657 | 0 | shape_id)); |
658 | 0 | return false; |
659 | 0 | } |
660 | | |
661 | | // Check that chain edges are connected for polylines and polygons. |
662 | 0 | if (dim > 0 && chain.length >= 2 && offset > 0) { |
663 | 0 | const S2Shape::Edge last = shape.chain_edge(chain_id, offset - 1); |
664 | 0 | if (last.v1 != edge.v0) { |
665 | 0 | *error = |
666 | 0 | S2Error(S2Error::NOT_CONTINUOUS, |
667 | 0 | absl::StrFormat("Chain %d of shape %d has neighboring " |
668 | 0 | "edges that don't connect.", |
669 | 0 | chain_id, shape_id)); |
670 | 0 | return false; |
671 | 0 | } |
672 | 0 | } |
673 | 0 | } |
674 | | |
675 | | // The rest of the checks are for non-empty polygon chains only. |
676 | 0 | if (dim != 2 || chain.length == 0) { |
677 | 0 | continue; |
678 | 0 | } |
679 | | |
680 | | // We need at least two distinct points in a chain before we can check its |
681 | | // orientation vs the cell center. Scan until we find a vertex different |
682 | | // than the first. |
683 | 0 | int unique_count = 1; |
684 | 0 | const S2Point first = shape.chain_edge(chain_id, 0).v0; |
685 | 0 | for (const S2Point& vertex : shape.vertices(chain_id)) { |
686 | 0 | if (vertex != first) { |
687 | 0 | ++unique_count; |
688 | 0 | break; |
689 | 0 | } |
690 | 0 | } |
691 | | |
692 | | // Only a single unique point. A degenerate edge will never test as a vertex |
693 | | // crossing (because 3 out of 4 vertices to S2::VertexCrossing() would be |
694 | | // equal making it false), so they can't toggle interior state and we can |
695 | | // ignore them. |
696 | 0 | if (unique_count == 1) { |
697 | 0 | continue; |
698 | 0 | } |
699 | | |
700 | 0 | chains_to_check.emplace_back(chain_id); |
701 | 0 | } |
702 | | |
703 | | // Check the selected chain to verify chain orientation. |
704 | 0 | for (int chain_id : chains_to_check) { |
705 | 0 | if (!CheckChainOrientation(iter, shape, shape_id, chain_id, error)) { |
706 | 0 | return false; |
707 | 0 | } |
708 | 0 | } |
709 | 0 | return true; |
710 | 0 | } |
711 | | |
712 | | template <typename IndexType> |
713 | 0 | bool S2ValidQuery<IndexType>::CheckForDuplicateEdges(S2Error* error) const { |
714 | 0 | int dim0 = options().allow_duplicate_polyline_edges() ? 2 : 1; |
715 | 0 | int dim1 = 2; |
716 | | |
717 | | // This is O(N^2) but cells don't have many edges in them and benchmarks show |
718 | | // this to be faster than trying to sort the whole array ahead of time. |
719 | 0 | absl::Span<const EdgeAndIdChain> edges = |
720 | 0 | CurrentCell().dim_range_edges(dim0, dim1); |
721 | 0 | int num_edges = edges.size(); |
722 | 0 | for (int i = 0; i < num_edges; ++i) { |
723 | 0 | for (int j = i + 1; j < num_edges; ++j) { |
724 | 0 | bool duplicate = (edges[i] == edges[j]); |
725 | |
|
726 | 0 | if (!options().allow_reverse_duplicates()) { |
727 | 0 | duplicate |= (edges[i].Reversed() == edges[j]); |
728 | 0 | } |
729 | |
|
730 | 0 | if (duplicate) { |
731 | 0 | *error = S2Error(S2Error::OVERLAPPING_GEOMETRY, |
732 | 0 | "One or more duplicate polygon edges detected"); |
733 | 0 | return false; |
734 | 0 | } |
735 | 0 | } |
736 | 0 | } |
737 | | |
738 | 0 | return true; |
739 | 0 | } |
740 | | |
741 | | template <typename IndexType> |
742 | 0 | bool S2ValidQuery<IndexType>::CheckForInteriorCrossings(S2Error* error) const { |
743 | | // Get all the polyline and polygon edges. |
744 | 0 | absl::Span<const EdgeAndIdChain> edges = CurrentCell().dim_range_edges(1, 2); |
745 | | |
746 | | // If we're allowing polyline edges to cross polyline edges, then we only have |
747 | | // to check against polygon edges. |
748 | 0 | size_t check_start = 0; |
749 | 0 | if (options().allow_polyline_interior_crossings()) { |
750 | 0 | check_start = CurrentCell().dim_edges(1).size(); |
751 | 0 | } |
752 | | |
753 | | // This can happen when we're allowing polyline crossings and only have |
754 | | // polylines, there's nothing to do. |
755 | 0 | if (check_start >= edges.size()) { |
756 | 0 | return true; |
757 | 0 | } |
758 | | |
759 | 0 | const size_t num_edges = edges.size(); |
760 | 0 | for (size_t i = 0; i + 1 < num_edges; ++i) { |
761 | | // We never have to check against edges at a lower index, because if we |
762 | | // intersect them, we'll have already checked that at this point. |
763 | 0 | size_t j = std::max(check_start, i + 1); |
764 | | |
765 | | // We can skip adjacent edges. |
766 | 0 | if (edges[i].v1 == edges[j].v0) { |
767 | 0 | if (++j >= num_edges) { |
768 | 0 | break; |
769 | 0 | } |
770 | 0 | } |
771 | | |
772 | 0 | S2EdgeCrosser crosser(&edges[i].v0, &edges[i].v1); |
773 | 0 | for (; j < num_edges; ++j) { |
774 | 0 | if (crosser.c() == nullptr || *crosser.c() != edges[j].v0) { |
775 | 0 | crosser.RestartAt(&edges[j].v0); |
776 | 0 | } |
777 | |
|
778 | 0 | if (crosser.CrossingSign(&edges[j].v1) > 0) { |
779 | 0 | *error = |
780 | 0 | S2Error(S2Error::OVERLAPPING_GEOMETRY, |
781 | 0 | absl::StrFormat("Chain %d edge %d crosses chain %d edge %d", |
782 | 0 | edges[i].chain, edges[i].offset, |
783 | 0 | edges[j].chain, edges[j].offset)); |
784 | 0 | return false; |
785 | 0 | } |
786 | 0 | } |
787 | 0 | } |
788 | 0 | return true; |
789 | 0 | } |
790 | | |
791 | | // Returns true if a vertex (0 or 1) of an edge of a polyline is a boundary |
792 | | // point or not. This is only true if the vertex is either the start or end |
793 | | // point of a chain and the chain is open. Returns false otherwise. |
794 | | // |
795 | | // REQUIRES: vertex == 0 or vertex == 1 |
796 | | inline bool PolylineVertexIsBoundaryPoint( |
797 | | const S2Shape& shape, const internal::S2IndexCellData::EdgeAndIdChain& edge, |
798 | 0 | int vertex) { |
799 | 0 | ABSL_DCHECK(vertex == 0 || vertex == 1); |
800 | |
|
801 | 0 | if (edge.offset == 0 && vertex == 0) { |
802 | | // First vertex of first edge |
803 | 0 | return s2shapeutil::PrevEdgeWrap(shape, edge.id) == -1; |
804 | 0 | } else if (edge.offset == shape.chain(edge.chain).length - 1 && vertex == 1) { |
805 | | // Last vertex of last edge |
806 | 0 | return s2shapeutil::NextEdgeWrap(shape, edge.id) == -1; |
807 | 0 | } |
808 | | |
809 | 0 | return false; |
810 | 0 | } |
811 | | |
812 | | template <typename IndexType> |
813 | 0 | bool S2ValidQuery<IndexType>::CheckTouchesAreValid(S2Error* error) { |
814 | 0 | using TouchType = typename Options::TouchType; |
815 | |
|
816 | 0 | const auto kAny = Options::kTouchAny; |
817 | |
|
818 | 0 | bool need_dim[3] = {true, true, true}; |
819 | 0 | for (int i = 0; i < 3; ++i) { |
820 | 0 | for (int j = 0; j < 3; ++j) { |
821 | 0 | bool any_allowed = |
822 | 0 | (options().allowed_touches(i, j) == std::make_pair(kAny, kAny)); |
823 | 0 | need_dim[i] &= !any_allowed; |
824 | 0 | } |
825 | 0 | } |
826 | | |
827 | | // If all touches are allowed, then we don't have to check, easy. |
828 | 0 | if (!need_dim[0] && !need_dim[1] && !need_dim[2]) { |
829 | 0 | return true; |
830 | 0 | } |
831 | | |
832 | | // Gather unique vertices from each dimension that needs to be checked. |
833 | 0 | test_vertices_.clear(); |
834 | 0 | for (const S2ClippedShape& clipped : CurrentCell().clipped_shapes()) { |
835 | 0 | const int shape_id = clipped.shape_id(); |
836 | 0 | const S2Shape& shape = *Index().shape(shape_id); |
837 | 0 | const int dim = shape.dimension(); |
838 | | |
839 | | // Skip if we're not checking this dimension. |
840 | 0 | if (!need_dim[dim]) { |
841 | 0 | continue; |
842 | 0 | } |
843 | | |
844 | 0 | for (const EdgeAndIdChain& edge : CurrentCell().shape_edges(shape_id)) { |
845 | | // For polylines, we have to handle the start and ending edges specially, |
846 | | // since we can have open chains that have boundary points. |
847 | 0 | if (dim == 1) { |
848 | 0 | bool on_boundary = PolylineVertexIsBoundaryPoint(shape, edge, 0); |
849 | 0 | test_vertices_.push_back( |
850 | 0 | {edge.v0, edge.id, shape_id, dim, on_boundary}); |
851 | | |
852 | | // Check vertex 1 too only if it's a boundary point, otherwise we would |
853 | | // test v1 twice after we grab v0 of the next edge. |
854 | 0 | on_boundary = PolylineVertexIsBoundaryPoint(shape, edge, 1); |
855 | 0 | if (on_boundary) { |
856 | 0 | test_vertices_.push_back({edge.v1, edge.id, shape_id, dim, true}); |
857 | 0 | } |
858 | 0 | } else { |
859 | | // All polygon vertices are on the boundary, all point vertices are not. |
860 | 0 | test_vertices_.push_back({edge.v0, edge.id, shape_id, dim, dim == 2}); |
861 | 0 | } |
862 | 0 | } |
863 | 0 | } |
864 | | |
865 | | // For each test vertex, scan over the other shapes and verify that any vertex |
866 | | // touches are allowed under the current semantics. |
867 | 0 | for (const TestVertex& testpnt : test_vertices_) { |
868 | 0 | for (const S2ClippedShape& clipped : CurrentCell().clipped_shapes()) { |
869 | 0 | const int shape_id = clipped.shape_id(); |
870 | 0 | const S2Shape& shape = *Index().shape(shape_id); |
871 | 0 | const int dim = shape.dimension(); |
872 | |
|
873 | 0 | for (const EdgeAndIdChain& edge : CurrentCell().shape_edges(shape_id)) { |
874 | | // Don't compare an edge against itself. |
875 | 0 | if (testpnt.shape_id == shape_id && testpnt.edge_id == edge.id) { |
876 | 0 | continue; |
877 | 0 | } |
878 | | |
879 | | // Figure out which (if any) vertex of this edge we touched. |
880 | 0 | int vertidx = -1; |
881 | 0 | if (testpnt.vertex == edge.v0) vertidx = 0; |
882 | 0 | if (testpnt.vertex == edge.v1) vertidx = 1; |
883 | 0 | if (vertidx < 0) { |
884 | 0 | continue; |
885 | 0 | } |
886 | | |
887 | | // Closed polylines are always allowed, so if we get a hit on the same |
888 | | // shape and it's a polyline, check that it's not from the closure. |
889 | 0 | if (testpnt.shape_id == shape_id && dim == 1) { |
890 | 0 | if (vertidx == 0 && |
891 | 0 | s2shapeutil::PrevEdgeWrap(shape, edge.id) == testpnt.edge_id) { |
892 | 0 | continue; |
893 | 0 | } |
894 | | |
895 | 0 | if (vertidx == 1 && |
896 | 0 | s2shapeutil::NextEdgeWrap(shape, edge.id) == testpnt.edge_id) { |
897 | 0 | continue; |
898 | 0 | } |
899 | 0 | } |
900 | | |
901 | | // Points vertices are always interior, Polygon vertices are always |
902 | | // boundary, and it may be either for polylines. |
903 | 0 | bool on_boundary = (dim == 2); |
904 | 0 | if (dim == 1) { |
905 | 0 | on_boundary = PolylineVertexIsBoundaryPoint(shape, edge, vertidx); |
906 | 0 | } |
907 | |
|
908 | 0 | TouchType typea = Options::kTouchInterior; |
909 | 0 | if (testpnt.on_boundary) { |
910 | 0 | typea = Options::kTouchBoundary; |
911 | 0 | } |
912 | |
|
913 | 0 | TouchType typeb = Options::kTouchInterior; |
914 | 0 | if (on_boundary) { |
915 | 0 | typeb = Options::kTouchBoundary; |
916 | 0 | } |
917 | |
|
918 | 0 | using TouchPair = std::pair<TouchType, TouchType>; |
919 | 0 | TouchPair allowed = options().allowed_touches(testpnt.dim, dim); |
920 | | |
921 | | // Returns true if both touches match the given allowed touches. |
922 | 0 | const auto PermittedTouches = [](TouchPair allowed, TouchType typea, |
923 | 0 | TouchType typeb) { |
924 | 0 | return (allowed.first & typea) && (allowed.second & typeb); |
925 | 0 | }; |
926 | | |
927 | | // We require that touches be symmetric, so that the touch is invalid |
928 | | // only if it's invalid from A->B and B->A. This lets us support |
929 | | // behavior such as requiring that one or the other point be an interior |
930 | | // point. |
931 | 0 | if (!PermittedTouches(allowed, typea, typeb) && |
932 | 0 | !PermittedTouches(allowed, typeb, typea)) { |
933 | 0 | *error = S2Error(S2Error::OVERLAPPING_GEOMETRY, |
934 | 0 | "Index has geometry with invalid vertex touches."); |
935 | 0 | return false; |
936 | 0 | } |
937 | 0 | } |
938 | 0 | } |
939 | 0 | } |
940 | 0 | return true; |
941 | 0 | } |
942 | | |
943 | | template <typename IndexType> |
944 | 0 | bool S2ValidQuery<IndexType>::StartCell(S2Error* error) { |
945 | 0 | if (!CheckForDuplicateEdges(error) || !CheckForInteriorCrossings(error)) { |
946 | 0 | return false; |
947 | 0 | } |
948 | | |
949 | 0 | if (!CheckTouchesAreValid(error)) { |
950 | 0 | return false; |
951 | 0 | } |
952 | | |
953 | 0 | return true; |
954 | 0 | } |
955 | | |
956 | | template <typename IndexType> |
957 | | bool S2ValidQuery<IndexType>::PointContained(S2CellId cell_id, int shape_id, |
958 | | const S2Point& point, |
959 | 0 | S2Error* error) { |
960 | 0 | const S2IndexCellData& cell = CurrentCell(); |
961 | 0 | for (const S2ClippedShape& clipped : cell.clipped_shapes()) { |
962 | 0 | if (clipped.shape_id() == shape_id) { |
963 | 0 | continue; |
964 | 0 | } |
965 | | |
966 | 0 | const S2Shape& shape = *Index().shape(clipped.shape_id()); |
967 | | |
968 | | // Only check for containment in polygons. |
969 | 0 | if (shape.dimension() != 2) { |
970 | 0 | continue; |
971 | 0 | } |
972 | | |
973 | 0 | if (cell.ShapeContains(clipped, point)) { |
974 | 0 | *error = S2Error( |
975 | 0 | S2Error::OVERLAPPING_GEOMETRY, |
976 | 0 | absl::StrFormat( |
977 | 0 | "Shape %d has one or more edges contained in another shape.", |
978 | 0 | shape_id)); |
979 | 0 | return true; |
980 | 0 | } |
981 | 0 | } |
982 | | |
983 | 0 | return false; |
984 | 0 | } |
985 | | |
986 | | template <typename IndexType> |
987 | | bool S2ValidQuery<IndexType>::CheckChainOrientation(Iterator& iter, |
988 | | const S2Shape& shape, |
989 | | int shape_id, int chain_id, |
990 | 0 | S2Error* error) { |
991 | 0 | const S2Shape::Chain& chain = shape.chain(chain_id); |
992 | | |
993 | | // Given that: |
994 | | // 1. Edges in the chain are connected continuously. |
995 | | // 2. The chain is closed. |
996 | | // 3. The chain at least two distinct points. |
997 | | // |
998 | | // Then we can test whether the chain is oriented properly relative to the |
999 | | // cell center by testing one edge of the chain for proper orientation. |
1000 | |
|
1001 | 0 | S2ContainsVertexQuery query; |
1002 | 0 | for (int offset = 0; offset < chain.length; ++offset) { |
1003 | 0 | const S2Point& vertex = shape.chain_edge(chain_id, offset).v0; |
1004 | 0 | query.Init(vertex); |
1005 | | |
1006 | | // Seek to the cell containing vertex and get the clipped shape. |
1007 | 0 | if (!iter.Locate(vertex)) { |
1008 | 0 | *error = S2Error::DataLoss("Shape vertex was not indexed"); |
1009 | 0 | return false; |
1010 | 0 | } |
1011 | 0 | const S2Point& center = iter.id().ToPoint(); |
1012 | |
|
1013 | 0 | const S2ClippedShape* clipped = iter.cell().find_clipped(shape_id); |
1014 | 0 | ABSL_DCHECK_NE(clipped, nullptr); |
1015 | | |
1016 | | // Compute winding number and vertex sign at the same time. |
1017 | 0 | int winding = clipped->contains_center(); |
1018 | 0 | S2CopyingEdgeCrosser crosser(center, vertex); |
1019 | |
|
1020 | 0 | for (int i = 0; i < clipped->num_edges(); ++i) { |
1021 | 0 | const S2Shape::Edge& edge = shape.edge(clipped->edge(i)); |
1022 | | |
1023 | | // Tally up the total change in winding number from center to vertex. |
1024 | 0 | winding += crosser.SignedEdgeOrVertexCrossing(edge.v0, edge.v1); |
1025 | | |
1026 | | // Include any edges incident on vertex in the contains vertex query. |
1027 | 0 | if (edge.IncidentOn(vertex)) { |
1028 | 0 | if (vertex == edge.v0) { |
1029 | 0 | query.AddEdge(edge.v1, +1); |
1030 | 0 | } else { |
1031 | 0 | query.AddEdge(edge.v0, -1); |
1032 | 0 | } |
1033 | 0 | } |
1034 | 0 | } |
1035 | |
|
1036 | 0 | bool duplicates = query.DuplicateEdges(); |
1037 | 0 | int sign = 0; |
1038 | | |
1039 | | // If we have a sign of zero on the vertex, all the edges incident on it |
1040 | | // were reverse duplicates and we can't use it to test orientation, continue |
1041 | | // trying to find another vertex. |
1042 | 0 | if (!duplicates) { |
1043 | 0 | sign = query.ContainsSign(); |
1044 | 0 | if (sign == 0) { |
1045 | 0 | continue; |
1046 | 0 | } |
1047 | 0 | } |
1048 | | |
1049 | | // The sign bit obtained by crossing edges should be consistent with the |
1050 | | // sign produced by the S2ContainsVertexQuery. |
1051 | 0 | if (duplicates || winding != (sign < 0 ? 0 : 1)) { |
1052 | 0 | *error = S2Error( |
1053 | 0 | S2Error::POLYGON_INCONSISTENT_LOOP_ORIENTATIONS, |
1054 | 0 | absl::StrFormat( |
1055 | 0 | "Shape %d has one or more edges with interior on the right.", |
1056 | 0 | shape_id)); |
1057 | 0 | return false; |
1058 | 0 | } |
1059 | 0 | return true; |
1060 | 0 | } |
1061 | 0 | return true; |
1062 | 0 | } |
1063 | | |
1064 | | template <typename IndexType> |
1065 | | bool S2ValidQuery<IndexType>::CheckEdge(const S2Shape& shape, |
1066 | | const S2ClippedShape& clipped, |
1067 | | const EdgeAndIdChain& edge, |
1068 | 0 | S2Error* error) { |
1069 | 0 | const S2IndexCellData& cell = CurrentCell(); |
1070 | 0 | const int dim = shape.dimension(); |
1071 | | |
1072 | | // For points, we can check that they're not contained in any other polygons |
1073 | | // locally within the cell by crossing edges to the cell center. |
1074 | 0 | if (dim == 0 && |
1075 | 0 | PointContained(cell.id(), clipped.shape_id(), edge.v0, error)) { |
1076 | 0 | return false; |
1077 | 0 | } |
1078 | | |
1079 | | // Edge is OK |
1080 | 0 | return true; |
1081 | 0 | } |
1082 | | |
1083 | | // Checks that edges of the given shape incident on vertex are ordered such that |
1084 | | // the incident chains do not cross. |
1085 | | // |
1086 | | // We can check this by looking at all the incident edges and making sure that, |
1087 | | // for each incoming edge, as we move counter-clockwise around the vertex, we |
1088 | | // encounter matching pairs of incoming/outgoing edges for each chain. |
1089 | | // |
1090 | | // Returns true if chains do not cross at the vertex, false otherwise. |
1091 | | inline bool CheckVertexCrossings(const S2Point& vertex, const S2Shape& shape, |
1092 | | int shape_id, |
1093 | | const absl::flat_hash_set<int32_t>& edge_ids, |
1094 | 0 | S2Error* error) { |
1095 | | // Extend S2Shape::Edge to wrap an edge with its chain, id and previous id. |
1096 | 0 | struct EdgeWithInfo : public S2Shape::Edge { |
1097 | 0 | EdgeWithInfo(S2Shape::Edge edge, int id, int chain, int prev, int sign) |
1098 | 0 | : S2Shape::Edge(edge), id(id), chain(chain), prev(prev), sign(sign) {} |
1099 | |
|
1100 | 0 | int id; |
1101 | 0 | int chain; |
1102 | 0 | int prev; |
1103 | 0 | int sign; // +1 for incoming and -1 for outgoing edges. |
1104 | 0 | }; |
1105 | | |
1106 | | // Aggregate edges of the current shape and sort them CCW around the vertex. |
1107 | 0 | absl::InlinedVector<EdgeWithInfo, 6> edges; |
1108 | 0 | for (int32_t edge_id : edge_ids) { |
1109 | 0 | S2Shape::ChainPosition pos = shape.chain_position(edge_id); |
1110 | 0 | const S2Shape::Edge& edge = shape.edge(edge_id); |
1111 | 0 | edges.push_back({ |
1112 | 0 | edge, // |
1113 | 0 | edge_id, // |
1114 | 0 | pos.chain_id, // |
1115 | 0 | s2shapeutil::PrevEdgeWrap(shape, edge_id), // |
1116 | 0 | edge.v0 == vertex ? -1 : +1, // |
1117 | 0 | }); |
1118 | 0 | } |
1119 | 0 | SortEdgesCcw(vertex, edges[0], edges); |
1120 | | |
1121 | | // Mapping from chain_id => count. We'll scan through and sum the signs on |
1122 | | // the edges for each chain. When we reach the incoming edge the sums should |
1123 | | // be zero for every chain. |
1124 | 0 | absl::flat_hash_map<int, int> chain_sums; |
1125 | 0 | chain_sums.reserve(16); |
1126 | |
|
1127 | 0 | for (size_t i = 0; i < edges.size(); ++i) { |
1128 | 0 | const EdgeWithInfo& curr = edges[i]; |
1129 | | |
1130 | | // Skip forward to next outgoing edge. |
1131 | 0 | if (curr.sign > 0) { |
1132 | 0 | continue; |
1133 | 0 | } |
1134 | | |
1135 | | // Scan until we find our incoming edge and tally chain counts. |
1136 | 0 | chain_sums.clear(); |
1137 | 0 | size_t j; |
1138 | 0 | for (j = 1; j < edges.size(); ++j) { |
1139 | 0 | const EdgeWithInfo& edge = edges[(i + j) % edges.size()]; |
1140 | 0 | if (curr.chain == edge.chain && curr.prev == edge.id) { |
1141 | 0 | for (const auto& sum : chain_sums) { |
1142 | 0 | if (sum.second != 0) { |
1143 | 0 | *error = S2Error( |
1144 | 0 | S2Error::OVERLAPPING_GEOMETRY, |
1145 | 0 | absl::StrFormat( |
1146 | 0 | "Shape %d has one or more chains that cross at a vertex", |
1147 | 0 | shape_id)); |
1148 | 0 | return false; |
1149 | 0 | } |
1150 | 0 | } |
1151 | 0 | break; |
1152 | 0 | } |
1153 | | |
1154 | 0 | chain_sums[edge.chain] += edge.sign; |
1155 | 0 | } |
1156 | | |
1157 | | // If we went all the way around and didn't find an incoming edge, then |
1158 | | // the geometry must be malformed, and thus isn't valid. |
1159 | 0 | if (j == edges.size()) { |
1160 | 0 | *error = S2Error(S2Error::INVALID_VERTEX, |
1161 | 0 | "Outgoing edge with no incoming edge"); |
1162 | 0 | return false; |
1163 | 0 | } |
1164 | 0 | } |
1165 | | |
1166 | 0 | return true; |
1167 | 0 | } |
1168 | | |
1169 | | template <typename IndexType> |
1170 | 0 | bool S2ValidQuery<IndexType>::Finish(S2Error* error) { |
1171 | | // We've checked edges having interiors on the right, and for crossings at |
1172 | | // interior points. The only case left is to check for chains that cross at a |
1173 | | // vertex. |
1174 | 0 | for (const auto& item : IncidentEdges()) { |
1175 | 0 | const S2Shape& shape = *Index().shape(item.first.shape_id); |
1176 | 0 | if (shape.dimension() == 2) { |
1177 | 0 | if (!CheckVertexCrossings(item.first.vertex, shape, item.first.shape_id, |
1178 | 0 | item.second, error)) { |
1179 | 0 | return false; |
1180 | 0 | } |
1181 | 0 | } |
1182 | 0 | } |
1183 | | |
1184 | | // If we get to this point we know that polygon edges don't cross any other |
1185 | | // edges and that edges are properly oriented with the interior on the left. |
1186 | | // |
1187 | | // Since edges don't cross, any given chain must be entirely inside or outside |
1188 | | // any other polygons. Thus, to determine that polygon interiors are |
1189 | | // disjoint, we only have to check one vertex of each chain in each shape for |
1190 | | // containment. |
1191 | | // |
1192 | | // We use the OPEN containment model because we check elsewhere if the vertex |
1193 | | // lands on another vertex. |
1194 | 0 | S2ContainsPointQueryOptions options; |
1195 | 0 | options.set_vertex_model(S2VertexModel::OPEN); |
1196 | |
|
1197 | 0 | S2ContainsPointQuery<IndexType> query(&Index(), options); |
1198 | 0 | for (int shape_id = 0; shape_id < Index().num_shape_ids(); ++shape_id) { |
1199 | 0 | const S2Shape* shape = Index().shape(shape_id); |
1200 | 0 | if (shape == nullptr || shape->dimension() == 0) { |
1201 | 0 | continue; |
1202 | 0 | } |
1203 | | |
1204 | 0 | for (int chain = 0; chain < shape->num_chains(); ++chain) { |
1205 | 0 | if (shape->chain(chain).length < 1) { |
1206 | 0 | continue; |
1207 | 0 | } |
1208 | | |
1209 | 0 | S2Point vertex = shape->chain_edge(chain, 0).v0; |
1210 | 0 | if (query.Contains(vertex)) { |
1211 | 0 | *error = S2Error( |
1212 | 0 | S2Error::OVERLAPPING_GEOMETRY, |
1213 | 0 | absl::StrFormat( |
1214 | 0 | "Shape %d has one or more edges contained in another shape.", |
1215 | 0 | shape_id)); |
1216 | 0 | return false; |
1217 | 0 | } |
1218 | 0 | } |
1219 | 0 | } |
1220 | 0 | return true; |
1221 | 0 | } |
1222 | | |
1223 | | ////////////////// S2LegacyValidQuery Implementation //////////////////// |
1224 | | |
1225 | | template <typename IndexType> |
1226 | 0 | S2LegacyValidQuery<IndexType>::S2LegacyValidQuery() : Base() { |
1227 | | // Don't allow degenerate or reverse duplicates edges for legacy semantics. |
1228 | 0 | Base::mutable_options().set_allow_degenerate_edges(false); |
1229 | 0 | Base::mutable_options().set_allow_reverse_duplicates(false); |
1230 | 0 | } |
1231 | | |
1232 | | template <typename IndexType> |
1233 | 0 | bool S2LegacyValidQuery<IndexType>::Start(S2Error* error) { |
1234 | 0 | if (!Base::Start(error)) { |
1235 | 0 | return false; |
1236 | 0 | } |
1237 | | |
1238 | | // We can't mix dimensions under legacy semantics. |
1239 | 0 | int dim = -1; |
1240 | 0 | for (const S2Shape* shape : Index()) { |
1241 | 0 | if (dim < 0) { |
1242 | 0 | dim = shape->dimension(); |
1243 | 0 | } |
1244 | |
|
1245 | 0 | if (dim != shape->dimension()) { |
1246 | 0 | *error = S2Error( |
1247 | 0 | S2Error::INVALID_DIMENSION, |
1248 | 0 | "Mixed dimensional geometry is invalid for legacy semantics."); |
1249 | 0 | return false; |
1250 | 0 | } |
1251 | 0 | } |
1252 | | |
1253 | 0 | return true; |
1254 | 0 | } |
1255 | | |
1256 | | template <typename IndexType> |
1257 | | bool S2LegacyValidQuery<IndexType>::CheckShape(Iterator& iter, |
1258 | | const S2Shape& shape, |
1259 | 0 | int shape_id, S2Error* error) { |
1260 | | // Count the number of empty chains. Non-empty chains must have at least |
1261 | | // three vertices. |
1262 | 0 | if (shape.dimension() == 2) { |
1263 | 0 | bool has_empty_loops = false; |
1264 | 0 | for (const S2Shape::Chain& chain : shape.chains()) { |
1265 | 0 | if (chain.length == 0) { |
1266 | 0 | has_empty_loops = true; |
1267 | 0 | } else if (chain.length < 3) { |
1268 | 0 | *error = S2Error( |
1269 | 0 | S2Error::LOOP_NOT_ENOUGH_VERTICES, |
1270 | 0 | absl::StrFormat( |
1271 | 0 | "Shape %d has a non-empty chain with less than three edges.", |
1272 | 0 | shape_id)); |
1273 | 0 | return false; |
1274 | 0 | } |
1275 | 0 | } |
1276 | | |
1277 | 0 | if (has_empty_loops && shape.num_chains() > 1) { |
1278 | 0 | *error = S2Error( |
1279 | 0 | S2Error::POLYGON_EMPTY_LOOP, |
1280 | 0 | absl::StrFormat("Shape %d has too many empty chains", shape_id)); |
1281 | 0 | return false; |
1282 | 0 | } |
1283 | 0 | } |
1284 | | |
1285 | 0 | if (!Base::CheckShape(iter, shape, shape_id, error)) { |
1286 | 0 | return false; |
1287 | 0 | } |
1288 | | |
1289 | 0 | return true; |
1290 | 0 | } |
1291 | | |
1292 | | template <typename IndexType> |
1293 | 0 | bool S2LegacyValidQuery<IndexType>::StartCell(S2Error* error) { |
1294 | | // Check for duplicate vertices within a chain. |
1295 | 0 | const S2IndexCellData& cell = CurrentCell(); |
1296 | 0 | for (const S2ClippedShape& clipped : cell.clipped_shapes()) { |
1297 | 0 | absl::Span<const EdgeAndIdChain> edges = |
1298 | 0 | cell.shape_edges(clipped.shape_id()); |
1299 | |
|
1300 | 0 | for (size_t i = 0; i < edges.size(); ++i) { |
1301 | 0 | for (size_t j = i + 1; j < edges.size(); ++j) { |
1302 | 0 | if (edges[j].chain != edges[i].chain) { |
1303 | 0 | continue; |
1304 | 0 | } |
1305 | | |
1306 | 0 | if (edges[j].v0 == edges[i].v0) { |
1307 | 0 | *error = S2Error( |
1308 | 0 | S2Error::DUPLICATE_VERTICES, |
1309 | 0 | absl::StrFormat("Chain %d of shape %d has duplicate vertices", |
1310 | 0 | edges[i].chain, clipped.shape_id())); |
1311 | 0 | return false; |
1312 | 0 | } |
1313 | 0 | } |
1314 | 0 | } |
1315 | 0 | } |
1316 | | |
1317 | 0 | return Base::StartCell(error); |
1318 | 0 | } |
1319 | | |
1320 | | template <typename IndexType> |
1321 | | bool S2LegacyValidQuery<IndexType>::CheckEdge(const S2Shape& shape, |
1322 | | const S2ClippedShape& clipped, |
1323 | | const EdgeAndIdChain& edge, |
1324 | 0 | S2Error* error) { |
1325 | 0 | if (!Base::CheckEdge(shape, clipped, edge, error)) { |
1326 | 0 | return false; |
1327 | 0 | } |
1328 | 0 | return true; |
1329 | 0 | } |
1330 | | |
1331 | | #endif // S2_S2VALIDATION_QUERY_H_ |