1use serde::{Deserialize, Serialize};
7
8#[derive(Debug)]
10pub enum GeoJsonError {
11 Serialization(String),
13 Deserialization(String),
15 InvalidGeometry(String),
17 InvalidCoordinates(String),
19}
20
21impl std::fmt::Display for GeoJsonError {
22 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23 match self {
24 Self::Serialization(msg) => write!(f, "GeoJSON serialization error: {}", msg),
25 Self::Deserialization(msg) => write!(f, "GeoJSON deserialization error: {}", msg),
26 Self::InvalidGeometry(msg) => write!(f, "Invalid GeoJSON geometry: {}", msg),
27 Self::InvalidCoordinates(msg) => write!(f, "Invalid GeoJSON coordinates: {}", msg),
28 }
29 }
30}
31
32impl std::error::Error for GeoJsonError {}
33
34#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
49pub struct Point {
50 inner: geo::Point<f64>,
51}
52
53impl Point {
54 #[inline]
69 pub fn new(x: f64, y: f64) -> Self {
70 Self {
71 inner: geo::Point::new(x, y),
72 }
73 }
74
75 #[inline]
77 pub fn x(&self) -> f64 {
78 self.inner.x()
79 }
80
81 #[inline]
83 pub fn y(&self) -> f64 {
84 self.inner.y()
85 }
86
87 #[inline]
89 pub fn lon(&self) -> f64 {
90 self.x()
91 }
92
93 #[inline]
95 pub fn lat(&self) -> f64 {
96 self.y()
97 }
98
99 #[inline]
101 pub fn inner(&self) -> &geo::Point<f64> {
102 &self.inner
103 }
104
105 #[inline]
107 pub fn into_inner(self) -> geo::Point<f64> {
108 self.inner
109 }
110
111 #[inline]
126 pub fn haversine_distance(&self, other: &Point) -> f64 {
127 use geo::Distance;
128 geo::Haversine.distance(self.inner, other.inner)
129 }
130
131 #[inline]
146 pub fn geodesic_distance(&self, other: &Point) -> f64 {
147 use geo::Distance;
148 geo::Geodesic.distance(self.inner, other.inner)
149 }
150
151 #[inline]
167 pub fn euclidean_distance(&self, other: &Point) -> f64 {
168 use geo::Distance;
169 geo::Euclidean.distance(self.inner, other.inner)
170 }
171
172 #[cfg(feature = "geojson")]
187 pub fn to_geojson(&self) -> Result<String, GeoJsonError> {
188 use geojson::{Geometry, Value};
189
190 let geom = Geometry::new(Value::Point(vec![self.x(), self.y()]));
191 serde_json::to_string(&geom)
192 .map_err(|e| GeoJsonError::Serialization(format!("Failed to serialize point: {}", e)))
193 }
194
195 #[cfg(feature = "geojson")]
210 pub fn from_geojson(geojson: &str) -> Result<Self, GeoJsonError> {
211 use geojson::{Geometry, Value};
212
213 let geom: Geometry = serde_json::from_str(geojson).map_err(|e| {
214 GeoJsonError::Deserialization(format!("Failed to parse GeoJSON: {}", e))
215 })?;
216
217 match geom.value {
218 Value::Point(coords) => {
219 if coords.len() < 2 {
220 return Err(GeoJsonError::InvalidCoordinates(
221 "Point must have at least 2 coordinates".to_string(),
222 ));
223 }
224 Ok(Point::new(coords[0], coords[1]))
225 }
226 _ => Err(GeoJsonError::InvalidGeometry(
227 "GeoJSON geometry is not a Point".to_string(),
228 )),
229 }
230 }
231}
232
233impl From<geo::Point<f64>> for Point {
234 fn from(point: geo::Point<f64>) -> Self {
235 Self { inner: point }
236 }
237}
238
239impl From<Point> for geo::Point<f64> {
240 fn from(point: Point) -> Self {
241 point.inner
242 }
243}
244
245impl From<(f64, f64)> for Point {
246 fn from((x, y): (f64, f64)) -> Self {
247 Self::new(x, y)
248 }
249}
250
251impl From<Point> for (f64, f64) {
252 fn from(point: Point) -> Self {
253 (point.x(), point.y())
254 }
255}
256
257#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
278pub struct Polygon {
279 inner: geo::Polygon<f64>,
280}
281
282impl Polygon {
283 pub fn new(exterior: geo::LineString<f64>, interiors: Vec<geo::LineString<f64>>) -> Self {
290 Self {
291 inner: geo::Polygon::new(exterior, interiors),
292 }
293 }
294
295 #[inline]
297 pub fn exterior(&self) -> &geo::LineString<f64> {
298 self.inner.exterior()
299 }
300
301 #[inline]
303 pub fn interiors(&self) -> &[geo::LineString<f64>] {
304 self.inner.interiors()
305 }
306
307 #[inline]
309 pub fn inner(&self) -> &geo::Polygon<f64> {
310 &self.inner
311 }
312
313 #[inline]
315 pub fn into_inner(self) -> geo::Polygon<f64> {
316 self.inner
317 }
318
319 #[inline]
339 pub fn contains(&self, point: &Point) -> bool {
340 use geo::Contains;
341 self.inner.contains(&point.inner)
342 }
343
344 #[cfg(feature = "geojson")]
367 pub fn to_geojson(&self) -> Result<String, GeoJsonError> {
368 use geojson::{Geometry, Value};
369
370 let mut rings = Vec::new();
371
372 let exterior: Vec<Vec<f64>> = self
373 .exterior()
374 .coords()
375 .map(|coord| vec![coord.x, coord.y])
376 .collect();
377 rings.push(exterior);
378
379 for interior in self.interiors() {
380 let ring: Vec<Vec<f64>> = interior
381 .coords()
382 .map(|coord| vec![coord.x, coord.y])
383 .collect();
384 rings.push(ring);
385 }
386
387 let geom = Geometry::new(Value::Polygon(rings));
388
389 serde_json::to_string(&geom)
390 .map_err(|e| GeoJsonError::Serialization(format!("Failed to serialize polygon: {}", e)))
391 }
392
393 #[cfg(feature = "geojson")]
408 pub fn from_geojson(geojson: &str) -> Result<Self, GeoJsonError> {
409 use geojson::{Geometry, Value};
410
411 let geom: Geometry = serde_json::from_str(geojson).map_err(|e| {
412 GeoJsonError::Deserialization(format!("Failed to parse GeoJSON: {}", e))
413 })?;
414
415 match geom.value {
416 Value::Polygon(rings) => {
417 if rings.is_empty() {
418 return Err(GeoJsonError::InvalidCoordinates(
419 "Polygon must have at least one ring".to_string(),
420 ));
421 }
422
423 let exterior: Result<Vec<geo::Coord>, GeoJsonError> = rings[0]
424 .iter()
425 .map(|coords| {
426 if coords.len() < 2 {
427 return Err(GeoJsonError::InvalidCoordinates(
428 "Coordinate must have at least 2 values".to_string(),
429 ));
430 }
431 Ok(geo::Coord {
432 x: coords[0],
433 y: coords[1],
434 })
435 })
436 .collect();
437
438 let exterior_coords = exterior?;
439 let exterior_line = geo::LineString::from(exterior_coords);
440
441 let mut interiors = Vec::new();
442 for ring in rings.iter().skip(1) {
443 let interior: Result<Vec<geo::Coord>, GeoJsonError> = ring
444 .iter()
445 .map(|coords| {
446 if coords.len() < 2 {
447 return Err(GeoJsonError::InvalidCoordinates(
448 "Coordinate must have at least 2 values".to_string(),
449 ));
450 }
451 Ok(geo::Coord {
452 x: coords[0],
453 y: coords[1],
454 })
455 })
456 .collect();
457 let interior_coords = interior?;
458 interiors.push(geo::LineString::from(interior_coords));
459 }
460
461 Ok(Polygon::new(exterior_line, interiors))
462 }
463 _ => Err(GeoJsonError::InvalidGeometry(
464 "GeoJSON geometry is not a Polygon".to_string(),
465 )),
466 }
467 }
468}
469
470impl From<geo::Polygon<f64>> for Polygon {
471 fn from(polygon: geo::Polygon<f64>) -> Self {
472 Self { inner: polygon }
473 }
474}
475
476impl From<Polygon> for geo::Polygon<f64> {
477 fn from(polygon: Polygon) -> Self {
478 polygon.inner
479 }
480}
481
482#[cfg(test)]
483mod tests {
484 use super::*;
485
486 #[test]
487 fn test_point_creation() {
488 let point = Point::new(-74.0060, 40.7128);
489 assert_eq!(point.x(), -74.0060);
490 assert_eq!(point.y(), 40.7128);
491 assert_eq!(point.lon(), -74.0060);
492 assert_eq!(point.lat(), 40.7128);
493 }
494
495 #[test]
496 fn test_point_from_tuple() {
497 let point: Point = (-74.0060, 40.7128).into();
498 assert_eq!(point.x(), -74.0060);
499 assert_eq!(point.y(), 40.7128);
500 }
501
502 #[test]
503 fn test_point_to_tuple() {
504 let point = Point::new(-74.0060, 40.7128);
505 let (x, y): (f64, f64) = point.into();
506 assert_eq!(x, -74.0060);
507 assert_eq!(y, 40.7128);
508 }
509
510 #[test]
511 fn test_point_haversine_distance() {
512 let nyc = Point::new(-74.0060, 40.7128);
513 let la = Point::new(-118.2437, 34.0522);
514 let distance = nyc.haversine_distance(&la);
515 assert!(distance > 3_900_000.0 && distance < 4_000_000.0);
517 }
518
519 #[test]
520 fn test_point_euclidean_distance() {
521 let p1 = Point::new(0.0, 0.0);
522 let p2 = Point::new(3.0, 4.0);
523 let distance = p1.euclidean_distance(&p2);
524 assert_eq!(distance, 5.0);
525 }
526
527 #[test]
528 fn test_polygon_creation() {
529 use geo::polygon;
530
531 let poly = polygon![
532 (x: -80.0, y: 35.0),
533 (x: -70.0, y: 35.0),
534 (x: -70.0, y: 45.0),
535 (x: -80.0, y: 45.0),
536 (x: -80.0, y: 35.0),
537 ];
538 let polygon = Polygon::from(poly);
539 assert_eq!(polygon.exterior().coords().count(), 5);
540 assert_eq!(polygon.interiors().len(), 0);
541 }
542
543 #[test]
544 fn test_polygon_contains() {
545 use geo::polygon;
546
547 let poly = polygon![
548 (x: -80.0, y: 35.0),
549 (x: -70.0, y: 35.0),
550 (x: -70.0, y: 45.0),
551 (x: -80.0, y: 45.0),
552 (x: -80.0, y: 35.0),
553 ];
554 let polygon = Polygon::from(poly);
555
556 let inside = Point::new(-75.0, 40.0);
557 let outside = Point::new(-85.0, 40.0);
558
559 assert!(polygon.contains(&inside));
560 assert!(!polygon.contains(&outside));
561 }
562
563 #[cfg(feature = "geojson")]
564 #[test]
565 fn test_point_geojson_roundtrip() {
566 let original = Point::new(-74.0060, 40.7128);
567 let json = original.to_geojson().unwrap();
568 let parsed = Point::from_geojson(&json).unwrap();
569
570 assert!((original.x() - parsed.x()).abs() < 1e-10);
571 assert!((original.y() - parsed.y()).abs() < 1e-10);
572 }
573
574 #[cfg(feature = "geojson")]
575 #[test]
576 fn test_polygon_geojson_roundtrip() {
577 use geo::polygon;
578
579 let poly = polygon![
580 (x: -80.0, y: 35.0),
581 (x: -70.0, y: 35.0),
582 (x: -70.0, y: 45.0),
583 (x: -80.0, y: 45.0),
584 (x: -80.0, y: 35.0),
585 ];
586 let original = Polygon::from(poly);
587 let json = original.to_geojson().unwrap();
588 let parsed = Polygon::from_geojson(&json).unwrap();
589
590 assert_eq!(
591 original.exterior().coords().count(),
592 parsed.exterior().coords().count()
593 );
594 }
595}