From dabd0ae7007794eaa89c7b3fde17f383df2396f8 Mon Sep 17 00:00:00 2001 From: gutmet Date: Wed, 5 Apr 2023 22:21:20 +0200 Subject: [PATCH] Initial --- ReverseGeoLookup.go | 221 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 221 insertions(+) create mode 100644 ReverseGeoLookup.go diff --git a/ReverseGeoLookup.go b/ReverseGeoLookup.go new file mode 100644 index 0000000..3360851 --- /dev/null +++ b/ReverseGeoLookup.go @@ -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 +}