-
Notifications
You must be signed in to change notification settings - Fork 182
/
Copy pathcontains_point_query.go
190 lines (169 loc) · 6.18 KB
/
contains_point_query.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
// Copyright 2018 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package s2
// VertexModel defines whether shapes are considered to contain their vertices.
// Note that these definitions differ from the ones used by BooleanOperation.
//
// Note that points other than vertices are never contained by polylines.
// If you want need this behavior, use ClosestEdgeQuery's IsDistanceLess
// with a suitable distance threshold instead.
type VertexModel int
const (
// VertexModelOpen means no shapes contain their vertices (not even
// points). Therefore Contains(Point) returns true if and only if the
// point is in the interior of some polygon.
VertexModelOpen VertexModel = iota
// VertexModelSemiOpen means that polygon point containment is defined
// such that if several polygons tile the region around a vertex, then
// exactly one of those polygons contains that vertex. Points and
// polylines still do not contain any vertices.
VertexModelSemiOpen
// VertexModelClosed means all shapes contain their vertices (including
// points and polylines).
VertexModelClosed
)
// ContainsPointQuery determines whether one or more shapes in a ShapeIndex
// contain a given Point. The ShapeIndex may contain any number of points,
// polylines, and/or polygons (possibly overlapping). Shape boundaries may be
// modeled as Open, SemiOpen, or Closed (this affects whether or not shapes are
// considered to contain their vertices).
//
// This type is not safe for concurrent use.
//
// However, note that if you need to do a large number of point containment
// tests, it is more efficient to re-use the query rather than creating a new
// one each time.
type ContainsPointQuery struct {
model VertexModel
index *ShapeIndex
iter *ShapeIndexIterator
}
// NewContainsPointQuery creates a new instance of the ContainsPointQuery for the index
// and given vertex model choice.
func NewContainsPointQuery(index *ShapeIndex, model VertexModel) *ContainsPointQuery {
return &ContainsPointQuery{
index: index,
model: model,
iter: index.Iterator(),
}
}
// Contains reports whether any shape in the queries index contains the point p
// under the queries vertex model (Open, SemiOpen, or Closed).
func (q *ContainsPointQuery) Contains(p Point) bool {
if !q.iter.LocatePoint(p) {
return false
}
cell := q.iter.IndexCell()
for _, clipped := range cell.shapes {
if q.shapeContains(clipped, q.iter.Center(), p) {
return true
}
}
return false
}
// shapeContains reports whether the clippedShape from the iterator's center position contains
// the given point.
func (q *ContainsPointQuery) shapeContains(clipped *clippedShape, center, p Point) bool {
inside := clipped.containsCenter
numEdges := clipped.numEdges()
if numEdges <= 0 {
return inside
}
shape := q.index.Shape(clipped.shapeID)
if shape.Dimension() != 2 {
// Points and polylines can be ignored unless the vertex model is Closed.
if q.model != VertexModelClosed {
return false
}
// Otherwise, the point is contained if and only if it matches a vertex.
for _, edgeID := range clipped.edges {
edge := shape.Edge(edgeID)
if edge.V0 == p || edge.V1 == p {
return true
}
}
return false
}
// Test containment by drawing a line segment from the cell center to the
// given point and counting edge crossings.
crosser := NewEdgeCrosser(center, p)
for _, edgeID := range clipped.edges {
edge := shape.Edge(edgeID)
sign := crosser.CrossingSign(edge.V0, edge.V1)
if sign == DoNotCross {
continue
}
if sign == MaybeCross {
// For the Open and Closed models, check whether p is a vertex.
if q.model != VertexModelSemiOpen && (edge.V0 == p || edge.V1 == p) {
return (q.model == VertexModelClosed)
}
// C++ plays fast and loose with the int <-> bool conversions here.
if VertexCrossing(crosser.a, crosser.b, edge.V0, edge.V1) {
sign = Cross
} else {
sign = DoNotCross
}
}
inside = inside != (sign == Cross)
}
return inside
}
// ShapeContains reports whether the given shape contains the point under this
// queries vertex model (Open, SemiOpen, or Closed).
//
// This requires the shape belongs to this queries index.
func (q *ContainsPointQuery) ShapeContains(shape Shape, p Point) bool {
if !q.iter.LocatePoint(p) {
return false
}
clipped := q.iter.IndexCell().findByShapeID(q.index.idForShape(shape))
if clipped == nil {
return false
}
return q.shapeContains(clipped, q.iter.Center(), p)
}
// shapeVisitorFunc is a type of function that can be called against shaped in an index.
type shapeVisitorFunc func(shape Shape) bool
// visitContainingShapes visits all shapes in the given index that contain the
// given point p, terminating early if the given visitor function returns false,
// in which case visitContainingShapes returns false. Each shape is
// visited at most once.
func (q *ContainsPointQuery) visitContainingShapes(p Point, f shapeVisitorFunc) bool {
// This function returns false only if the algorithm terminates early
// because the visitor function returned false.
if !q.iter.LocatePoint(p) {
return true
}
cell := q.iter.IndexCell()
for _, clipped := range cell.shapes {
if q.shapeContains(clipped, q.iter.Center(), p) &&
!f(q.index.Shape(clipped.shapeID)) {
return false
}
}
return true
}
// ContainingShapes returns a slice of all shapes that contain the given point.
func (q *ContainsPointQuery) ContainingShapes(p Point) []Shape {
var shapes []Shape
q.visitContainingShapes(p, func(shape Shape) bool {
shapes = append(shapes, shape)
return true
})
return shapes
}
// TODO(roberts): Remaining methods from C++
// type edgeVisitorFunc func(shape ShapeEdge) bool
// func (q *ContainsPointQuery) visitIncidentEdges(p Point, v edgeVisitorFunc) bool