package service import ( "encoding/json" "fmt" "io/ioutil" "log" "net/http" "sync" "time" ) type GeocodingService interface { GetAddressFromCoordinates(latitude, longitude float64) (string, error) } type nominatimGeocoder struct { client *http.Client cache sync.Map // key: "lat,lon" → string address } func NewGeocodingService() GeocodingService { client := &http.Client{ Timeout: 10 * time.Second, } return &nominatimGeocoder{ client: client, } } type NominatimResponse struct { DisplayName string `json:"display_name"` Error string `json:"error"` } func (g *nominatimGeocoder) GetAddressFromCoordinates(latitude, longitude float64) (string, error) { if latitude < -90 || latitude > 90 || longitude < -180 || longitude > 180 { return "", fmt.Errorf("invalid coordinates: lat=%f, lon=%f", latitude, longitude) } // Check in-memory cache first — avoids repeated Nominatim calls for same device cacheKey := fmt.Sprintf("%.6f,%.6f", latitude, longitude) if cached, ok := g.cache.Load(cacheKey); ok { return cached.(string), nil } url := fmt.Sprintf( "https://nominatim.openstreetmap.org/reverse?format=json&lat=%f&lon=%f&zoom=18&addressdetails=1", latitude, longitude, ) req, err := http.NewRequest("GET", url, nil) if err != nil { log.Printf("Request creation error: %v", err) return "", err } req.Header.Set("User-Agent", "AssetManagementApp") resp, err := g.client.Do(req) if err != nil { log.Printf("HTTP request error: %v", err) return "", err } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Printf("Nominatim returned HTTP %d for lat=%f lon=%f", resp.StatusCode, latitude, longitude) return "", fmt.Errorf("geocoding service returned status %d", resp.StatusCode) } bodyBytes, err := ioutil.ReadAll(resp.Body) if err != nil { log.Printf("Error reading response body: %v", err) return "", err } if len(bodyBytes) > 0 && bodyBytes[0] == '<' { log.Printf("Nominatim returned HTML instead of JSON (possible rate limit) for lat=%f lon=%f", latitude, longitude) return "", fmt.Errorf("geocoding service unavailable (rate limited or network error)") } var result NominatimResponse if err := json.Unmarshal(bodyBytes, &result); err != nil { log.Printf("JSON unmarshal error: %v", err) return "", err } if result.Error != "" { log.Printf("Nominatim API Error: %s", result.Error) return "", fmt.Errorf("geocoding error: %s", result.Error) } if result.DisplayName == "" { log.Printf("No address found for coordinates: %f, %f", latitude, longitude) return "", fmt.Errorf("no address found for these coordinates") } // Store in cache for future calls g.cache.Store(cacheKey, result.DisplayName) return result.DisplayName, nil }