1use crate::error::PostgisError;
7use serde::{Deserialize, Serialize};
8
9pub const DEFAULT_SRID: i32 = 4326;
11
12#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
14pub struct Point {
15 pub x: f64,
16 pub y: f64,
17 pub srid: i32,
18}
19
20impl Point {
21 pub fn new(x: f64, y: f64) -> Self {
22 Self {
23 x,
24 y,
25 srid: DEFAULT_SRID,
26 }
27 }
28
29 pub fn with_srid(x: f64, y: f64, srid: i32) -> Self {
30 Self { x, y, srid }
31 }
32
33 pub fn euclidean_distance(&self, other: &Point) -> f64 {
35 let dx = self.x - other.x;
36 let dy = self.y - other.y;
37 (dx * dx + dy * dy).sqrt()
38 }
39
40 pub fn haversine_distance(&self, other: &Point) -> f64 {
42 const EARTH_RADIUS_M: f64 = 6_371_000.0;
43 let to_rad = |deg: f64| deg * std::f64::consts::PI / 180.0;
44 let lat1 = to_rad(self.y);
45 let lat2 = to_rad(other.y);
46 let dlat = to_rad(other.y - self.y);
47 let dlon = to_rad(other.x - self.x);
48 let a = (dlat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (dlon / 2.0).sin().powi(2);
49 let c = 2.0 * a.sqrt().asin();
50 EARTH_RADIUS_M * c
51 }
52
53 pub fn to_ewkt(&self) -> String {
55 format!("SRID={};POINT({} {})", self.srid, self.x, self.y)
56 }
57
58 pub fn to_wkt(&self) -> String {
60 format!("POINT({} {})", self.x, self.y)
61 }
62
63 pub fn midpoint(&self, other: &Point) -> Point {
65 Point::with_srid(
66 (self.x + other.x) / 2.0,
67 (self.y + other.y) / 2.0,
68 self.srid,
69 )
70 }
71
72 pub fn bearing(&self, other: &Point) -> f64 {
74 let to_rad = |deg: f64| deg * std::f64::consts::PI / 180.0;
75 let to_deg = |rad: f64| rad * 180.0 / std::f64::consts::PI;
76 let lat1 = to_rad(self.y);
77 let lat2 = to_rad(other.y);
78 let dlon = to_rad(other.x - self.x);
79 let y = dlon.sin() * lat2.cos();
80 let x = lat1.cos() * lat2.sin() - lat1.sin() * lat2.cos() * dlon.cos();
81 let bearing = to_deg(y.atan2(x));
82 (bearing + 360.0) % 360.0
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
88pub struct LineString {
89 pub points: Vec<Point>,
90 pub srid: i32,
91}
92
93impl LineString {
94 pub fn new(points: Vec<Point>) -> Self {
95 let srid = points.first().map(|p| p.srid).unwrap_or(DEFAULT_SRID);
96 Self { points, srid }
97 }
98
99 pub fn euclidean_length(&self) -> f64 {
101 self.points
102 .windows(2)
103 .map(|w| w[0].euclidean_distance(&w[1]))
104 .sum()
105 }
106
107 pub fn haversine_length(&self) -> f64 {
109 self.points
110 .windows(2)
111 .map(|w| w[0].haversine_distance(&w[1]))
112 .sum()
113 }
114
115 pub fn to_ewkt(&self) -> String {
116 let coords: Vec<String> = self
117 .points
118 .iter()
119 .map(|p| format!("{} {}", p.x, p.y))
120 .collect();
121 format!("SRID={};LINESTRING({})", self.srid, coords.join(", "))
122 }
123
124 pub fn to_wkt(&self) -> String {
126 let coords: Vec<String> = self
127 .points
128 .iter()
129 .map(|p| format!("{} {}", p.x, p.y))
130 .collect();
131 format!("LINESTRING({})", coords.join(", "))
132 }
133
134 pub fn point_count(&self) -> usize {
136 self.points.len()
137 }
138}
139
140#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
142pub struct Polygon {
143 pub rings: Vec<Vec<Point>>,
144 pub srid: i32,
145}
146
147impl Polygon {
148 pub fn new(outer: Vec<Point>) -> Self {
149 let srid = outer.first().map(|p| p.srid).unwrap_or(DEFAULT_SRID);
150 Self {
151 rings: vec![outer],
152 srid,
153 }
154 }
155
156 pub fn with_holes(outer: Vec<Point>, holes: Vec<Vec<Point>>) -> Self {
157 let srid = outer.first().map(|p| p.srid).unwrap_or(DEFAULT_SRID);
158 let mut rings = vec![outer];
159 rings.extend(holes);
160 Self { rings, srid }
161 }
162
163 pub fn shoelace_area(&self) -> f64 {
165 if self.rings.is_empty() {
166 return 0.0;
167 }
168 let outer = &self.rings[0];
169 if outer.len() < 3 {
170 return 0.0;
171 }
172 let mut sum = 0.0;
173 for i in 0..outer.len() {
174 let j = (i + 1) % outer.len();
175 sum += outer[i].x * outer[j].y;
176 sum -= outer[j].x * outer[i].y;
177 }
178 (sum / 2.0).abs()
179 }
180
181 pub fn contains_point(&self, point: &Point) -> bool {
183 if self.rings.is_empty() {
184 return false;
185 }
186 let outer = &self.rings[0];
187 let mut inside = false;
188 let mut j = outer.len() - 1;
189 for i in 0..outer.len() {
190 let intersect = (outer[i].y > point.y) != (outer[j].y > point.y)
191 && point.x
192 < (outer[j].x - outer[i].x) * (point.y - outer[i].y)
193 / (outer[j].y - outer[i].y)
194 + outer[i].x;
195 if intersect {
196 inside = !inside;
197 }
198 j = i;
199 }
200 if inside {
202 for hole in self.rings.iter().skip(1) {
203 let mut hole_inside = false;
204 let mut j = hole.len() - 1;
205 for i in 0..hole.len() {
206 let intersect = (hole[i].y > point.y) != (hole[j].y > point.y)
207 && point.x
208 < (hole[j].x - hole[i].x) * (point.y - hole[i].y)
209 / (hole[j].y - hole[i].y)
210 + hole[i].x;
211 if intersect {
212 hole_inside = !hole_inside;
213 }
214 j = i;
215 }
216 if hole_inside {
217 return false;
218 }
219 }
220 }
221 inside
222 }
223
224 pub fn to_ewkt(&self) -> String {
225 let rings: Vec<String> = self
226 .rings
227 .iter()
228 .map(|ring| {
229 let coords: Vec<String> = ring.iter().map(|p| format!("{} {}", p.x, p.y)).collect();
230 format!("({})", coords.join(", "))
231 })
232 .collect();
233 format!("SRID={};POLYGON({})", self.srid, rings.join(", "))
234 }
235
236 pub fn to_wkt(&self) -> String {
238 let rings: Vec<String> = self
239 .rings
240 .iter()
241 .map(|ring| {
242 let coords: Vec<String> = ring.iter().map(|p| format!("{} {}", p.x, p.y)).collect();
243 format!("({})", coords.join(", "))
244 })
245 .collect();
246 format!("POLYGON({})", rings.join(", "))
247 }
248
249 pub fn perimeter(&self) -> f64 {
251 if self.rings.is_empty() {
252 return 0.0;
253 }
254 let outer = &self.rings[0];
255 if outer.len() < 2 {
256 return 0.0;
257 }
258 let mut perim = 0.0;
259 for i in 0..outer.len() {
260 let j = (i + 1) % outer.len();
261 perim += outer[i].euclidean_distance(&outer[j]);
262 }
263 perim
264 }
265
266 pub fn ring_count(&self) -> usize {
268 self.rings.len()
269 }
270}
271
272#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
274pub enum Geometry {
275 Point(Point),
276 LineString(LineString),
277 Polygon(Polygon),
278 MultiPoint(Vec<Point>),
279 MultiLineString(Vec<LineString>),
280 MultiPolygon(Vec<Polygon>),
281}
282
283impl Geometry {
284 pub fn srid(&self) -> i32 {
286 match self {
287 Geometry::Point(p) => p.srid,
288 Geometry::LineString(ls) => ls.srid,
289 Geometry::Polygon(poly) => poly.srid,
290 Geometry::MultiPoint(pts) => pts.first().map(|p| p.srid).unwrap_or(DEFAULT_SRID),
291 Geometry::MultiLineString(lss) => lss.first().map(|ls| ls.srid).unwrap_or(DEFAULT_SRID),
292 Geometry::MultiPolygon(polys) => polys.first().map(|p| p.srid).unwrap_or(DEFAULT_SRID),
293 }
294 }
295
296 pub fn validate_srid(&self) -> Result<(), PostgisError> {
298 let expected = self.srid();
299 let check = |srid: i32| -> Result<(), PostgisError> {
300 if srid != expected {
301 Err(PostgisError::SridMismatch {
302 expected,
303 actual: srid,
304 })
305 } else {
306 Ok(())
307 }
308 };
309 match self {
310 Geometry::MultiPoint(pts) => {
311 for p in pts {
312 check(p.srid)?;
313 }
314 }
315 Geometry::MultiLineString(lss) => {
316 for ls in lss {
317 check(ls.srid)?;
318 }
319 }
320 Geometry::MultiPolygon(polys) => {
321 for p in polys {
322 check(p.srid)?;
323 }
324 }
325 _ => {}
326 }
327 Ok(())
328 }
329
330 pub fn type_name(&self) -> &'static str {
332 match self {
333 Geometry::Point(_) => "Point",
334 Geometry::LineString(_) => "LineString",
335 Geometry::Polygon(_) => "Polygon",
336 Geometry::MultiPoint(_) => "MultiPoint",
337 Geometry::MultiLineString(_) => "MultiLineString",
338 Geometry::MultiPolygon(_) => "MultiPolygon",
339 }
340 }
341
342 pub fn to_wkt(&self) -> String {
344 match self {
345 Geometry::Point(p) => p.to_wkt(),
346 Geometry::LineString(ls) => ls.to_wkt(),
347 Geometry::Polygon(poly) => poly.to_wkt(),
348 Geometry::MultiPoint(pts) => {
349 let coords: Vec<String> = pts.iter().map(|p| format!("{} {}", p.x, p.y)).collect();
350 format!("MULTIPOINT({})", coords.join(", "))
351 }
352 Geometry::MultiLineString(lss) => {
353 let lines: Vec<String> = lss
354 .iter()
355 .map(|ls| {
356 let coords: Vec<String> = ls
357 .points
358 .iter()
359 .map(|p| format!("{} {}", p.x, p.y))
360 .collect();
361 format!("({})", coords.join(", "))
362 })
363 .collect();
364 format!("MULTILINESTRING({})", lines.join(", "))
365 }
366 Geometry::MultiPolygon(polys) => {
367 let polygons: Vec<String> = polys
368 .iter()
369 .map(|poly| {
370 let rings: Vec<String> = poly
371 .rings
372 .iter()
373 .map(|ring| {
374 let coords: Vec<String> =
375 ring.iter().map(|p| format!("{} {}", p.x, p.y)).collect();
376 format!("({})", coords.join(", "))
377 })
378 .collect();
379 format!("({})", rings.join(", "))
380 })
381 .collect();
382 format!("MULTIPOLYGON({})", polygons.join(", "))
383 }
384 }
385 }
386
387 pub fn bounding_box(&self) -> Option<(f64, f64, f64, f64)> {
389 let points: Vec<&Point> = match self {
390 Geometry::Point(p) => vec![p],
391 Geometry::LineString(ls) => ls.points.iter().collect(),
392 Geometry::Polygon(poly) => poly.rings.iter().flatten().collect(),
393 Geometry::MultiPoint(pts) => pts.iter().collect(),
394 Geometry::MultiLineString(lss) => lss.iter().flat_map(|ls| ls.points.iter()).collect(),
395 Geometry::MultiPolygon(polys) => polys
396 .iter()
397 .flat_map(|p| p.rings.iter().flatten())
398 .collect(),
399 };
400 if points.is_empty() {
401 return None;
402 }
403 let mut min_x = points[0].x;
404 let mut min_y = points[0].y;
405 let mut max_x = points[0].x;
406 let mut max_y = points[0].y;
407 for p in &points[1..] {
408 min_x = min_x.min(p.x);
409 min_y = min_y.min(p.y);
410 max_x = max_x.max(p.x);
411 max_y = max_y.max(p.y);
412 }
413 Some((min_x, min_y, max_x, max_y))
414 }
415
416 pub fn from_ewkt(ewkt: &str) -> Result<Self, PostgisError> {
423 let (srid, wkt) = if let Some(semi) = ewkt.find(';') {
424 let srid_str = &ewkt[..semi];
425 let wkt = &ewkt[semi + 1..];
426 if !srid_str.starts_with("SRID=") {
427 return Err(PostgisError::Query(format!(
428 "invalid EWKT SRID prefix: {}",
429 srid_str
430 )));
431 }
432 let srid: i32 = srid_str[5..]
433 .parse()
434 .map_err(|e| PostgisError::Query(format!("invalid SRID: {}", e)))?;
435 (srid, wkt)
436 } else {
437 (DEFAULT_SRID, ewkt)
438 };
439
440 let wkt = wkt.trim();
441 let upper = wkt.to_uppercase();
442
443 if upper.starts_with("POINT") {
444 let coords = extract_paren_content(&upper, "POINT")?;
445 let nums = parse_coord_pair(&coords)?;
446 Ok(Geometry::Point(Point::with_srid(nums.0, nums.1, srid)))
447 } else if upper.starts_with("LINESTRING") {
448 let coords = extract_paren_content(&upper, "LINESTRING")?;
449 let points = parse_coord_list(&coords)?
450 .into_iter()
451 .map(|(x, y)| Point::with_srid(x, y, srid))
452 .collect();
453 Ok(Geometry::LineString(LineString { points, srid }))
454 } else if upper.starts_with("POLYGON") {
455 let rings_str = extract_paren_content(&upper, "POLYGON")?;
456 let rings = parse_polygon_rings(&rings_str, srid)?;
458 Ok(Geometry::Polygon(Polygon { rings, srid }))
459 } else if upper.starts_with("MULTIPOINT") {
460 let coords = extract_paren_content(&upper, "MULTIPOINT")?;
461 let points = parse_coord_list(&coords)?
462 .into_iter()
463 .map(|(x, y)| Point::with_srid(x, y, srid))
464 .collect();
465 Ok(Geometry::MultiPoint(points))
466 } else {
467 Err(PostgisError::Query(format!(
468 "unsupported WKT type in: {}",
469 wkt
470 )))
471 }
472 }
473
474 pub fn to_ewkt(&self) -> String {
476 match self {
477 Geometry::Point(p) => p.to_ewkt(),
478 Geometry::LineString(ls) => ls.to_ewkt(),
479 Geometry::Polygon(poly) => poly.to_ewkt(),
480 Geometry::MultiPoint(pts) => {
481 let srid = pts.first().map(|p| p.srid).unwrap_or(DEFAULT_SRID);
482 let coords: Vec<String> = pts.iter().map(|p| format!("{} {}", p.x, p.y)).collect();
483 format!("SRID={};MULTIPOINT({})", srid, coords.join(", "))
484 }
485 Geometry::MultiLineString(lss) => {
486 let srid = lss.first().map(|ls| ls.srid).unwrap_or(DEFAULT_SRID);
487 let lines: Vec<String> = lss
488 .iter()
489 .map(|ls| {
490 let coords: Vec<String> = ls
491 .points
492 .iter()
493 .map(|p| format!("{} {}", p.x, p.y))
494 .collect();
495 format!("({})", coords.join(", "))
496 })
497 .collect();
498 format!("SRID={};MULTILINESTRING({})", srid, lines.join(", "))
499 }
500 Geometry::MultiPolygon(polys) => {
501 let srid = polys.first().map(|p| p.srid).unwrap_or(DEFAULT_SRID);
502 let polygons: Vec<String> = polys
503 .iter()
504 .map(|poly| {
505 let rings: Vec<String> = poly
506 .rings
507 .iter()
508 .map(|ring| {
509 let coords: Vec<String> =
510 ring.iter().map(|p| format!("{} {}", p.x, p.y)).collect();
511 format!("({})", coords.join(", "))
512 })
513 .collect();
514 format!("({})", rings.join(", "))
515 })
516 .collect();
517 format!("SRID={};MULTIPOLYGON({})", srid, polygons.join(", "))
518 }
519 }
520 }
521}
522
523fn extract_paren_content(upper_wkt: &str, type_name: &str) -> Result<String, PostgisError> {
525 let start = upper_wkt
526 .find(type_name)
527 .ok_or_else(|| PostgisError::Query(format!("missing type name {}", type_name)))?
528 + type_name.len();
529 let rest = &upper_wkt[start..];
530 let rest = rest.trim_start();
531 if !rest.starts_with('(') {
532 return Err(PostgisError::Query(format!(
533 "missing opening paren after {}: {}",
534 type_name, rest
535 )));
536 }
537 let mut depth = 0i32;
539 let mut end = 0usize;
540 for (i, c) in rest.chars().enumerate() {
541 match c {
542 '(' => depth += 1,
543 ')' => {
544 depth -= 1;
545 if depth == 0 {
546 end = i;
547 break;
548 }
549 }
550 _ => {}
551 }
552 }
553 if depth != 0 {
554 return Err(PostgisError::Query(format!(
555 "unbalanced parens in {}: {}",
556 type_name, rest
557 )));
558 }
559 Ok(rest[1..end].to_string())
560}
561
562fn parse_coord_pair(s: &str) -> Result<(f64, f64), PostgisError> {
564 let parts: Vec<&str> = s.split_whitespace().collect();
565 if parts.len() < 2 {
566 return Err(PostgisError::Query(format!(
567 "expected 2 coords, got {}: {}",
568 parts.len(),
569 s
570 )));
571 }
572 let x: f64 = parts[0]
573 .parse()
574 .map_err(|e| PostgisError::Query(format!("invalid x coord: {}", e)))?;
575 let y: f64 = parts[1]
576 .parse()
577 .map_err(|e| PostgisError::Query(format!("invalid y coord: {}", e)))?;
578 Ok((x, y))
579}
580
581fn parse_coord_list(s: &str) -> Result<Vec<(f64, f64)>, PostgisError> {
583 s.split(',')
584 .map(|pair| parse_coord_pair(pair.trim()))
585 .collect()
586}
587
588fn parse_polygon_rings(s: &str, srid: i32) -> Result<Vec<Vec<Point>>, PostgisError> {
590 let mut rings = Vec::new();
591 let mut depth = 0i32;
592 let mut current = String::new();
593 for c in s.chars() {
594 match c {
595 '(' => {
596 depth += 1;
597 if depth == 1 {
598 current.clear();
599 } else {
600 current.push(c);
601 }
602 }
603 ')' => {
604 depth -= 1;
605 if depth == 0 {
606 let ring: Vec<Point> = parse_coord_list(¤t)?
607 .into_iter()
608 .map(|(x, y)| Point::with_srid(x, y, srid))
609 .collect();
610 rings.push(ring);
611 } else {
612 current.push(c);
613 }
614 }
615 _ if depth >= 1 => {
616 current.push(c);
617 }
618 _ => {} }
620 }
621 if rings.is_empty() {
622 return Err(PostgisError::Query(format!("no rings parsed from: {}", s)));
623 }
624 Ok(rings)
625}
626
627#[cfg(test)]
628mod tests {
629 use super::*;
630
631 #[test]
632 fn test_point_ewkt() {
633 let p = Point::new(116.404, 39.915);
634 assert_eq!(p.to_ewkt(), "SRID=4326;POINT(116.404 39.915)");
635 }
636
637 #[test]
638 fn test_point_euclidean_distance() {
639 let p1 = Point::new(0.0, 0.0);
640 let p2 = Point::new(3.0, 4.0);
641 let dist = p1.euclidean_distance(&p2);
642 assert!((dist - 5.0).abs() < 1e-10);
643 }
644
645 #[test]
646 fn test_point_haversine_distance() {
647 let beijing = Point::new(116.404, 39.915);
649 let shanghai = Point::new(121.474, 31.230);
650 let dist = beijing.haversine_distance(&shanghai);
651 assert!(dist > 1_000_000.0 && dist < 1_200_000.0);
652 }
653
654 #[test]
655 fn test_linestring_length() {
656 let ls = LineString::new(vec![
657 Point::new(0.0, 0.0),
658 Point::new(3.0, 4.0),
659 Point::new(3.0, 9.0),
660 ]);
661 let len = ls.euclidean_length();
663 assert!((len - 10.0).abs() < 1e-10);
664 }
665
666 #[test]
667 fn test_polygon_area() {
668 let poly = Polygon::new(vec![
669 Point::new(0.0, 0.0),
670 Point::new(4.0, 0.0),
671 Point::new(4.0, 3.0),
672 Point::new(0.0, 3.0),
673 ]);
674 let area = poly.shoelace_area();
675 assert!((area - 12.0).abs() < 1e-10);
676 }
677
678 #[test]
679 fn test_polygon_contains_point() {
680 let poly = Polygon::new(vec![
681 Point::new(0.0, 0.0),
682 Point::new(4.0, 0.0),
683 Point::new(4.0, 3.0),
684 Point::new(0.0, 3.0),
685 ]);
686 assert!(poly.contains_point(&Point::new(2.0, 1.5)));
687 assert!(!poly.contains_point(&Point::new(5.0, 1.5)));
688 }
689
690 #[test]
691 fn test_polygon_with_hole() {
692 let outer = vec![
693 Point::new(0.0, 0.0),
694 Point::new(10.0, 0.0),
695 Point::new(10.0, 10.0),
696 Point::new(0.0, 10.0),
697 ];
698 let hole = vec![
699 Point::new(3.0, 3.0),
700 Point::new(7.0, 3.0),
701 Point::new(7.0, 7.0),
702 Point::new(3.0, 7.0),
703 ];
704 let poly = Polygon::with_holes(outer, vec![hole]);
705 assert!(poly.contains_point(&Point::new(1.0, 1.0)));
707 assert!(!poly.contains_point(&Point::new(5.0, 5.0)));
709 }
710
711 #[test]
712 fn test_geometry_srid_validation() {
713 let g = Geometry::MultiPoint(vec![
714 Point::with_srid(0.0, 0.0, 4326),
715 Point::with_srid(1.0, 1.0, 3857),
716 ]);
717 assert!(matches!(
718 g.validate_srid(),
719 Err(PostgisError::SridMismatch {
720 expected: 4326,
721 actual: 3857
722 })
723 ));
724 }
725
726 #[test]
727 fn test_geometry_ewkt() {
728 let g = Geometry::Point(Point::new(116.404, 39.915));
729 assert_eq!(g.to_ewkt(), "SRID=4326;POINT(116.404 39.915)");
730 assert_eq!(g.type_name(), "Point");
731 }
732
733 #[test]
734 fn test_point_wkt() {
735 let p = Point::new(1.0, 2.0);
736 assert_eq!(p.to_wkt(), "POINT(1 2)");
737 }
738
739 #[test]
740 fn test_point_midpoint() {
741 let p1 = Point::new(0.0, 0.0);
742 let p2 = Point::new(4.0, 6.0);
743 let mid = p1.midpoint(&p2);
744 assert!((mid.x - 2.0).abs() < 1e-10);
745 assert!((mid.y - 3.0).abs() < 1e-10);
746 }
747
748 #[test]
749 fn test_point_bearing_north() {
750 let p1 = Point::new(0.0, 0.0);
751 let p2 = Point::new(0.0, 1.0);
752 let bearing = p1.bearing(&p2);
753 assert!((bearing - 0.0).abs() < 1e-6);
754 }
755
756 #[test]
757 fn test_point_bearing_east() {
758 let p1 = Point::new(0.0, 0.0);
759 let p2 = Point::new(1.0, 0.0);
760 let bearing = p1.bearing(&p2);
761 assert!((bearing - 90.0).abs() < 1e-6);
762 }
763
764 #[test]
765 fn test_linestring_wkt() {
766 let ls = LineString::new(vec![Point::new(0.0, 0.0), Point::new(1.0, 1.0)]);
767 assert_eq!(ls.to_wkt(), "LINESTRING(0 0, 1 1)");
768 }
769
770 #[test]
771 fn test_linestring_point_count() {
772 let ls = LineString::new(vec![Point::new(0.0, 0.0), Point::new(1.0, 1.0)]);
773 assert_eq!(ls.point_count(), 2);
774 }
775
776 #[test]
777 fn test_polygon_wkt() {
778 let poly = Polygon::new(vec![
779 Point::new(0.0, 0.0),
780 Point::new(4.0, 0.0),
781 Point::new(4.0, 3.0),
782 Point::new(0.0, 3.0),
783 ]);
784 assert!(poly.to_wkt().starts_with("POLYGON("));
785 }
786
787 #[test]
788 fn test_polygon_perimeter() {
789 let poly = Polygon::new(vec![
790 Point::new(0.0, 0.0),
791 Point::new(4.0, 0.0),
792 Point::new(4.0, 3.0),
793 Point::new(0.0, 3.0),
794 ]);
795 let perim = poly.perimeter();
796 assert!((perim - 14.0).abs() < 1e-10);
797 }
798
799 #[test]
800 fn test_polygon_ring_count() {
801 let poly = Polygon::new(vec![
802 Point::new(0.0, 0.0),
803 Point::new(1.0, 0.0),
804 Point::new(0.0, 1.0),
805 ]);
806 assert_eq!(poly.ring_count(), 1);
807 }
808
809 #[test]
810 fn test_geometry_to_wkt_point() {
811 let g = Geometry::Point(Point::new(1.0, 2.0));
812 assert_eq!(g.to_wkt(), "POINT(1 2)");
813 }
814
815 #[test]
816 fn test_geometry_to_wkt_linestring() {
817 let g = Geometry::LineString(LineString::new(vec![
818 Point::new(0.0, 0.0),
819 Point::new(1.0, 1.0),
820 ]));
821 assert_eq!(g.to_wkt(), "LINESTRING(0 0, 1 1)");
822 }
823
824 #[test]
825 fn test_geometry_bounding_box_point() {
826 let g = Geometry::Point(Point::new(3.0, 5.0));
827 let bb = g.bounding_box().unwrap();
828 assert_eq!(bb, (3.0, 5.0, 3.0, 5.0));
829 }
830
831 #[test]
832 fn test_geometry_bounding_box_polygon() {
833 let poly = Polygon::new(vec![
834 Point::new(0.0, 0.0),
835 Point::new(4.0, 0.0),
836 Point::new(4.0, 3.0),
837 Point::new(0.0, 3.0),
838 ]);
839 let g = Geometry::Polygon(poly);
840 let bb = g.bounding_box().unwrap();
841 assert_eq!(bb, (0.0, 0.0, 4.0, 3.0));
842 }
843
844 #[test]
845 fn test_geometry_bounding_box_empty() {
846 let g = Geometry::MultiPoint(vec![]);
847 assert!(g.bounding_box().is_none());
848 }
849}