181 lines
4.5 KiB
Go
181 lines
4.5 KiB
Go
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
|
|
}
|