1use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
13pub struct Coord {
14 pub lng: f64,
15 pub lat: f64,
16}
17
18impl Coord {
19 pub fn new(lng: f64, lat: f64) -> Self {
20 Self { lng, lat }
21 }
22}
23
24#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
26#[serde(tag = "type")]
27pub enum Geometry {
28 Point {
29 coordinates: [f64; 2],
30 },
31 LineString {
32 coordinates: Vec<[f64; 2]>,
33 },
34 Polygon {
35 coordinates: Vec<Vec<[f64; 2]>>,
36 },
37 MultiPoint {
38 coordinates: Vec<[f64; 2]>,
39 },
40 MultiLineString {
41 coordinates: Vec<Vec<[f64; 2]>>,
42 },
43 MultiPolygon {
44 coordinates: Vec<Vec<Vec<[f64; 2]>>>,
45 },
46 GeometryCollection {
47 geometries: Vec<Geometry>,
48 },
49}
50
51impl Geometry {
52 pub fn point(lng: f64, lat: f64) -> Self {
54 Geometry::Point {
55 coordinates: [lng, lat],
56 }
57 }
58
59 pub fn line_string(coords: Vec<[f64; 2]>) -> Self {
61 Geometry::LineString {
62 coordinates: coords,
63 }
64 }
65
66 pub fn polygon(rings: Vec<Vec<[f64; 2]>>) -> Self {
71 Geometry::Polygon { coordinates: rings }
72 }
73
74 pub fn geometry_type(&self) -> &'static str {
76 match self {
77 Geometry::Point { .. } => "Point",
78 Geometry::LineString { .. } => "LineString",
79 Geometry::Polygon { .. } => "Polygon",
80 Geometry::MultiPoint { .. } => "MultiPoint",
81 Geometry::MultiLineString { .. } => "MultiLineString",
82 Geometry::MultiPolygon { .. } => "MultiPolygon",
83 Geometry::GeometryCollection { .. } => "GeometryCollection",
84 }
85 }
86
87 pub fn centroid(&self) -> Option<[f64; 2]> {
89 match self {
90 Geometry::Point { coordinates } => Some(*coordinates),
91 Geometry::LineString { coordinates } => {
92 if coordinates.is_empty() {
93 return None;
94 }
95 let n = coordinates.len() as f64;
96 let sum_lng: f64 = coordinates.iter().map(|c| c[0]).sum();
97 let sum_lat: f64 = coordinates.iter().map(|c| c[1]).sum();
98 Some([sum_lng / n, sum_lat / n])
99 }
100 Geometry::Polygon { coordinates } => {
101 coordinates.first().and_then(|ring| {
103 if ring.is_empty() {
104 return None;
105 }
106 let n = ring.len() as f64;
107 let sum_lng: f64 = ring.iter().map(|c| c[0]).sum();
108 let sum_lat: f64 = ring.iter().map(|c| c[1]).sum();
109 Some([sum_lng / n, sum_lat / n])
110 })
111 }
112 Geometry::MultiPoint { coordinates } => {
113 if coordinates.is_empty() {
114 return None;
115 }
116 let n = coordinates.len() as f64;
117 let sum_lng: f64 = coordinates.iter().map(|c| c[0]).sum();
118 let sum_lat: f64 = coordinates.iter().map(|c| c[1]).sum();
119 Some([sum_lng / n, sum_lat / n])
120 }
121 _ => None,
122 }
123 }
124}
125
126const EARTH_RADIUS_M: f64 = 6_371_000.0;
129
130pub fn haversine_distance(lng1: f64, lat1: f64, lng2: f64, lat2: f64) -> f64 {
134 let lat1_r = lat1.to_radians();
135 let lat2_r = lat2.to_radians();
136 let dlat = (lat2 - lat1).to_radians();
137 let dlng = (lng2 - lng1).to_radians();
138
139 let a = (dlat / 2.0).sin().powi(2) + lat1_r.cos() * lat2_r.cos() * (dlng / 2.0).sin().powi(2);
140 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
141 EARTH_RADIUS_M * c
142}
143
144pub fn haversine_bearing(lng1: f64, lat1: f64, lng2: f64, lat2: f64) -> f64 {
146 let lat1_r = lat1.to_radians();
147 let lat2_r = lat2.to_radians();
148 let dlng = (lng2 - lng1).to_radians();
149
150 let y = dlng.sin() * lat2_r.cos();
151 let x = lat1_r.cos() * lat2_r.sin() - lat1_r.sin() * lat2_r.cos() * dlng.cos();
152 let bearing = y.atan2(x).to_degrees();
153 (bearing + 360.0) % 360.0
154}
155
156pub fn polygon_area(ring: &[[f64; 2]]) -> f64 {
161 if ring.len() < 3 {
162 return 0.0;
163 }
164 let mut sum = 0.0;
165 let n = ring.len();
166 for i in 0..n {
167 let j = (i + 1) % n;
168 let lat_avg = ((ring[i][1] + ring[j][1]) / 2.0).to_radians();
170 let x1 = ring[i][0].to_radians() * EARTH_RADIUS_M * lat_avg.cos();
171 let y1 = ring[i][1].to_radians() * EARTH_RADIUS_M;
172 let x2 = ring[j][0].to_radians() * EARTH_RADIUS_M * lat_avg.cos();
173 let y2 = ring[j][1].to_radians() * EARTH_RADIUS_M;
174 sum += x1 * y2 - x2 * y1;
175 }
176 (sum / 2.0).abs()
177}
178
179pub fn point_in_polygon(lng: f64, lat: f64, ring: &[[f64; 2]]) -> bool {
181 let mut inside = false;
182 let n = ring.len();
183 let mut j = n.wrapping_sub(1);
184 for i in 0..n {
185 let yi = ring[i][1];
186 let yj = ring[j][1];
187 if ((yi > lat) != (yj > lat))
188 && (lng < (ring[j][0] - ring[i][0]) * (lat - yi) / (yj - yi) + ring[i][0])
189 {
190 inside = !inside;
191 }
192 j = i;
193 }
194 inside
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 #[test]
202 fn point_creation() {
203 let p = Geometry::point(-73.9857, 40.7484);
204 assert_eq!(p.geometry_type(), "Point");
205 if let Geometry::Point { coordinates } = &p {
206 assert!((coordinates[0] - (-73.9857)).abs() < 1e-6);
207 assert!((coordinates[1] - 40.7484).abs() < 1e-6);
208 }
209 }
210
211 #[test]
212 fn haversine_nyc_to_london() {
213 let d = haversine_distance(-74.006, 40.7128, -0.1278, 51.5074);
215 assert!((d - 5_570_000.0).abs() < 50_000.0, "got {d}m");
217 }
218
219 #[test]
220 fn haversine_same_point() {
221 let d = haversine_distance(0.0, 0.0, 0.0, 0.0);
222 assert!(d.abs() < 1e-6);
223 }
224
225 #[test]
226 fn bearing_north() {
227 let b = haversine_bearing(0.0, 0.0, 0.0, 1.0);
228 assert!((b - 0.0).abs() < 1.0, "expected ~0, got {b}");
229 }
230
231 #[test]
232 fn bearing_east() {
233 let b = haversine_bearing(0.0, 0.0, 1.0, 0.0);
234 assert!((b - 90.0).abs() < 1.0, "expected ~90, got {b}");
235 }
236
237 #[test]
238 fn polygon_area_simple() {
239 let ring = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0]];
241 let area = polygon_area(&ring);
242 let area_km2 = area / 1e6;
243 assert!(
244 (area_km2 - 12321.0).abs() < 500.0,
245 "expected ~12321 km², got {area_km2}"
246 );
247 }
248
249 #[test]
250 fn point_in_polygon_inside() {
251 let ring = vec![
252 [0.0, 0.0],
253 [10.0, 0.0],
254 [10.0, 10.0],
255 [0.0, 10.0],
256 [0.0, 0.0],
257 ];
258 assert!(point_in_polygon(5.0, 5.0, &ring));
259 assert!(!point_in_polygon(15.0, 5.0, &ring));
260 }
261
262 #[test]
263 fn centroid_point() {
264 let p = Geometry::point(10.0, 20.0);
265 assert_eq!(p.centroid(), Some([10.0, 20.0]));
266 }
267
268 #[test]
269 fn centroid_linestring() {
270 let ls = Geometry::line_string(vec![[0.0, 0.0], [10.0, 0.0], [10.0, 10.0]]);
271 let c = ls.centroid().unwrap();
272 assert!((c[0] - 6.6667).abs() < 0.01);
273 assert!((c[1] - 3.3333).abs() < 0.01);
274 }
275
276 #[test]
277 fn geojson_serialize() {
278 let p = Geometry::point(1.0, 2.0);
279 let json = serde_json::to_string(&p).unwrap();
280 assert!(json.contains("\"type\":\"Point\""));
281 assert!(json.contains("\"coordinates\":[1.0,2.0]"));
282 }
283
284 #[test]
285 fn geojson_roundtrip() {
286 let original = Geometry::polygon(vec![vec![
287 [0.0, 0.0],
288 [1.0, 0.0],
289 [1.0, 1.0],
290 [0.0, 1.0],
291 [0.0, 0.0],
292 ]]);
293 let json = serde_json::to_string(&original).unwrap();
294 let parsed: Geometry = serde_json::from_str(&json).unwrap();
295 assert_eq!(original, parsed);
296 }
297}