-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgeocode.go
139 lines (121 loc) · 2.98 KB
/
geocode.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
package housing
import (
"bufio"
"encoding/json"
"fmt"
"net/url"
"os"
"time"
"github.com/pkg/errors"
"housing/util"
)
type GeocodeNoResultsError struct {
addr string
}
func (e *GeocodeNoResultsError) Error() string {
return fmt.Sprintf("no geocoding results for %s", e.addr)
}
type latlngprecision struct {
lat float64
lng float64
precision float64 // meters
}
type Geocoder struct {
APIKey string
PrecisionMeters float64
cache map[string]latlngprecision
}
func NewGeocoder(apiKey string, precision float64) *Geocoder {
g := Geocoder{
APIKey: apiKey,
PrecisionMeters: precision,
cache: make(map[string]latlngprecision),
}
return &g
}
func (g *Geocoder) PopulateCache(fname string) error {
f, err := os.Open(fname)
if err != nil {
return errors.Wrap(err, fmt.Sprintf("os.Open %s", fname))
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
b := []byte(scanner.Text())
ga := struct {
Addr string
Lat float64
Lng float64
Precision float64
}{}
if err := json.Unmarshal(b, &ga); err != nil {
return errors.Wrap(err, fmt.Sprintf("%s", b))
}
g.cache[ga.Addr] = latlngprecision{lat: ga.Lat, lng: ga.Lng, precision: ga.Precision}
}
if err := scanner.Err(); err != nil {
return err
}
return nil
}
func (g *Geocoder) GeocodeWithRetry(addr string) (float64, float64, error) {
var geocodeErr error
numRetries := 5
for i := 0; i < numRetries; i++ {
lat, lng, err := g.Geocode(addr)
if err == nil {
return lat, lng, nil
}
if gnrErr, ok := err.(*GeocodeNoResultsError); ok {
return -1, -1, gnrErr
}
geocodeErr = err
if i < numRetries-1 {
<-time.After(time.Duration(i) * time.Second)
}
}
return -1, -1, errors.Wrap(geocodeErr, "reversegeocode")
}
func (g *Geocoder) Geocode(addr string) (float64, float64, error) {
llp, ok := g.cache[addr]
if ok && llp.precision < g.PrecisionMeters {
return llp.lat, llp.lng, nil
}
lat, lng, err := g.geocode(addr)
if err != nil {
return -1, -1, err
}
g.cache[addr] = latlngprecision{lat: lat, lng: lng, precision: 0}
return lat, lng, nil
}
func (g *Geocoder) geocode(addr string) (float64, float64, error) {
v := url.Values{
"key": {g.APIKey},
"address": {addr},
}
urlStr := "https://maps.googleapis.com/maps/api/geocode/json?" + v.Encode()
resp := struct {
Results []struct {
Geometry struct {
Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
} `json:"location"`
} `json:"geometry"`
} `json:"results"`
Status string `json:"status"`
}{}
_, respBody, err := util.JSONReq3("GET", urlStr, &resp)
if err != nil {
return -1, -1, errors.Wrap(err, "JSONReq3")
}
if resp.Status != "OK" {
if resp.Status == "ZERO_RESULTS" {
return -1, -1, &GeocodeNoResultsError{addr: addr}
}
return -1, -1, fmt.Errorf("google geo code: %s", respBody)
}
lat := resp.Results[0].Geometry.Location.Lat
lng := resp.Results[0].Geometry.Location.Lng
return lat, lng, nil
}