1use anyhow::Result;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Coordinate {
11 pub latitude: f64,
12 pub longitude: f64,
13}
14
15pub struct GeospatialCalculator;
17
18impl GeospatialCalculator {
19 pub fn new() -> Self {
20 Self
21 }
22
23 pub fn distance(&self, from: &Coordinate, to: &Coordinate) -> f64 {
26 const EARTH_RADIUS_KM: f64 = 6371.0;
27
28 let lat1_rad = from.latitude.to_radians();
29 let lat2_rad = to.latitude.to_radians();
30 let delta_lat = (to.latitude - from.latitude).to_radians();
31 let delta_lon = (to.longitude - from.longitude).to_radians();
32
33 let a = (delta_lat / 2.0).sin().powi(2)
34 + lat1_rad.cos() * lat2_rad.cos() * (delta_lon / 2.0).sin().powi(2);
35 let c = 2.0 * a.sqrt().asin();
36
37 EARTH_RADIUS_KM * c
38 }
39
40 pub fn bearing(&self, from: &Coordinate, to: &Coordinate) -> f64 {
43 let lat1_rad = from.latitude.to_radians();
44 let lat2_rad = to.latitude.to_radians();
45 let delta_lon = (to.longitude - from.longitude).to_radians();
46
47 let y = delta_lon.sin() * lat2_rad.cos();
48 let x = lat1_rad.cos() * lat2_rad.sin() - lat1_rad.sin() * lat2_rad.cos() * delta_lon.cos();
49
50 let bearing_rad = y.atan2(x);
51 let bearing_deg = bearing_rad.to_degrees();
52
53 (bearing_deg + 360.0) % 360.0
54 }
55
56 pub fn parse_coordinate(&self, coord_str: &str) -> Result<Coordinate> {
58 let parts: Vec<&str> = coord_str.split(',').map(|s| s.trim()).collect();
59 if parts.len() != 2 {
60 anyhow::bail!(
61 "Invalid coordinate format. Expected 'lat,lon', got: {}",
62 coord_str
63 );
64 }
65
66 let lat = parts[0]
67 .parse::<f64>()
68 .map_err(|e| anyhow::anyhow!("Invalid latitude: {}", e))?;
69 let lon = parts[1]
70 .parse::<f64>()
71 .map_err(|e| anyhow::anyhow!("Invalid longitude: {}", e))?;
72
73 if lat < -90.0 || lat > 90.0 {
74 anyhow::bail!("Latitude must be between -90 and 90, got: {}", lat);
75 }
76 if lon < -180.0 || lon > 180.0 {
77 anyhow::bail!("Longitude must be between -180 and 180, got: {}", lon);
78 }
79
80 Ok(Coordinate {
81 latitude: lat,
82 longitude: lon,
83 })
84 }
85
86 pub fn distance_from_strings(&self, from_str: &str, to_str: &str) -> Result<f64> {
88 let from = self.parse_coordinate(from_str)?;
89 let to = self.parse_coordinate(to_str)?;
90 Ok(self.distance(&from, &to))
91 }
92}