Skip to main content

linkrs_core/value/
geography.rs

1//! Geospatial Types Module
2//!
3//! This module defines geographic spatial types and related operations.
4//! Supports Point, LineString, Polygon, MultiPoint, MultiLineString, and MultiPolygon.
5
6use regex::Regex;
7use serde::{Deserialize, Serialize};
8
9/// Geographic Point type (single coordinate pair)
10#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, PartialOrd)]
11pub struct GeographyValue {
12    pub latitude: f64,
13    pub longitude: f64,
14}
15
16impl std::hash::Hash for GeographyValue {
17    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
18        self.latitude.to_bits().hash(state);
19        self.longitude.to_bits().hash(state);
20    }
21}
22
23impl GeographyValue {
24    pub fn new(latitude: f64, longitude: f64) -> Self {
25        GeographyValue {
26            latitude,
27            longitude,
28        }
29    }
30
31    /// Calculate the Haversine distance between two points (unit: kilometers)
32    pub fn distance(&self, other: &GeographyValue) -> f64 {
33        const EARTH_RADIUS_KM: f64 = 6371.0;
34        const DEG_TO_RAD: f64 = std::f64::consts::PI / 180.0;
35
36        let lat1 = self.latitude * DEG_TO_RAD;
37        let lat2 = other.latitude * DEG_TO_RAD;
38        let delta_lat = (other.latitude - self.latitude) * DEG_TO_RAD;
39        let delta_lon = (other.longitude - self.longitude) * DEG_TO_RAD;
40
41        let a = (delta_lat / 2.0).sin().powi(2)
42            + lat1.cos() * lat2.cos() * (delta_lon / 2.0).sin().powi(2);
43        let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
44
45        EARTH_RADIUS_KM * c
46    }
47
48    /// Calculate the azimuth angle between two points (unit: degrees)
49    pub fn bearing(&self, other: &GeographyValue) -> f64 {
50        const DEG_TO_RAD: f64 = std::f64::consts::PI / 180.0;
51        const RAD_TO_DEG: f64 = 180.0 / std::f64::consts::PI;
52
53        let lat1 = self.latitude * DEG_TO_RAD;
54        let lat2 = other.latitude * DEG_TO_RAD;
55        let delta_lon = (other.longitude - self.longitude) * DEG_TO_RAD;
56
57        let y = delta_lon.sin() * lat2.cos();
58        let x = lat1.cos() * lat2.sin() - lat1.sin() * lat2.cos() * delta_lon.cos();
59
60        let bearing = y.atan2(x) * RAD_TO_DEG;
61        (bearing + 360.0) % 360.0
62    }
63
64    /// Check whether the checkpoint is within the specified rectangular area.
65    pub fn in_bbox(&self, min_lat: f64, max_lat: f64, min_lon: f64, max_lon: f64) -> bool {
66        self.latitude >= min_lat
67            && self.latitude <= max_lat
68            && self.longitude >= min_lon
69            && self.longitude <= max_lon
70    }
71
72    /// Check if the point is valid (within coordinate ranges)
73    pub fn is_valid(&self) -> bool {
74        self.latitude >= -90.0
75            && self.latitude <= 90.0
76            && self.longitude >= -180.0
77            && self.longitude <= 180.0
78    }
79}
80
81impl Default for GeographyValue {
82    fn default() -> Self {
83        GeographyValue {
84            latitude: 0.0,
85            longitude: 0.0,
86        }
87    }
88}
89
90impl GeographyValue {
91    /// Estimate the memory usage of the geography value
92    pub fn estimated_size(&self) -> usize {
93        std::mem::size_of::<Self>()
94    }
95}
96
97/// LineString type (ordered sequence of points)
98#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
99pub struct LineStringValue {
100    pub points: Vec<GeographyValue>,
101}
102
103impl LineStringValue {
104    pub fn new(points: Vec<GeographyValue>) -> Self {
105        LineStringValue { points }
106    }
107
108    /// Calculate total length in kilometers
109    pub fn length(&self) -> f64 {
110        self.points.windows(2).map(|w| w[0].distance(&w[1])).sum()
111    }
112
113    /// Get the number of points
114    pub fn num_points(&self) -> usize {
115        self.points.len()
116    }
117
118    /// Check if the linestring is closed (first point equals last)
119    pub fn is_closed(&self) -> bool {
120        if self.points.len() < 2 {
121            return false;
122        }
123        let first = &self.points[0];
124        let last = &self.points[self.points.len() - 1];
125        (first.latitude - last.latitude).abs() < 1e-9
126            && (first.longitude - last.longitude).abs() < 1e-9
127    }
128
129    /// Check if the linestring is a ring (closed and has at least 4 points)
130    pub fn is_ring(&self) -> bool {
131        self.points.len() >= 4 && self.is_closed()
132    }
133
134    /// Calculate centroid (average of all points)
135    pub fn centroid(&self) -> Option<GeographyValue> {
136        if self.points.is_empty() {
137            return None;
138        }
139        let (sum_lat, sum_lon) = self.points.iter().fold((0.0, 0.0), |(lat, lon), p| {
140            (lat + p.latitude, lon + p.longitude)
141        });
142        Some(GeographyValue::new(
143            sum_lat / self.points.len() as f64,
144            sum_lon / self.points.len() as f64,
145        ))
146    }
147
148    /// Get start point
149    pub fn start_point(&self) -> Option<&GeographyValue> {
150        self.points.first()
151    }
152
153    /// Get end point
154    pub fn end_point(&self) -> Option<&GeographyValue> {
155        self.points.last()
156    }
157
158    /// Estimate memory usage
159    pub fn estimated_size(&self) -> usize {
160        std::mem::size_of::<Self>() + self.points.len() * std::mem::size_of::<GeographyValue>()
161    }
162
163    /// Check if all points are valid
164    pub fn is_valid(&self) -> bool {
165        self.points.iter().all(|p| p.is_valid())
166    }
167}
168
169/// Polygon type (closed ring with optional holes)
170#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
171pub struct PolygonValue {
172    pub exterior: LineStringValue,
173    pub holes: Vec<LineStringValue>,
174}
175
176impl PolygonValue {
177    pub fn new(exterior: LineStringValue, holes: Vec<LineStringValue>) -> Self {
178        PolygonValue { exterior, holes }
179    }
180
181    pub fn from_ring(ring: LineStringValue) -> Self {
182        PolygonValue {
183            exterior: ring,
184            holes: Vec::new(),
185        }
186    }
187
188    /// Calculate area in square kilometers (approximate using spherical excess)
189    pub fn area(&self) -> f64 {
190        let exterior_area = self.ring_area(&self.exterior);
191        let holes_area: f64 = self.holes.iter().map(|h| self.ring_area(h)).sum();
192        (exterior_area - holes_area).abs()
193    }
194
195    /// Calculate ring area using spherical excess formula
196    fn ring_area(&self, ring: &LineStringValue) -> f64 {
197        if ring.points.len() < 4 {
198            return 0.0;
199        }
200
201        const EARTH_RADIUS_KM: f64 = 6371.0;
202        const DEG_TO_RAD: f64 = std::f64::consts::PI / 180.0;
203
204        let mut sum = 0.0;
205        let n = ring.points.len() - 1; // Exclude the closing point
206
207        for i in 0..n {
208            let j = (i + 1) % n;
209            let lat_i = ring.points[i].latitude * DEG_TO_RAD;
210            let lon_i = ring.points[i].longitude * DEG_TO_RAD;
211            let lon_j = ring.points[j].longitude * DEG_TO_RAD;
212
213            sum += (lon_j - lon_i) * lat_i.sin();
214        }
215
216        (sum.abs() * EARTH_RADIUS_KM * EARTH_RADIUS_KM) / 2.0
217    }
218
219    /// Calculate perimeter in kilometers
220    pub fn perimeter(&self) -> f64 {
221        let exterior_perimeter = self.exterior.length();
222        let holes_perimeter: f64 = self.holes.iter().map(|h| h.length()).sum();
223        exterior_perimeter + holes_perimeter
224    }
225
226    /// Check if a point is inside the polygon using ray casting algorithm
227    pub fn contains_point(&self, point: &GeographyValue) -> bool {
228        self.point_in_ring(point, &self.exterior)
229            && !self.holes.iter().any(|h| self.point_in_ring(point, h))
230    }
231
232    /// Ray casting algorithm for point-in-ring test
233    fn point_in_ring(&self, point: &GeographyValue, ring: &LineStringValue) -> bool {
234        if ring.points.len() < 4 {
235            return false;
236        }
237
238        let mut inside = false;
239        let n = ring.points.len() - 1;
240
241        for i in 0..n {
242            let j = (i + 1) % n;
243            let pi = &ring.points[i];
244            let pj = &ring.points[j];
245
246            if ((pi.latitude > point.latitude) != (pj.latitude > point.latitude))
247                && (point.longitude
248                    < (pj.longitude - pi.longitude) * (point.latitude - pi.latitude)
249                        / (pj.latitude - pi.latitude)
250                        + pi.longitude)
251            {
252                inside = !inside;
253            }
254        }
255
256        inside
257    }
258
259    /// Calculate centroid
260    pub fn centroid(&self) -> Option<GeographyValue> {
261        self.exterior.centroid()
262    }
263
264    /// Get bounding box as (min_lat, max_lat, min_lon, max_lon)
265    pub fn bounding_box(&self) -> Option<(f64, f64, f64, f64)> {
266        if self.exterior.points.is_empty() {
267            return None;
268        }
269
270        let mut min_lat = f64::MAX;
271        let mut max_lat = f64::MIN;
272        let mut min_lon = f64::MAX;
273        let mut max_lon = f64::MIN;
274
275        for p in &self.exterior.points {
276            min_lat = min_lat.min(p.latitude);
277            max_lat = max_lat.max(p.latitude);
278            min_lon = min_lon.min(p.longitude);
279            max_lon = max_lon.max(p.longitude);
280        }
281
282        Some((min_lat, max_lat, min_lon, max_lon))
283    }
284
285    /// Estimate memory usage
286    pub fn estimated_size(&self) -> usize {
287        let mut size = std::mem::size_of::<Self>();
288        size += self.exterior.estimated_size();
289        for hole in &self.holes {
290            size += hole.estimated_size();
291        }
292        size
293    }
294
295    /// Check if polygon is valid
296    pub fn is_valid(&self) -> bool {
297        self.exterior.is_valid()
298            && self.exterior.is_closed()
299            && self.holes.iter().all(|h| h.is_valid() && h.is_closed())
300    }
301}
302
303/// MultiPoint type (collection of points)
304#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
305pub struct MultiPointValue {
306    pub points: Vec<GeographyValue>,
307}
308
309impl MultiPointValue {
310    pub fn new(points: Vec<GeographyValue>) -> Self {
311        MultiPointValue { points }
312    }
313
314    /// Get the number of points
315    pub fn num_points(&self) -> usize {
316        self.points.len()
317    }
318
319    /// Calculate centroid
320    pub fn centroid(&self) -> Option<GeographyValue> {
321        if self.points.is_empty() {
322            return None;
323        }
324        let (sum_lat, sum_lon) = self.points.iter().fold((0.0, 0.0), |(lat, lon), p| {
325            (lat + p.latitude, lon + p.longitude)
326        });
327        Some(GeographyValue::new(
328            sum_lat / self.points.len() as f64,
329            sum_lon / self.points.len() as f64,
330        ))
331    }
332
333    /// Estimate memory usage
334    pub fn estimated_size(&self) -> usize {
335        std::mem::size_of::<Self>() + self.points.len() * std::mem::size_of::<GeographyValue>()
336    }
337
338    /// Check if all points are valid
339    pub fn is_valid(&self) -> bool {
340        self.points.iter().all(|p| p.is_valid())
341    }
342}
343
344/// MultiLineString type (collection of linestrings)
345#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
346pub struct MultiLineStringValue {
347    pub linestrings: Vec<LineStringValue>,
348}
349
350impl MultiLineStringValue {
351    pub fn new(linestrings: Vec<LineStringValue>) -> Self {
352        MultiLineStringValue { linestrings }
353    }
354
355    /// Get the number of linestrings
356    pub fn num_linestrings(&self) -> usize {
357        self.linestrings.len()
358    }
359
360    /// Calculate total length
361    pub fn length(&self) -> f64 {
362        self.linestrings.iter().map(|ls| ls.length()).sum()
363    }
364
365    /// Estimate memory usage
366    pub fn estimated_size(&self) -> usize {
367        std::mem::size_of::<Self>()
368            + self
369                .linestrings
370                .iter()
371                .map(|ls| ls.estimated_size())
372                .sum::<usize>()
373    }
374
375    /// Check if all linestrings are valid
376    pub fn is_valid(&self) -> bool {
377        self.linestrings.iter().all(|ls| ls.is_valid())
378    }
379}
380
381/// MultiPolygon type (collection of polygons)
382#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
383pub struct MultiPolygonValue {
384    pub polygons: Vec<PolygonValue>,
385}
386
387impl MultiPolygonValue {
388    pub fn new(polygons: Vec<PolygonValue>) -> Self {
389        MultiPolygonValue { polygons }
390    }
391
392    /// Get the number of polygons
393    pub fn num_polygons(&self) -> usize {
394        self.polygons.len()
395    }
396
397    /// Calculate total area
398    pub fn area(&self) -> f64 {
399        self.polygons.iter().map(|p| p.area()).sum()
400    }
401
402    /// Check if a point is inside any polygon
403    pub fn contains_point(&self, point: &GeographyValue) -> bool {
404        self.polygons.iter().any(|p| p.contains_point(point))
405    }
406
407    /// Estimate memory usage
408    pub fn estimated_size(&self) -> usize {
409        std::mem::size_of::<Self>()
410            + self
411                .polygons
412                .iter()
413                .map(|p| p.estimated_size())
414                .sum::<usize>()
415    }
416
417    /// Check if all polygons are valid
418    pub fn is_valid(&self) -> bool {
419        self.polygons.iter().all(|p| p.is_valid())
420    }
421}
422
423/// Geographic type enumeration
424#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
425pub enum Geography {
426    Point(GeographyValue),
427    LineString(LineStringValue),
428    Polygon(PolygonValue),
429    MultiPoint(MultiPointValue),
430    MultiLineString(MultiLineStringValue),
431    MultiPolygon(MultiPolygonValue),
432}
433
434impl std::hash::Hash for Geography {
435    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
436        match self {
437            Geography::Point(p) => {
438                0u8.hash(state);
439                p.hash(state);
440            }
441            Geography::LineString(ls) => {
442                1u8.hash(state);
443                ls.points.hash(state);
444            }
445            Geography::Polygon(p) => {
446                2u8.hash(state);
447                p.exterior.points.hash(state);
448                p.holes.len().hash(state);
449            }
450            Geography::MultiPoint(mp) => {
451                3u8.hash(state);
452                mp.points.hash(state);
453            }
454            Geography::MultiLineString(mls) => {
455                4u8.hash(state);
456                mls.linestrings.len().hash(state);
457                for ls in &mls.linestrings {
458                    ls.points.hash(state);
459                }
460            }
461            Geography::MultiPolygon(mp) => {
462                5u8.hash(state);
463                mp.polygons.len().hash(state);
464                for p in &mp.polygons {
465                    p.exterior.points.hash(state);
466                }
467            }
468        }
469    }
470}
471
472impl Geography {
473    /// Parse geographic data from the WKT format
474    pub fn from_wkt(wkt: &str) -> Result<Self, String> {
475        let wkt_upper = wkt.trim().to_uppercase();
476
477        if wkt_upper.starts_with("POINT") {
478            Self::parse_point_wkt(wkt.trim())
479        } else if wkt_upper.starts_with("LINESTRING") {
480            Self::parse_linestring_wkt(wkt.trim())
481        } else if wkt_upper.starts_with("POLYGON") {
482            Self::parse_polygon_wkt(wkt.trim())
483        } else if wkt_upper.starts_with("MULTIPOINT") {
484            Self::parse_multipoint_wkt(wkt.trim())
485        } else if wkt_upper.starts_with("MULTILINESTRING") {
486            Self::parse_multilinestring_wkt(wkt.trim())
487        } else if wkt_upper.starts_with("MULTIPOLYGON") {
488            Self::parse_multipolygon_wkt(wkt.trim())
489        } else {
490            Err(format!("Unsupported WKT format: {}", wkt))
491        }
492    }
493
494    /// Convert to WKT format
495    pub fn to_wkt(&self) -> String {
496        match self {
497            Geography::Point(p) => format!("POINT({} {})", p.longitude, p.latitude),
498            Geography::LineString(ls) => {
499                let coords: Vec<String> = ls
500                    .points
501                    .iter()
502                    .map(|p| format!("{} {}", p.longitude, p.latitude))
503                    .collect();
504                format!("LINESTRING({})", coords.join(", "))
505            }
506            Geography::Polygon(p) => {
507                let mut rings: Vec<String> = Vec::new();
508                let exterior_coords: Vec<String> = p
509                    .exterior
510                    .points
511                    .iter()
512                    .map(|pt| format!("{} {}", pt.longitude, pt.latitude))
513                    .collect();
514                rings.push(format!("({})", exterior_coords.join(", ")));
515                for hole in &p.holes {
516                    let hole_coords: Vec<String> = hole
517                        .points
518                        .iter()
519                        .map(|pt| format!("{} {}", pt.longitude, pt.latitude))
520                        .collect();
521                    rings.push(format!("({})", hole_coords.join(", ")));
522                }
523                format!("POLYGON({})", rings.join(", "))
524            }
525            Geography::MultiPoint(mp) => {
526                let coords: Vec<String> = mp
527                    .points
528                    .iter()
529                    .map(|p| format!("({} {})", p.longitude, p.latitude))
530                    .collect();
531                format!("MULTIPOINT({})", coords.join(", "))
532            }
533            Geography::MultiLineString(mls) => {
534                let linestrings: Vec<String> = mls
535                    .linestrings
536                    .iter()
537                    .map(|ls| {
538                        let coords: Vec<String> = ls
539                            .points
540                            .iter()
541                            .map(|p| format!("{} {}", p.longitude, p.latitude))
542                            .collect();
543                        format!("({})", coords.join(", "))
544                    })
545                    .collect();
546                format!("MULTILINESTRING({})", linestrings.join(", "))
547            }
548            Geography::MultiPolygon(mp) => {
549                let polygons: Vec<String> = mp
550                    .polygons
551                    .iter()
552                    .map(|p| {
553                        let mut rings: Vec<String> = Vec::new();
554                        let exterior_coords: Vec<String> = p
555                            .exterior
556                            .points
557                            .iter()
558                            .map(|pt| format!("{} {}", pt.longitude, pt.latitude))
559                            .collect();
560                        rings.push(format!("({})", exterior_coords.join(", ")));
561                        for hole in &p.holes {
562                            let hole_coords: Vec<String> = hole
563                                .points
564                                .iter()
565                                .map(|pt| format!("{} {}", pt.longitude, pt.latitude))
566                                .collect();
567                            rings.push(format!("({})", hole_coords.join(", ")));
568                        }
569                        format!("({})", rings.join(", "))
570                    })
571                    .collect();
572                format!("MULTIPOLYGON({})", polygons.join(", "))
573            }
574        }
575    }
576
577    fn parse_point_wkt(wkt: &str) -> Result<Self, String> {
578        let re = Regex::new(r"(?i)POINT\s*\(\s*([-\d.]+)\s+([-\d.]+)\s*\)")
579            .map_err(|_| "Invalid regular expression".to_string())?;
580
581        if let Some(caps) = re.captures(wkt) {
582            let lon = caps
583                .get(1)
584                .ok_or("Missing longitude coordinate")?
585                .as_str()
586                .parse::<f64>()
587                .map_err(|_| "Invalid longitude format")?;
588            let lat = caps
589                .get(2)
590                .ok_or("Missing latitude coordinate")?
591                .as_str()
592                .parse::<f64>()
593                .map_err(|_| "Invalid latitude format")?;
594            return Ok(Geography::Point(GeographyValue::new(lat, lon)));
595        }
596
597        Err("Invalid POINT WKT format".to_string())
598    }
599
600    fn parse_linestring_wkt(wkt: &str) -> Result<Self, String> {
601        let re = Regex::new(r"(?i)LINESTRING\s*\(([^)]+)\)")
602            .map_err(|_| "Invalid regular expression".to_string())?;
603
604        if let Some(caps) = re.captures(wkt) {
605            let coords_str = caps.get(1).ok_or("Missing coordinates")?.as_str();
606            let points = Self::parse_coordinate_pairs(coords_str)?;
607            if points.len() < 2 {
608                return Err("LineString must have at least 2 points".to_string());
609            }
610            return Ok(Geography::LineString(LineStringValue::new(points)));
611        }
612
613        Err("Invalid LINESTRING WKT format".to_string())
614    }
615
616    fn parse_polygon_wkt(wkt: &str) -> Result<Self, String> {
617        let re = Regex::new(r"(?i)POLYGON\s*\((.+)\)\s*$")
618            .map_err(|_| "Invalid regular expression".to_string())?;
619
620        if let Some(caps) = re.captures(wkt) {
621            let content = caps.get(1).ok_or("Missing polygon content")?.as_str();
622            let rings = Self::parse_rings(content)?;
623
624            if rings.is_empty() {
625                return Err("Polygon must have at least one ring".to_string());
626            }
627
628            let exterior = rings.into_iter().next().unwrap();
629            return Ok(Geography::Polygon(PolygonValue::from_ring(exterior)));
630        }
631
632        Err("Invalid POLYGON WKT format".to_string())
633    }
634
635    fn parse_multipoint_wkt(wkt: &str) -> Result<Self, String> {
636        let re = Regex::new(r"(?i)MULTIPOINT\s*\(([^)]*)\)")
637            .map_err(|_| "Invalid regular expression".to_string())?;
638
639        if let Some(caps) = re.captures(wkt) {
640            let coords_str = caps.get(1).ok_or("Missing coordinates")?.as_str();
641            if coords_str.trim().is_empty() {
642                return Ok(Geography::MultiPoint(MultiPointValue::default()));
643            }
644            let points = Self::parse_coordinate_pairs(coords_str)?;
645            return Ok(Geography::MultiPoint(MultiPointValue::new(points)));
646        }
647
648        Err("Invalid MULTIPOINT WKT format".to_string())
649    }
650
651    fn parse_multilinestring_wkt(wkt: &str) -> Result<Self, String> {
652        let re = Regex::new(r"(?i)MULTILINESTRING\s*\((.+)\)\s*$")
653            .map_err(|_| "Invalid regular expression".to_string())?;
654
655        if let Some(caps) = re.captures(wkt) {
656            let content = caps
657                .get(1)
658                .ok_or("Missing multilinestring content")?
659                .as_str();
660            let rings = Self::parse_rings(content)?;
661
662            let linestrings: Vec<LineStringValue> =
663                rings.into_iter().filter(|r| r.points.len() >= 2).collect();
664
665            return Ok(Geography::MultiLineString(MultiLineStringValue::new(
666                linestrings,
667            )));
668        }
669
670        Err("Invalid MULTILINESTRING WKT format".to_string())
671    }
672
673    fn parse_multipolygon_wkt(wkt: &str) -> Result<Self, String> {
674        let re = Regex::new(r"(?i)MULTIPOLYGON\s*\((.+)\)\s*$")
675            .map_err(|_| "Invalid regular expression".to_string())?;
676
677        if let Some(caps) = re.captures(wkt) {
678            let content = caps.get(1).ok_or("Missing multipolygon content")?.as_str();
679            let polygons = Self::parse_multipolygon_content(content)?;
680
681            if polygons.is_empty() {
682                return Ok(Geography::MultiPolygon(MultiPolygonValue::default()));
683            }
684
685            return Ok(Geography::MultiPolygon(MultiPolygonValue::new(polygons)));
686        }
687
688        Err("Invalid MULTIPOLYGON WKT format".to_string())
689    }
690
691    fn parse_coordinate_pairs(s: &str) -> Result<Vec<GeographyValue>, String> {
692        let mut points = Vec::new();
693        let pairs: Vec<&str> = s.split(',').collect();
694
695        for pair in pairs {
696            let pair = pair.trim();
697            if pair.is_empty() {
698                continue;
699            }
700
701            let coords: Vec<&str> = pair.split_whitespace().collect();
702            if coords.len() < 2 {
703                return Err(format!("Invalid coordinate pair: {}", pair));
704            }
705
706            let lon = coords[0]
707                .parse::<f64>()
708                .map_err(|_| format!("Invalid longitude: {}", coords[0]))?;
709            let lat = coords[1]
710                .parse::<f64>()
711                .map_err(|_| format!("Invalid latitude: {}", coords[1]))?;
712
713            points.push(GeographyValue::new(lat, lon));
714        }
715
716        Ok(points)
717    }
718
719    fn parse_rings(s: &str) -> Result<Vec<LineStringValue>, String> {
720        let mut rings = Vec::new();
721        let mut depth = 0;
722        let mut current = String::new();
723
724        for c in s.chars() {
725            match c {
726                '(' => {
727                    depth += 1;
728                    if depth > 1 {
729                        current.push(c);
730                    }
731                }
732                ')' => {
733                    depth -= 1;
734                    if depth > 0 {
735                        current.push(c);
736                    } else if !current.trim().is_empty() {
737                        let points = Self::parse_coordinate_pairs(&current)?;
738                        if !points.is_empty() {
739                            rings.push(LineStringValue::new(points));
740                        }
741                        current.clear();
742                    }
743                }
744                _ => {
745                    if depth > 0 {
746                        current.push(c);
747                    }
748                }
749            }
750        }
751
752        Ok(rings)
753    }
754
755    fn parse_multipolygon_content(s: &str) -> Result<Vec<PolygonValue>, String> {
756        let mut polygons = Vec::new();
757        let mut depth = 0;
758        let mut polygon_content = String::new();
759        let mut in_polygon = false;
760
761        for c in s.chars() {
762            match c {
763                '(' => {
764                    depth += 1;
765                    if depth == 2 {
766                        in_polygon = true;
767                    }
768                    if in_polygon {
769                        polygon_content.push(c);
770                    }
771                }
772                ')' => {
773                    if in_polygon {
774                        polygon_content.push(c);
775                    }
776                    depth -= 1;
777                    if depth == 1 && in_polygon {
778                        let rings = Self::parse_rings(&polygon_content)?;
779                        if !rings.is_empty() {
780                            let exterior = rings.into_iter().next().unwrap();
781                            polygons.push(PolygonValue::from_ring(exterior));
782                        }
783                        polygon_content.clear();
784                        in_polygon = false;
785                    }
786                }
787                _ => {
788                    if in_polygon {
789                        polygon_content.push(c);
790                    }
791                }
792            }
793        }
794
795        Ok(polygons)
796    }
797
798    /// Get geometry type name
799    pub fn geometry_type(&self) -> &'static str {
800        match self {
801            Geography::Point(_) => "Point",
802            Geography::LineString(_) => "LineString",
803            Geography::Polygon(_) => "Polygon",
804            Geography::MultiPoint(_) => "MultiPoint",
805            Geography::MultiLineString(_) => "MultiLineString",
806            Geography::MultiPolygon(_) => "MultiPolygon",
807        }
808    }
809
810    /// Calculate centroid of the geometry
811    pub fn centroid(&self) -> Option<GeographyValue> {
812        match self {
813            Geography::Point(p) => Some(p.clone()),
814            Geography::LineString(ls) => ls.centroid(),
815            Geography::Polygon(p) => p.centroid(),
816            Geography::MultiPoint(mp) => mp.centroid(),
817            Geography::MultiLineString(mls) => {
818                let all_points: Vec<&GeographyValue> =
819                    mls.linestrings.iter().flat_map(|ls| &ls.points).collect();
820                if all_points.is_empty() {
821                    return None;
822                }
823                let (sum_lat, sum_lon) = all_points.iter().fold((0.0, 0.0), |(lat, lon), p| {
824                    (lat + p.latitude, lon + p.longitude)
825                });
826                Some(GeographyValue::new(
827                    sum_lat / all_points.len() as f64,
828                    sum_lon / all_points.len() as f64,
829                ))
830            }
831            Geography::MultiPolygon(mp) => {
832                let centroids: Vec<GeographyValue> =
833                    mp.polygons.iter().filter_map(|p| p.centroid()).collect();
834                if centroids.is_empty() {
835                    return None;
836                }
837                let (sum_lat, sum_lon) = centroids.iter().fold((0.0, 0.0), |(lat, lon), p| {
838                    (lat + p.latitude, lon + p.longitude)
839                });
840                Some(GeographyValue::new(
841                    sum_lat / centroids.len() as f64,
842                    sum_lon / centroids.len() as f64,
843                ))
844            }
845        }
846    }
847
848    /// Check if geometry is valid
849    pub fn is_valid(&self) -> bool {
850        match self {
851            Geography::Point(p) => p.is_valid(),
852            Geography::LineString(ls) => ls.is_valid(),
853            Geography::Polygon(p) => p.is_valid(),
854            Geography::MultiPoint(mp) => mp.is_valid(),
855            Geography::MultiLineString(mls) => mls.is_valid(),
856            Geography::MultiPolygon(mp) => mp.is_valid(),
857        }
858    }
859
860    /// Estimate memory usage
861    pub fn estimated_size(&self) -> usize {
862        match self {
863            Geography::Point(p) => p.estimated_size(),
864            Geography::LineString(ls) => ls.estimated_size(),
865            Geography::Polygon(p) => p.estimated_size(),
866            Geography::MultiPoint(mp) => mp.estimated_size(),
867            Geography::MultiLineString(mls) => mls.estimated_size(),
868            Geography::MultiPolygon(mp) => mp.estimated_size(),
869        }
870    }
871
872    /// Get bounding box as (min_lat, max_lat, min_lon, max_lon)
873    pub fn bounding_box(&self) -> Option<(f64, f64, f64, f64)> {
874        match self {
875            Geography::Point(p) => Some((p.latitude, p.latitude, p.longitude, p.longitude)),
876            Geography::LineString(ls) => {
877                if ls.points.is_empty() {
878                    return None;
879                }
880                let mut min_lat = f64::MAX;
881                let mut max_lat = f64::MIN;
882                let mut min_lon = f64::MAX;
883                let mut max_lon = f64::MIN;
884                for p in &ls.points {
885                    min_lat = min_lat.min(p.latitude);
886                    max_lat = max_lat.max(p.latitude);
887                    min_lon = min_lon.min(p.longitude);
888                    max_lon = max_lon.max(p.longitude);
889                }
890                Some((min_lat, max_lat, min_lon, max_lon))
891            }
892            Geography::Polygon(p) => p.bounding_box(),
893            Geography::MultiPoint(mp) => {
894                if mp.points.is_empty() {
895                    return None;
896                }
897                let mut min_lat = f64::MAX;
898                let mut max_lat = f64::MIN;
899                let mut min_lon = f64::MAX;
900                let mut max_lon = f64::MIN;
901                for p in &mp.points {
902                    min_lat = min_lat.min(p.latitude);
903                    max_lat = max_lat.max(p.latitude);
904                    min_lon = min_lon.min(p.longitude);
905                    max_lon = max_lon.max(p.longitude);
906                }
907                Some((min_lat, max_lat, min_lon, max_lon))
908            }
909            Geography::MultiLineString(mls) => {
910                let all_points: Vec<&GeographyValue> =
911                    mls.linestrings.iter().flat_map(|ls| &ls.points).collect();
912                if all_points.is_empty() {
913                    return None;
914                }
915                let mut min_lat = f64::MAX;
916                let mut max_lat = f64::MIN;
917                let mut min_lon = f64::MAX;
918                let mut max_lon = f64::MIN;
919                for p in all_points {
920                    min_lat = min_lat.min(p.latitude);
921                    max_lat = max_lat.max(p.latitude);
922                    min_lon = min_lon.min(p.longitude);
923                    max_lon = max_lon.max(p.longitude);
924                }
925                Some((min_lat, max_lat, min_lon, max_lon))
926            }
927            Geography::MultiPolygon(mp) => {
928                let all_points: Vec<&GeographyValue> = mp
929                    .polygons
930                    .iter()
931                    .flat_map(|p| p.exterior.points.iter())
932                    .collect();
933                if all_points.is_empty() {
934                    return None;
935                }
936                let mut min_lat = f64::MAX;
937                let mut max_lat = f64::MIN;
938                let mut min_lon = f64::MAX;
939                let mut max_lon = f64::MIN;
940                for p in all_points {
941                    min_lat = min_lat.min(p.latitude);
942                    max_lat = max_lat.max(p.latitude);
943                    min_lon = min_lon.min(p.longitude);
944                    max_lon = max_lon.max(p.longitude);
945                }
946                Some((min_lat, max_lat, min_lon, max_lon))
947            }
948        }
949    }
950}
951
952impl std::fmt::Display for Geography {
953    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
954        write!(f, "{}", self.to_wkt())
955    }
956}
957
958// ============================================================================
959// GeoJSON Support
960// ============================================================================
961
962/// GeoJSON Geometry Object
963#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
964#[serde(tag = "type", rename_all = "PascalCase")]
965pub enum GeoJsonGeometry {
966    Point {
967        coordinates: Vec<f64>,
968    },
969    LineString {
970        coordinates: Vec<Vec<f64>>,
971    },
972    Polygon {
973        coordinates: Vec<Vec<Vec<f64>>>,
974    },
975    MultiPoint {
976        coordinates: Vec<Vec<f64>>,
977    },
978    MultiLineString {
979        coordinates: Vec<Vec<Vec<f64>>>,
980    },
981    MultiPolygon {
982        coordinates: Vec<Vec<Vec<Vec<f64>>>>,
983    },
984}
985
986/// GeoJSON Feature Object
987#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
988pub struct GeoJsonFeature {
989    #[serde(rename = "type")]
990    pub type_: String,
991    pub geometry: Option<GeoJsonGeometry>,
992    pub properties: serde_json::Map<String, serde_json::Value>,
993}
994
995/// GeoJSON FeatureCollection Object
996#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
997pub struct GeoJsonFeatureCollection {
998    #[serde(rename = "type")]
999    pub type_: String,
1000    pub features: Vec<GeoJsonFeature>,
1001}
1002
1003impl Geography {
1004    /// Convert to GeoJSON Geometry object
1005    pub fn to_geojson(&self) -> GeoJsonGeometry {
1006        match self {
1007            Geography::Point(p) => GeoJsonGeometry::Point {
1008                coordinates: vec![p.longitude, p.latitude],
1009            },
1010            Geography::LineString(ls) => GeoJsonGeometry::LineString {
1011                coordinates: ls
1012                    .points
1013                    .iter()
1014                    .map(|p| vec![p.longitude, p.latitude])
1015                    .collect(),
1016            },
1017            Geography::Polygon(poly) => GeoJsonGeometry::Polygon {
1018                coordinates: {
1019                    let mut rings = Vec::new();
1020                    rings.push(
1021                        poly.exterior
1022                            .points
1023                            .iter()
1024                            .map(|p| vec![p.longitude, p.latitude])
1025                            .collect(),
1026                    );
1027                    for hole in &poly.holes {
1028                        rings.push(
1029                            hole.points
1030                                .iter()
1031                                .map(|p| vec![p.longitude, p.latitude])
1032                                .collect(),
1033                        );
1034                    }
1035                    rings
1036                },
1037            },
1038            Geography::MultiPoint(mp) => GeoJsonGeometry::MultiPoint {
1039                coordinates: mp
1040                    .points
1041                    .iter()
1042                    .map(|p| vec![p.longitude, p.latitude])
1043                    .collect(),
1044            },
1045            Geography::MultiLineString(mls) => GeoJsonGeometry::MultiLineString {
1046                coordinates: mls
1047                    .linestrings
1048                    .iter()
1049                    .map(|ls| {
1050                        ls.points
1051                            .iter()
1052                            .map(|p| vec![p.longitude, p.latitude])
1053                            .collect()
1054                    })
1055                    .collect(),
1056            },
1057            Geography::MultiPolygon(mp) => GeoJsonGeometry::MultiPolygon {
1058                coordinates: mp
1059                    .polygons
1060                    .iter()
1061                    .map(|poly| {
1062                        let mut rings: Vec<Vec<Vec<f64>>> = Vec::new();
1063                        rings.push(
1064                            poly.exterior
1065                                .points
1066                                .iter()
1067                                .map(|p| vec![p.longitude, p.latitude])
1068                                .collect(),
1069                        );
1070                        for hole in &poly.holes {
1071                            rings.push(
1072                                hole.points
1073                                    .iter()
1074                                    .map(|p| vec![p.longitude, p.latitude])
1075                                    .collect(),
1076                            );
1077                        }
1078                        rings
1079                    })
1080                    .collect(),
1081            },
1082        }
1083    }
1084
1085    /// Parse from GeoJSON Geometry object
1086    pub fn from_geojson(geojson: &GeoJsonGeometry) -> Result<Self, String> {
1087        match geojson {
1088            GeoJsonGeometry::Point { coordinates } => {
1089                if coordinates.len() < 2 {
1090                    return Err("Point must have at least 2 coordinates".to_string());
1091                }
1092                Ok(Geography::Point(GeographyValue::new(
1093                    coordinates[1],
1094                    coordinates[0],
1095                )))
1096            }
1097            GeoJsonGeometry::LineString { coordinates } => {
1098                let points: Result<Vec<_>, _> = coordinates
1099                    .iter()
1100                    .map(|c| {
1101                        if c.len() < 2 {
1102                            Err("LineString coordinate must have at least 2 values".to_string())
1103                        } else {
1104                            Ok(GeographyValue::new(c[1], c[0]))
1105                        }
1106                    })
1107                    .collect();
1108                Ok(Geography::LineString(LineStringValue::new(points?)))
1109            }
1110            GeoJsonGeometry::Polygon { coordinates } => {
1111                if coordinates.is_empty() {
1112                    return Err("Polygon must have at least one ring".to_string());
1113                }
1114                let exterior = parse_ring(&coordinates[0])?;
1115                let holes: Result<Vec<_>, _> = coordinates[1..]
1116                    .iter()
1117                    .map(|ring| parse_ring(ring))
1118                    .collect();
1119                Ok(Geography::Polygon(PolygonValue::new(exterior, holes?)))
1120            }
1121            GeoJsonGeometry::MultiPoint { coordinates } => {
1122                let points: Result<Vec<_>, _> = coordinates
1123                    .iter()
1124                    .map(|c| {
1125                        if c.len() < 2 {
1126                            Err("MultiPoint coordinate must have at least 2 values".to_string())
1127                        } else {
1128                            Ok(GeographyValue::new(c[1], c[0]))
1129                        }
1130                    })
1131                    .collect();
1132                Ok(Geography::MultiPoint(MultiPointValue::new(points?)))
1133            }
1134            GeoJsonGeometry::MultiLineString { coordinates } => {
1135                let linestrings: Result<Vec<_>, _> =
1136                    coordinates.iter().map(|ring| parse_ring(ring)).collect();
1137                Ok(Geography::MultiLineString(MultiLineStringValue::new(
1138                    linestrings?,
1139                )))
1140            }
1141            GeoJsonGeometry::MultiPolygon { coordinates } => {
1142                let polygons: Result<Vec<_>, _> = coordinates
1143                    .iter()
1144                    .map(|poly_coords| {
1145                        if poly_coords.is_empty() {
1146                            return Err(
1147                                "MultiPolygon polygon must have at least one ring".to_string()
1148                            );
1149                        }
1150                        let exterior = parse_ring(&poly_coords[0])?;
1151                        let holes: Result<Vec<_>, _> = poly_coords[1..]
1152                            .iter()
1153                            .map(|ring| parse_ring(ring))
1154                            .collect();
1155                        Ok(PolygonValue::new(exterior, holes?))
1156                    })
1157                    .collect();
1158                Ok(Geography::MultiPolygon(MultiPolygonValue::new(polygons?)))
1159            }
1160        }
1161    }
1162
1163    /// Convert to GeoJSON string
1164    pub fn to_geojson_string(&self) -> String {
1165        serde_json::to_string(&self.to_geojson()).unwrap_or_default()
1166    }
1167
1168    /// Parse from GeoJSON string
1169    pub fn from_geojson_string(json: &str) -> Result<Self, String> {
1170        let geojson: GeoJsonGeometry =
1171            serde_json::from_str(json).map_err(|e| format!("Invalid GeoJSON: {}", e))?;
1172        Self::from_geojson(&geojson)
1173    }
1174}
1175
1176fn parse_ring(coords: &[Vec<f64>]) -> Result<LineStringValue, String> {
1177    let points: Result<Vec<_>, _> = coords
1178        .iter()
1179        .map(|c| {
1180            if c.len() < 2 {
1181                Err("Ring coordinate must have at least 2 values".to_string())
1182            } else {
1183                Ok(GeographyValue::new(c[1], c[0]))
1184            }
1185        })
1186        .collect();
1187    Ok(LineStringValue::new(points?))
1188}
1189
1190#[cfg(test)]
1191mod tests {
1192    use super::*;
1193
1194    #[test]
1195    fn test_point_wkt_parsing() {
1196        let wkt = "POINT(116.4 39.9)";
1197        let geo = Geography::from_wkt(wkt).unwrap();
1198        assert!(matches!(geo, Geography::Point(_)));
1199        if let Geography::Point(p) = geo {
1200            assert!((p.longitude - 116.4).abs() < 1e-6);
1201            assert!((p.latitude - 39.9).abs() < 1e-6);
1202        }
1203    }
1204
1205    #[test]
1206    fn test_linestring_wkt_parsing() {
1207        let wkt = "LINESTRING(116.4 39.9, 121.5 31.2, 113.3 23.1)";
1208        let geo = Geography::from_wkt(wkt).unwrap();
1209        assert!(matches!(geo, Geography::LineString(_)));
1210        if let Geography::LineString(ls) = geo {
1211            assert_eq!(ls.points.len(), 3);
1212            assert!(ls.length() > 1000.0);
1213        }
1214    }
1215
1216    #[test]
1217    fn test_polygon_wkt_parsing() {
1218        let wkt = "POLYGON((116.0 40.0, 117.0 40.0, 117.0 39.0, 116.0 39.0, 116.0 40.0))";
1219        let geo = Geography::from_wkt(wkt).unwrap();
1220        assert!(matches!(geo, Geography::Polygon(_)));
1221    }
1222
1223    #[test]
1224    fn test_linestring_length() {
1225        let ls = LineStringValue::new(vec![
1226            GeographyValue::new(39.9, 116.4),
1227            GeographyValue::new(31.2, 121.5),
1228        ]);
1229        let length = ls.length();
1230        assert!(length > 1000.0);
1231        assert!(length < 1500.0);
1232    }
1233
1234    #[test]
1235    fn test_polygon_contains_point() {
1236        let polygon = PolygonValue::new(
1237            LineStringValue::new(vec![
1238                GeographyValue::new(40.0, 116.0),
1239                GeographyValue::new(40.0, 117.0),
1240                GeographyValue::new(39.0, 117.0),
1241                GeographyValue::new(39.0, 116.0),
1242                GeographyValue::new(40.0, 116.0),
1243            ]),
1244            vec![],
1245        );
1246        let point_inside = GeographyValue::new(39.5, 116.5);
1247        let point_outside = GeographyValue::new(50.0, 120.0);
1248
1249        assert!(polygon.contains_point(&point_inside));
1250        assert!(!polygon.contains_point(&point_outside));
1251    }
1252
1253    #[test]
1254    fn test_wkt_roundtrip() {
1255        let wkt = "POINT(116.4 39.9)";
1256        let geo = Geography::from_wkt(wkt).unwrap();
1257        let roundtrip = geo.to_wkt();
1258        let geo2 = Geography::from_wkt(&roundtrip).unwrap();
1259        assert_eq!(geo, geo2);
1260    }
1261
1262    #[test]
1263    fn test_centroid() {
1264        let ls = LineStringValue::new(vec![
1265            GeographyValue::new(0.0, 0.0),
1266            GeographyValue::new(10.0, 10.0),
1267        ]);
1268        let centroid = ls.centroid().unwrap();
1269        assert!((centroid.latitude - 5.0).abs() < 1e-6);
1270        assert!((centroid.longitude - 5.0).abs() < 1e-6);
1271    }
1272
1273    #[test]
1274    fn test_is_valid() {
1275        let valid_point = GeographyValue::new(45.0, 90.0);
1276        assert!(valid_point.is_valid());
1277
1278        let invalid_point = GeographyValue::new(100.0, 200.0);
1279        assert!(!invalid_point.is_valid());
1280    }
1281
1282    #[test]
1283    fn test_multipoint_wkt_parsing() {
1284        let wkt = "MULTIPOINT(116.4 39.9, 121.5 31.2)";
1285        let geo = Geography::from_wkt(wkt).unwrap();
1286        assert!(matches!(geo, Geography::MultiPoint(_)));
1287        if let Geography::MultiPoint(mp) = geo {
1288            assert_eq!(mp.points.len(), 2);
1289        }
1290    }
1291
1292    #[test]
1293    fn test_multilinestring_wkt_parsing() {
1294        let wkt = "MULTILINESTRING((116.4 39.9, 121.5 31.2), (113.3 23.1, 114.1 22.5))";
1295        let geo = Geography::from_wkt(wkt).unwrap();
1296        assert!(matches!(geo, Geography::MultiLineString(_)));
1297        if let Geography::MultiLineString(mls) = geo {
1298            assert_eq!(mls.linestrings.len(), 2);
1299        }
1300    }
1301
1302    #[test]
1303    fn test_geojson_point() {
1304        let point = Geography::Point(GeographyValue::new(39.9, 116.4));
1305        let geojson = point.to_geojson();
1306        assert!(matches!(geojson, GeoJsonGeometry::Point { .. }));
1307        if let GeoJsonGeometry::Point { coordinates } = geojson {
1308            assert_eq!(coordinates, vec![116.4, 39.9]);
1309        }
1310    }
1311
1312    #[test]
1313    fn test_geojson_linestring() {
1314        let ls = Geography::LineString(LineStringValue::new(vec![
1315            GeographyValue::new(39.9, 116.4),
1316            GeographyValue::new(31.2, 121.5),
1317        ]));
1318        let geojson = ls.to_geojson();
1319        assert!(matches!(geojson, GeoJsonGeometry::LineString { .. }));
1320        if let GeoJsonGeometry::LineString { coordinates } = geojson {
1321            assert_eq!(coordinates.len(), 2);
1322            assert_eq!(coordinates[0], vec![116.4, 39.9]);
1323        }
1324    }
1325
1326    #[test]
1327    fn test_geojson_polygon() {
1328        let polygon = PolygonValue::new(
1329            LineStringValue::new(vec![
1330                GeographyValue::new(40.0, 116.0),
1331                GeographyValue::new(40.0, 117.0),
1332                GeographyValue::new(39.0, 117.0),
1333                GeographyValue::new(39.0, 116.0),
1334                GeographyValue::new(40.0, 116.0),
1335            ]),
1336            vec![],
1337        );
1338        let geo = Geography::Polygon(polygon);
1339        let geojson = geo.to_geojson();
1340        assert!(matches!(geojson, GeoJsonGeometry::Polygon { .. }));
1341    }
1342
1343    #[test]
1344    fn test_geojson_roundtrip() {
1345        let point = Geography::Point(GeographyValue::new(39.9, 116.4));
1346        let geojson = point.to_geojson();
1347        let parsed = Geography::from_geojson(&geojson).unwrap();
1348        assert_eq!(point, parsed);
1349
1350        let ls = Geography::LineString(LineStringValue::new(vec![
1351            GeographyValue::new(39.9, 116.4),
1352            GeographyValue::new(31.2, 121.5),
1353        ]));
1354        let geojson = ls.to_geojson();
1355        let parsed = Geography::from_geojson(&geojson).unwrap();
1356        assert_eq!(ls, parsed);
1357    }
1358
1359    #[test]
1360    fn test_geojson_string() {
1361        let point = Geography::Point(GeographyValue::new(39.9, 116.4));
1362        let json_str = point.to_geojson_string();
1363        assert!(json_str.contains("\"type\":\"Point\""));
1364        assert!(json_str.contains("\"coordinates\""));
1365
1366        let parsed = Geography::from_geojson_string(&json_str).unwrap();
1367        assert_eq!(point, parsed);
1368    }
1369}