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