NAM-APJATEL-BACKEND/utils/service/geo_service.go

102 lines
2.7 KiB
Go

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
}