-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgeo.go
209 lines (187 loc) · 5.38 KB
/
geo.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
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Contributor:
// - Julien Vehent [email protected]
// - Aaron Meihm [email protected]
//
// This code is directly based off work done by Julien Vehent in the
// geolog project. See https://github.com/jvehent/geolog.
package main
import (
"fmt"
geo "github.com/oschwald/geoip2-golang"
"math"
"net"
)
var maxmind *geo.Reader
func maxmindInit() (err error) {
maxmind, err = geo.Open(cfg.General.MaxMind)
if err != nil {
return err
}
logf("initialized maxmind db")
return nil
}
func geoObjectResult(o *objectResult) (err error) {
defer func() {
if e := recover(); e != nil {
err = fmt.Errorf("geoObjectResult() -> %v", e)
}
}()
ip := net.ParseIP(o.SourceIPV4)
record, err := maxmind.City(ip)
if err != nil {
panic(err)
}
o.Latitude = record.Location.Latitude
o.Longitude = record.Location.Longitude
cityName := record.City.Names["en"]
countryName := record.Country.Names["en"]
if cityName == "" {
cityName = "Unknown"
}
if countryName == "" {
countryName = "Unknown"
}
// Check if the ip is part of our custom overrides
for _, override := range cfg.overrides {
_, subnet, _ := net.ParseCIDR(override.cidr)
if subnet.Contains(ip) {
cityName = override.city
countryName = override.country
o.Latitude = override.latitude
o.Longitude = override.longitude
break
}
}
o.Locality.City = cityName
o.Locality.Country = countryName
o.Weight = 1
return nil
}
// Collapse branches in the object based on proximity to tres; the number of
// branches collapsed during the operation is returned
func geoCollapseUsing(o *object, tres objectResult) float64 {
var ret float64
for i := range o.Results {
p0 := &o.Results[i]
if p0.BranchID == tres.BranchID {
continue
}
la1 := tres.Latitude
la2 := p0.Latitude
lo1 := tres.Longitude
lo2 := p0.Longitude
dist := kmBetweenTwoPoints(la1, lo1, la2, lo2)
if dist > float64(cfg.Geo.CollapseMaximum) {
continue
}
p0.Collapsed = true
p0.CollapseBranch = tres.BranchID
// If any of the objects we are linking have been escalated,
// mark the other as escalated as well.
if tres.Escalated {
p0.Escalated = true
} else if p0.Escalated {
tres.Escalated = true
}
ret++
}
return ret
}
func geoFlatten(o *object) (err error) {
for i := range o.Results {
o.Results[i].Collapsed = false
o.Results[i].CollapseBranch = ""
o.Results[i].Weight = 1
}
return nil
}
func geoCollapse(o *object) (err error) {
for i := range o.Results {
// If a node has already been collapsed, don't look at it again
if o.Results[i].Collapsed {
continue
}
o.Results[i].Weight += geoCollapseUsing(o, o.Results[i])
}
o.NumCenters = 0
for _, x := range o.Results {
if !x.Collapsed {
o.NumCenters++
}
}
return nil
}
func geoFindGeocenter(o object) (gc objectGeocenter, err error) {
var lat, lonGw, lonDl float64
// First pass: calculate two geocenters: one on the greenwich meridian
// and one of the dateline meridian
for _, loc := range o.Results {
lat += (loc.Latitude * loc.Weight)
lonGw += (loc.Longitude * loc.Weight)
lonDl += (switchMeridians(loc.Longitude) * loc.Weight)
gc.Weight += loc.Weight
}
lat /= gc.Weight
lonGw /= gc.Weight
lonDl /= gc.Weight
lonDl = switchMeridians(lonDl)
// Second pass: calculate the distance of each location to the greenwich
// meridian and the dateline meridian. The average distance that is the
// shortest indicates which meridian is appropriate to use.
var distToGw, avgDistToGw, distToDl, avgDistToDl float64
for _, loc := range o.Results {
distToGw = kmBetweenTwoPoints(loc.Latitude, loc.Longitude, lat, lonGw)
avgDistToGw += (distToGw * loc.Weight)
distToDl = kmBetweenTwoPoints(loc.Latitude, loc.Longitude, lat, lonDl)
avgDistToDl += (distToDl * loc.Weight)
}
avgDistToGw /= gc.Weight
avgDistToDl /= gc.Weight
if avgDistToGw > avgDistToDl {
// average distance to greenwich meridian is longer than average distance
// to dateline meridian, so the dateline meridian is our geocenter
gc.Longitude = lonDl
gc.AvgDist = avgDistToDl
} else {
gc.Longitude = lonGw
gc.AvgDist = avgDistToGw
}
gc.Latitude = lat
return gc, nil
}
// haversin(θ) function
func hsin(theta float64) float64 {
return math.Pow(math.Sin(theta/2), 2)
}
// Distance function returns the distance (in meters) between two points of
// a given longitude and latitude relatively accurately (using a spherical
// approximation of the Earth) through the Haversin Distance Formula for
// great arc distance on a sphere with accuracy for small distances
//
// point coordinates are supplied in degrees and converted into rad. in the func
//
// distance returned is Kilometers
// http://en.wikipedia.org/wiki/Haversine_formula
func kmBetweenTwoPoints(lat1, lon1, lat2, lon2 float64) float64 {
// convert to radians
// must cast radius as float to multiply later
var la1, lo1, la2, lo2, r float64
la1 = lat1 * math.Pi / 180
lo1 = lon1 * math.Pi / 180
la2 = lat2 * math.Pi / 180
lo2 = lon2 * math.Pi / 180
r = 6378 // Earth radius in Kilometers
// calculate
h := hsin(la2-la1) + math.Cos(la1)*math.Cos(la2)*hsin(lo2-lo1)
return 2 * r * math.Asin(math.Sqrt(h))
}
func switchMeridians(lon float64) float64 {
if lon < 0.0 {
return lon + 180.0
}
return lon - 180.0
}