193 lines
5.0 KiB
Go
193 lines
5.0 KiB
Go
// backend\address_reverse.go
|
|
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type reverseAddressSuggestion struct {
|
|
ID string `json:"id"`
|
|
Label string `json:"label"`
|
|
Lat float64 `json:"lat"`
|
|
Lon float64 `json:"lon"`
|
|
Type string `json:"type,omitempty"`
|
|
GeoJSON json.RawMessage `json:"geojson,omitempty"`
|
|
}
|
|
|
|
type nominatimReverseAddress struct {
|
|
HouseNumber string `json:"house_number"`
|
|
Road string `json:"road"`
|
|
Footway string `json:"footway"`
|
|
Pedestrian string `json:"pedestrian"`
|
|
Path string `json:"path"`
|
|
Suburb string `json:"suburb"`
|
|
Quarter string `json:"quarter"`
|
|
CityDistrict string `json:"city_district"`
|
|
Borough string `json:"borough"`
|
|
City string `json:"city"`
|
|
Town string `json:"town"`
|
|
Village string `json:"village"`
|
|
Municipality string `json:"municipality"`
|
|
County string `json:"county"`
|
|
State string `json:"state"`
|
|
Postcode string `json:"postcode"`
|
|
Country string `json:"country"`
|
|
}
|
|
|
|
type nominatimReverseResponse struct {
|
|
PlaceID int64 `json:"place_id"`
|
|
OSMType string `json:"osm_type"`
|
|
OSMID int64 `json:"osm_id"`
|
|
DisplayName string `json:"display_name"`
|
|
Lat string `json:"lat"`
|
|
Lon string `json:"lon"`
|
|
Type string `json:"type"`
|
|
Address nominatimReverseAddress `json:"address"`
|
|
GeoJSON json.RawMessage `json:"geojson,omitempty"`
|
|
}
|
|
|
|
func appendUnique(parts []string, value string) []string {
|
|
value = strings.TrimSpace(value)
|
|
|
|
if value == "" {
|
|
return parts
|
|
}
|
|
|
|
for _, part := range parts {
|
|
if strings.EqualFold(part, value) {
|
|
return parts
|
|
}
|
|
}
|
|
|
|
return append(parts, value)
|
|
}
|
|
|
|
func formatReverseAddressLabel(result nominatimReverseResponse) string {
|
|
address := result.Address
|
|
|
|
street := firstNonEmpty(
|
|
address.Road,
|
|
address.Footway,
|
|
address.Pedestrian,
|
|
address.Path,
|
|
)
|
|
|
|
streetLine := street
|
|
|
|
if street != "" && strings.TrimSpace(address.HouseNumber) != "" {
|
|
streetLine = fmt.Sprintf("%s %s", street, strings.TrimSpace(address.HouseNumber))
|
|
}
|
|
|
|
city := firstNonEmpty(
|
|
address.City,
|
|
address.Town,
|
|
address.Village,
|
|
address.Municipality,
|
|
)
|
|
|
|
postcodeCity := city
|
|
|
|
if strings.TrimSpace(address.Postcode) != "" && city != "" {
|
|
postcodeCity = fmt.Sprintf("%s %s", strings.TrimSpace(address.Postcode), city)
|
|
}
|
|
|
|
parts := []string{}
|
|
|
|
parts = appendUnique(parts, streetLine)
|
|
parts = appendUnique(parts, postcodeCity)
|
|
parts = appendUnique(parts, address.State)
|
|
parts = appendUnique(parts, address.Country)
|
|
|
|
if len(parts) > 0 {
|
|
return strings.Join(parts, ", ")
|
|
}
|
|
|
|
return strings.TrimSpace(result.DisplayName)
|
|
}
|
|
|
|
func (s *Server) handleAddressReverse(w http.ResponseWriter, r *http.Request) {
|
|
latRaw := strings.TrimSpace(r.URL.Query().Get("lat"))
|
|
lonRaw := strings.TrimSpace(r.URL.Query().Get("lon"))
|
|
|
|
lat, err := strconv.ParseFloat(latRaw, 64)
|
|
if err != nil || lat < -90 || lat > 90 {
|
|
writeError(w, http.StatusBadRequest, "Ungültige Latitude")
|
|
return
|
|
}
|
|
|
|
lon, err := strconv.ParseFloat(lonRaw, 64)
|
|
if err != nil || lon < -180 || lon > 180 {
|
|
writeError(w, http.StatusBadRequest, "Ungültige Longitude")
|
|
return
|
|
}
|
|
|
|
params := url.Values{}
|
|
params.Set("format", "jsonv2")
|
|
params.Set("addressdetails", "1")
|
|
params.Set("polygon_geojson", "1")
|
|
params.Set("lat", strconv.FormatFloat(lat, 'f', 7, 64))
|
|
params.Set("lon", strconv.FormatFloat(lon, 'f', 7, 64))
|
|
params.Set("zoom", "18")
|
|
params.Set("accept-language", "de")
|
|
|
|
reverseURL := fmt.Sprintf(
|
|
"https://nominatim.openstreetmap.org/reverse?%s",
|
|
params.Encode(),
|
|
)
|
|
|
|
request, err := http.NewRequestWithContext(r.Context(), http.MethodGet, reverseURL, nil)
|
|
if err != nil {
|
|
writeError(w, http.StatusInternalServerError, "Adresse konnte nicht vorbereitet werden")
|
|
return
|
|
}
|
|
|
|
request.Header.Set("User-Agent", "TEG/1.0 address reverse lookup")
|
|
|
|
client := &http.Client{
|
|
Timeout: 8 * time.Second,
|
|
}
|
|
|
|
response, err := client.Do(request)
|
|
if err != nil {
|
|
writeError(w, http.StatusBadGateway, "Adresse konnte nicht ermittelt werden")
|
|
return
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
|
writeError(w, http.StatusBadGateway, "Adresse konnte nicht ermittelt werden")
|
|
return
|
|
}
|
|
|
|
var result nominatimReverseResponse
|
|
|
|
if err := json.NewDecoder(response.Body).Decode(&result); err != nil {
|
|
writeError(w, http.StatusBadGateway, "Adresse konnte nicht gelesen werden")
|
|
return
|
|
}
|
|
|
|
label := formatReverseAddressLabel(result)
|
|
if label == "" {
|
|
writeError(w, http.StatusNotFound, "Für diese Position wurde keine Adresse gefunden")
|
|
return
|
|
}
|
|
|
|
writeJSON(w, http.StatusOK, map[string]any{
|
|
"suggestion": reverseAddressSuggestion{
|
|
ID: fmt.Sprintf("reverse-%d-%d", result.PlaceID, time.Now().UnixNano()),
|
|
Label: label,
|
|
Lat: lat,
|
|
Lon: lon,
|
|
Type: result.Type,
|
|
GeoJSON: result.GeoJSON,
|
|
},
|
|
})
|
|
}
|