This commit is contained in:
gutmet 2023-04-05 22:21:20 +02:00
commit dabd0ae700

221
ReverseGeoLookup.go Normal file
View 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
}