final
This commit is contained in:
180
internal/domain/location.go
Normal file
180
internal/domain/location.go
Normal file
@@ -0,0 +1,180 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user