package ReverseGeoLookup import ( "encoding/json" "errors" "fmt" "io" "io/ioutil" "net/http" "strings" "time" ) const ( timeout = time.Minute * 10 maxHttpRespBytes = 8 * 1024 * 1024 addressCache = "knownAddresses.json" marshalIndent = " " sleepBetweenLookup = time.Second ) type Location struct { Latitude float64 Longitude float64 } type Address struct { Street string House string Town string Error error } func (l *Location) values() (float64, float64) { if l == nil { return 0, 0 } return l.Latitude, l.Longitude } func (l *Location) isEmpty() bool { if l == nil { return true } return l.Latitude == 0 || l.Longitude == 0 } var knownAddresses map[string]Address func (l *Location) cachedAddress() (Address, bool) { if l == nil { return Address{}, false } val, ok := knownAddresses[l.String()] return val, ok } func (l *Location) fetchAddress() Address { if l == nil { return Address{} } if cachedAddress, hasKey := l.cachedAddress(); hasKey { return cachedAddress } content, err := NominatimRequest(l) if err == nil && len(content) > 0 { var d Nominatim err = json.Unmarshal(content, &d) if err == nil { address := d.Address.simpleAddress() knownAddresses[l.String()] = address return address } } return Address{Error: errors.New("fetchAddress: " + err.Error())} } func (l *Location) String() string { if l == nil { return "" } return fmt.Sprintf("%v, %v", l.Latitude, l.Longitude) } func fetchAddresses(locations []Location) map[Location]Address { addresses := make(map[Location]Address) for i, location := range locations { addresses[location] = location.fetchAddress() if i < len(locations)-1 { time.Sleep(sleepBetweenLookup) // TODO: unnötig bei gecacheten } } return addresses } func (a Address) fields() []string { return []string{a.Street, a.House, a.Town} } func (a Address) isValid() bool { if a.Error != nil { return false } for _, field := range a.fields() { if field != "" { return true } } return false } func (a Address) String() string { s := "" for _, field := range a.fields() { if field != "" { s += " " + field } } return strings.TrimSpace(s) } /* ----- Nominatim ----- */ func NominatimRequest(l *Location) ([]byte, error) { lat, lon := l.values() return httpRequest(fmt.Sprintf("http://nominatim.openstreetmap.org/reverse?format=jsonv2&addressdetails=1&lat=%v&lon=%v", lat, lon)) } type Nominatim struct { Address NominatimAddress } type NominatimAddress struct { House_Number string Path string Footway string Pedestrian string Road string Suburb string Village string Town string City string } func first(values []string) string { for _, v := range values { if v != "" { return v } } return "" } func (a *NominatimAddress) simpleAddress() Address { if a == nil { return Address{} } street := a.getStreet() town := a.getTown() house := a.House_Number return Address{street, house, town, nil} } func (a NominatimAddress) getStreet() string { prio := []string{a.Path, a.Footway, a.Pedestrian, a.Road} return first(prio) } func (a NominatimAddress) getTown() string { prio := []string{a.Village, a.Suburb, a.Town, a.City} return first(prio) } /* ----- End Nominatim ----- */ func httpRequest(address string) ([]byte, error) { resp, err := http.Get(address) if err != nil { return nil, err } defer resp.Body.Close() content, err := ioutil.ReadAll(io.LimitReader(resp.Body, maxHttpRespBytes)) return content, err } func loadKnownAddresses() { knownAddresses = make(map[string]Address, 0) data, err := ioutil.ReadFile(addressCache) if err == nil { err = json.Unmarshal(data, &knownAddresses) if err != nil { knownAddresses = make(map[string]Address, 0) } } } func saveKnownAddresses() error { data, err := json.MarshalIndent(knownAddresses, "", marshalIndent) if err == nil { ioutil.WriteFile(addressCache, []byte(data), 0644) } else { err = errors.New("saveKnownAddresses: " + err.Error()) } return err } func GetAddresses(locations []Location) (map[Location]Address, []error) { errs := make([]error, 0) loadKnownAddresses() addresses := fetchAddresses(locations) for _, addr := range addresses { if addr.Error != nil { errs = append(errs, addr.Error) } } err := saveKnownAddresses() if err != nil { errs = append(errs, err) } return addresses, errs }