forked from weilunwu/go-geofence
-
Notifications
You must be signed in to change notification settings - Fork 0
/
geofence.go
165 lines (145 loc) · 4.81 KB
/
geofence.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
package geofence
import "github.com/kellydunn/golang-geo"
// Geofence is a struct for efficient search whether a point is in polygon
type Geofence struct {
vertices []*geo.Point
holes [][]*geo.Point
tiles map[float64]string
granularity int64
minX float64
maxX float64
minY float64
maxY float64
tileWidth float64
tileHeight float64
minTileX float64
maxTileX float64
minTileY float64
maxTileY float64
}
const defaultGranularity = 20
// NewGeofence is the construct for Geofence, vertices: {{(1,2),(2,3)}, {(1,0)}}.
// 1st array contains polygon vertices. 2nd array contains holes.
func NewGeofence(points [][]*geo.Point, args ...interface{}) *Geofence {
geofence := &Geofence{}
if len(args) > 0 {
geofence.granularity = args[0].(int64)
} else {
geofence.granularity = defaultGranularity
}
geofence.vertices = points[0]
if len(points) > 1 {
geofence.holes = points[1:]
}
geofence.tiles = make(map[float64]string)
geofence.setInclusionTiles()
return geofence
}
// Inside checks whether a given point is inside the geofence
func (geofence *Geofence) Inside(point *geo.Point) bool {
// Bbox check first
if point.Lat() < geofence.minX || point.Lat() > geofence.maxX || point.Lng() < geofence.minY || point.Lng() > geofence.maxY {
return false
}
tileHash := (project(point.Lng(), geofence.tileHeight)-geofence.minTileY)*float64(geofence.granularity) + (project(point.Lat(), geofence.tileWidth) - geofence.minTileX)
intersects := geofence.tiles[tileHash]
if intersects == "i" {
return true
} else if intersects == "x" {
polygon := geo.NewPolygon(geofence.vertices)
inside := polygon.Contains(point)
if !inside || len(geofence.holes) == 0 {
return inside
}
// if we hanve holes cut out, and the point falls within the outer ring,
// ensure no inner rings exclude this point
for i := 0; i < len(geofence.holes); i++ {
holePoly := geo.NewPolygon(geofence.holes[i])
if holePoly.Contains(point) {
return false
}
}
return true
} else {
return false
}
}
func (geofence *Geofence) setInclusionTiles() {
xVertices := geofence.getXVertices()
yVertices := geofence.getYVertices()
geofence.minX = getMin(xVertices)
geofence.minY = getMin(yVertices)
geofence.maxX = getMax(xVertices)
geofence.maxY = getMax(yVertices)
xRange := geofence.maxX - geofence.minX
yRange := geofence.maxY - geofence.minY
geofence.tileWidth = xRange / float64(geofence.granularity)
geofence.tileHeight = yRange / float64(geofence.granularity)
geofence.minTileX = project(geofence.minX, geofence.tileWidth)
geofence.minTileY = project(geofence.minY, geofence.tileHeight)
geofence.maxTileX = project(geofence.maxX, geofence.tileWidth)
geofence.maxTileY = project(geofence.maxY, geofence.tileHeight)
geofence.setExclusionTiles(geofence.vertices, true)
if len(geofence.holes) > 0 {
for _, hole := range geofence.holes {
geofence.setExclusionTiles(hole, false)
}
}
}
func (geofence *Geofence) setExclusionTiles(vertices []*geo.Point, inclusive bool) {
var tileHash float64
var bBoxPoly []*geo.Point
for tileX := geofence.minTileX; tileX <= geofence.maxTileX; tileX++ {
for tileY := geofence.minTileY; tileY <= geofence.maxTileY; tileY++ {
tileHash = (tileY-geofence.minTileY)*float64(geofence.granularity) + (tileX - geofence.minTileX)
bBoxPoly = []*geo.Point{geo.NewPoint(tileX*geofence.tileWidth, tileY*geofence.tileHeight), geo.NewPoint((tileX+1)*geofence.tileWidth, tileY*geofence.tileHeight), geo.NewPoint((tileX+1)*geofence.tileWidth, (tileY+1)*geofence.tileHeight), geo.NewPoint(tileX*geofence.tileWidth, (tileY+1)*geofence.tileHeight), geo.NewPoint(tileX*geofence.tileWidth, tileY*geofence.tileHeight)}
if haveIntersectingEdges(bBoxPoly, vertices) || hasPointInPolygon(vertices, bBoxPoly) {
geofence.tiles[tileHash] = "x"
} else if hasPointInPolygon(bBoxPoly, vertices) {
if inclusive {
geofence.tiles[tileHash] = "i"
} else {
geofence.tiles[tileHash] = "o"
}
} // else all points are outside the poly
}
}
}
func (geofence *Geofence) getXVertices() []float64 {
xVertices := make([]float64, len(geofence.vertices))
for i := 0; i < len(geofence.vertices); i++ {
xVertices[i] = geofence.vertices[i].Lat()
}
return xVertices
}
func (geofence *Geofence) getYVertices() []float64 {
yVertices := make([]float64, len(geofence.vertices))
for i := 0; i < len(geofence.vertices); i++ {
yVertices[i] = geofence.vertices[i].Lng()
}
return yVertices
}
func getMin(slice []float64) float64 {
var min float64
if len(slice) > 0 {
min = slice[0]
}
for i := 1; i < len(slice); i++ {
if slice[i] < min {
min = slice[i]
}
}
return min
}
func getMax(slice []float64) float64 {
var max float64
if len(slice) > 0 {
max = slice[0]
}
for i := 1; i < len(slice); i++ {
if slice[i] > max {
max = slice[i]
}
}
return max
}