222 lines
4.4 KiB
Go
222 lines
4.4 KiB
Go
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
|
|
}
|