package domain import ( "context" "encoding/json" "fmt" "io" "math" "net/http" "time" ) // Location represents a location with coordinates type Location struct { Name string `json:"name"` Latitude float64 `json:"lat"` Longitude float64 `json:"lon"` } // PersonLocation represents a person's location type PersonLocation struct { Name string `json:"name"` Surname string `json:"surname"` Latitude float64 `json:"lat"` Longitude float64 `json:"lon"` } // DistanceResult represents the distance calculation result type DistanceResult struct { Person string `json:"person"` Location string `json:"location"` DistanceKm float64 `json:"distance_km"` } // Haversine calculates the distance between two points on Earth (in kilometers) // using the Haversine formula func Haversine(lat1, lon1, lat2, lon2 float64) float64 { const earthRadiusKm = 6371.0 // Convert degrees to radians lat1Rad := lat1 * math.Pi / 180 lat2Rad := lat2 * math.Pi / 180 deltaLat := (lat2 - lat1) * math.Pi / 180 deltaLon := (lon2 - lon1) * math.Pi / 180 // Haversine formula a := math.Sin(deltaLat/2)*math.Sin(deltaLat/2) + math.Cos(lat1Rad)*math.Cos(lat2Rad)* math.Sin(deltaLon/2)*math.Sin(deltaLon/2) c := 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a)) return earthRadiusKm * c } // CalculateDistance calculates distance between person and location func CalculateDistance(person PersonLocation, location Location) DistanceResult { distance := Haversine(person.Latitude, person.Longitude, location.Latitude, location.Longitude) return DistanceResult{ Person: person.Name + " " + person.Surname, Location: location.Name, DistanceKm: distance, } } // FindClosestLocation finds the closest location to a person func FindClosestLocation(person PersonLocation, locations []Location) *DistanceResult { if len(locations) == 0 { return nil } var closest *DistanceResult minDistance := math.MaxFloat64 for _, loc := range locations { result := CalculateDistance(person, loc) if result.DistanceKm < minDistance { minDistance = result.DistanceKm closest = &result } } return closest } // NominatimResponse represents OpenStreetMap Nominatim API response type NominatimResponse struct { Lat string `json:"lat"` Lon string `json:"lon"` } // GetPlantGeolocation fetches accurate coordinates for a city using geocoding API func GetPlantGeolocation(ctx context.Context, cityName string) (float64, float64, error) { url := fmt.Sprintf("https://nominatim.openstreetmap.org/search?city=%s&country=Poland&format=json&limit=1", cityName) req, err := http.NewRequestWithContext(ctx, "GET", url, nil) if err != nil { return 0, 0, fmt.Errorf("creating request: %w", err) } // OpenStreetMap requires User-Agent header req.Header.Set("User-Agent", "PersonProcessor/1.0") client := &http.Client{ Timeout: 10 * time.Second, } resp, err := client.Do(req) if err != nil { return 0, 0, fmt.Errorf("making request: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return 0, 0, fmt.Errorf("reading response: %w", err) } var results []NominatimResponse if err := json.Unmarshal(body, &results); err != nil { return 0, 0, fmt.Errorf("parsing JSON: %w", err) } if len(results) == 0 { return 0, 0, fmt.Errorf("no results found for city: %s", cityName) } // Parse coordinates var lat, lon float64 if _, err := fmt.Sscanf(results[0].Lat, "%f", &lat); err != nil { return 0, 0, fmt.Errorf("parsing latitude: %w", err) } if _, err := fmt.Sscanf(results[0].Lon, "%f", &lon); err != nil { return 0, 0, fmt.Errorf("parsing longitude: %w", err) } return lat, lon, nil } // NearestPointResult represents result of finding nearest point type NearestPointResult struct { Latitude float64 `json:"latitude"` Longitude float64 `json:"longitude"` DistanceKm float64 `json:"distance_km"` Index int `json:"index"` } // FindNearestPoint finds the nearest point from a list to a reference point func FindNearestPoint(referenceLat, referenceLon float64, points [][]float64) *NearestPointResult { if len(points) == 0 { return nil } var nearest *NearestPointResult minDistance := math.MaxFloat64 for i, point := range points { if len(point) < 2 { continue // Skip invalid points } lat := point[0] lon := point[1] distance := Haversine(referenceLat, referenceLon, lat, lon) if distance < minDistance { minDistance = distance nearest = &NearestPointResult{ Latitude: lat, Longitude: lon, DistanceKm: distance, Index: i, } } } return nearest }