-
Notifications
You must be signed in to change notification settings - Fork 16
/
within.go
108 lines (100 loc) · 2.43 KB
/
within.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
package geom
import "math"
// Withiner is an interface for types that can be determined to be
// within a polygon or not.
type Withiner interface {
Within(Polygonal) WithinStatus
}
// pointInPolygonal determines whether "pt" is
// within any of the polygons in "pg".
// adapted from https://rosettacode.org/wiki/Ray-casting_algorithm#Go.
// In this version of the algorithm, points that lie on the edge of the polygon
// are considered inside.
func pointInPolygonal(pt Point, pg Polygonal) (in WithinStatus) {
for _, poly := range pg.Polygons() {
pgBounds := poly.ringBounds()
tempIn := pointInPolygon(pt, poly, pgBounds)
if tempIn == OnEdge {
return tempIn
} else if tempIn == Inside {
in = in.invert()
}
}
return in
}
// WithinStatus gives the status of a point relative to a polygon: whether
// it is inside, outside, or on the edge.
type WithinStatus int
// WithinStatus gives the status of a point relative to a polygon: whether
// it is inside, outside, or on the edge.
const (
Outside WithinStatus = iota
Inside
OnEdge
)
func (w WithinStatus) invert() WithinStatus {
if w == Outside {
return Inside
}
return Outside
}
// pointInPolygon determines whether "pt" is
// within "pg".
// adapted from https://rosettacode.org/wiki/Ray-casting_algorithm#Go.
// pgBounds is the bounds of each ring in pg.
func pointInPolygon(pt Point, pg Polygon, pgBounds []*Bounds) (in WithinStatus) {
for i, ring := range pg {
if len(ring) < 3 {
continue
}
if !pgBounds[i].Overlaps(NewBoundsPoint(pt)) {
continue
}
// check segment between beginning and ending points
if !ring[len(ring)-1].Equals(ring[0]) {
if pointOnSegment(pt, ring[len(ring)-1], ring[0]) {
return OnEdge
}
if rayIntersectsSegment(pt, ring[len(ring)-1], ring[0]) {
in = in.invert()
}
}
// check the rest of the segments.
for i := 1; i < len(ring); i++ {
if pointOnSegment(pt, ring[i-1], ring[i]) {
return OnEdge
}
if rayIntersectsSegment(pt, ring[i-1], ring[i]) {
in = in.invert()
}
}
}
return in
}
func rayIntersectsSegment(p, a, b Point) bool {
if a.Y > b.Y {
a, b = b, a
}
for p.Y == a.Y || p.Y == b.Y {
p.Y = math.Nextafter(p.Y, math.Inf(1))
}
if p.Y < a.Y || p.Y > b.Y {
return false
}
if a.X > b.X {
if p.X >= a.X {
return false
}
if p.X < b.X {
return true
}
} else {
if p.X > b.X {
return false
}
if p.X < a.X {
return true
}
}
return (p.Y-a.Y)/(p.X-a.X) >= (b.Y-a.Y)/(b.X-a.X)
}