teg/backend/address_reverse.go
2026-05-28 15:06:37 +02:00

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,
},
})
}