Coverage Report

Created: 2026-10-10 06:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/s2geometry/src/s2/internal/s2incident_edge_tracker.h
Line
Count
Source
1
// Copyright 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
#ifndef S2_INTERNAL_S2INCIDENT_EDGE_TRACKER_H_
17
#define S2_INTERNAL_S2INCIDENT_EDGE_TRACKER_H_
18
19
#include <algorithm>
20
#include <cstdint>
21
#include <utility>
22
#include <vector>
23
24
#include "absl/container/btree_map.h"
25
#include "absl/container/flat_hash_set.h"
26
#include "s2/_fp_contract_off.h"  // IWYU pragma: keep
27
#include "s2/s2point.h"
28
#include "s2/s2shape.h"
29
30
namespace internal {
31
32
// A tuple of (shape id, vertex) that compares by shape id.
33
struct IncidentEdgeKey {
34
  IncidentEdgeKey() = default;
35
36
  explicit IncidentEdgeKey(int shape_id)
37
0
      : shape_id(shape_id) { /* Leave vertex undefined */
38
0
  }
39
40
  IncidentEdgeKey(int shape_id, S2Point vertex)
41
0
      : shape_id(shape_id), vertex(vertex) {}
42
43
  int32_t shape_id;
44
  S2Point vertex;
45
46
  // We need a strict ordering to be a valid key for an ordered container, but
47
  // we don't actually care about the ordering of the vertices (as long as
48
  // they're grouped by shape id).  Vertices are 3D points so they don't have a
49
  // natural ordering, so we'll just compare them lexicographically.
50
0
  bool operator<(const IncidentEdgeKey& b) const {
51
0
    if (shape_id < b.shape_id) return true;
52
0
    if (shape_id > b.shape_id) return false;
53
0
    for (int i = 0; i < 3; ++i) {
54
0
      if (vertex[i] < b.vertex[i]) return true;
55
0
      if (vertex[i] > b.vertex[i]) return false;
56
0
    }
57
0
    return false;  // Must be equal.
58
0
  }
59
};
60
61
// Map from keys of (shape id, vertex) to a set of edge_ids.  We can and do
62
// encounter the same edges multiple times, so we need to use a set to
63
// deduplicate edges as they're inserted.
64
using IncidentEdgeSet =
65
    absl::btree_map<IncidentEdgeKey, absl::flat_hash_set<int32_t>>;
66
67
// A class for detecting and tracking shape edges that are incident on the same
68
// vertex.  Edges of multiple shapes may be tracked, but lookup is by shape id
69
// and vertex: there is no facility to get all edges of all shapes at a vertex.
70
// Edge vertices must compare exactly equal to be considered the same vertex, no
71
// tolerance is applied as this isn't intended for e.g.: snapping shapes
72
// together, which S2Builder does better and more robustly.
73
//
74
// To use, instantiate and then add edges with one or more sequences of calls,
75
// where each sequence begins with StartShape(), followed by AddEdge() calls to
76
// add edges for that shape, and ends with FinishShape().  Those sequences do
77
// not need to visit shapes or edges in order. Then, call incidentEdges() to get
78
// the resulting map from IncidentEdgeKeys (which are shapeId, vertex pairs) to
79
// a set of edgeIds of the shape that are incident to that vertex..
80
//
81
// This works on a block of edges at a time, meaning that to detect incident
82
// edges on a particular vertex, we must have at least three edges incident
83
// at that vertex when FinishShape() is called. We don't maintain partial
84
// information between calls.  However, subject to this constraint, a single
85
// shape's edges may be defined with multiple sequences of StartShape(),
86
// AddEdge()... , FinishShape() calls.
87
//
88
// The reason for this is simple: most edges don't have more than two incident
89
// edges (the incoming and outgoing edge). If we had to maintain information
90
// between calls, we'd end up with a map that contains every vertex, to no
91
// benefit.  Instead, when FinishShape() is called, we discard vertices that
92
// contain two or fewer incident edges.
93
//
94
// In principle this isn't a real limitation because generally we process an
95
// S2ShapeIndex cell at a time, and, if a vertex has multiple edges, we'll see
96
// all the edges in the same cell as the vertex, and, in general, it's possible
97
// to aggregate edges before calling.
98
//
99
// The tracker maintains incident edges until it's cleared.  If you call it with
100
// each cell from an S2ShapeIndex, then at the end you will have all the
101
// incident edge information for the whole index.  If only a subset is needed,
102
// call Reset() when you're done.
103
class S2IncidentEdgeTracker {
104
 public:
105
  // Add edges to the edge tracker.  After calling, any vertices with multiple
106
  // (> 2) incident edges will appear in the incident edge map.
107
  void StartShape(int shape_id);
108
  void AddEdge(int edge_id, const S2Shape::Edge& edge);
109
  void FinishShape();
110
111
  // Clear any accumulated state.
112
0
  void Reset() { incident_edge_map_.clear(); }
113
114
  // Returns a const reference to the incident edge map.
115
0
  const IncidentEdgeSet& IncidentEdges() const { return incident_edge_map_; }
116
117
 private:
118
  // Scratch space to temporarily store incident edges in.  Having a separate
119
  // space for these lets us remove vertices with only one incident edge
120
  // easily.
121
  struct VertexEdge {
122
    S2Point vertex;
123
    int32_t edge_id;
124
  };
125
  std::vector<VertexEdge> nursery_;
126
127
  int current_shape_ = -1;
128
129
  // Actual incident edge map.
130
  IncidentEdgeSet incident_edge_map_;
131
};
132
133
}  // namespace internal
134
135
#endif  // S2_INTERNAL_S2INCIDENT_EDGE_TRACKER_H_