Initial
This commit is contained in:
commit
dabd0ae700
221
ReverseGeoLookup.go
Normal file
221
ReverseGeoLookup.go
Normal file
|
@ -0,0 +1,221 @@
|
|||
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
|
||||
}
|
Loading…
Reference in New Issue
Block a user