1use serde::{Deserialize, Serialize};
4
5fn haversine_distance(p1: &Point, p2: &Point) -> f64 {
10 const EARTH_RADIUS_KM: f64 = 6371.0;
11
12 let lat1 = p1.y.to_radians();
13 let lat2 = p2.y.to_radians();
14 let delta_lat = (p2.y - p1.y).to_radians();
15 let delta_lon = (p2.x - p1.x).to_radians();
16
17 let a =
18 (delta_lat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (delta_lon / 2.0).sin().powi(2);
19 let c = 2.0 * a.sqrt().atan2((1.0 - a).sqrt());
20
21 EARTH_RADIUS_KM * c * 1000.0 }
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct Point {
29 pub x: f64,
31 pub y: f64,
33 pub srid: i32, }
36
37impl Point {
38 pub fn new(x: f64, y: f64) -> Self {
51 Self { x, y, srid: 4326 } }
53 pub fn with_srid(x: f64, y: f64, srid: i32) -> Self {
55 Self { x, y, srid }
56 }
57 pub fn distance_to(&self, other: &Point) -> f64 {
63 if self.srid == 4326 && other.srid == 4326 {
65 haversine_distance(self, other)
66 } else if self.srid != other.srid {
67 eprintln!(
69 "Warning: Computing distance between points with different SRIDs ({} and {}). \
70 Consider transforming to a common SRID first for accurate results.",
71 self.srid, other.srid
72 );
73 ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
75 } else {
76 ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
78 }
79 }
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct LineString {
85 pub points: Vec<Point>,
87 pub srid: i32,
89}
90
91impl LineString {
92 pub fn length(&self) -> f64 {
97 self.points
98 .array_windows::<2>()
99 .map(|[p1, p2]| {
100 let dx = p2.x - p1.x;
101 let dy = p2.y - p1.y;
102 (dx * dx + dy * dy).sqrt()
103 })
104 .sum()
105 }
106 pub fn geodesic_length(&self) -> f64 {
109 if self.srid == 4326 {
110 self.points
112 .array_windows::<2>()
113 .map(|[p1, p2]| haversine_distance(p1, p2))
114 .sum()
115 } else {
116 self.length()
118 }
119 }
120}
121
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct Polygon {
125 pub exterior: Vec<Point>,
127 pub interiors: Vec<Vec<Point>>,
129 pub srid: i32,
131}
132
133impl Polygon {
134 pub fn area(&self) -> f64 {
138 if self.exterior.len() < 3 {
139 return 0.0;
140 }
141
142 let exterior_area = calculate_ring_area(&self.exterior);
144
145 let interior_area: f64 = self
147 .interiors
148 .iter()
149 .map(|ring| calculate_ring_area(ring))
150 .sum();
151
152 (exterior_area - interior_area).abs()
153 }
154 pub fn exterior_area(&self) -> f64 {
157 calculate_ring_area(&self.exterior)
158 }
159 pub fn perimeter(&self) -> f64 {
162 let mut total = calculate_ring_perimeter(&self.exterior);
163 for interior in &self.interiors {
164 total += calculate_ring_perimeter(interior);
165 }
166 total
167 }
168 pub fn contains_point(&self, point: &Point) -> bool {
171 point_in_polygon(point, &self.exterior)
172 }
173}
174
175fn calculate_ring_area(ring: &[Point]) -> f64 {
177 if ring.len() < 3 {
178 return 0.0;
179 }
180
181 let mut area = 0.0;
182 for i in 0..ring.len() {
183 let j = (i + 1) % ring.len();
184 area += ring[i].x * ring[j].y;
185 area -= ring[j].x * ring[i].y;
186 }
187 (area / 2.0).abs()
188}
189
190fn calculate_ring_perimeter(ring: &[Point]) -> f64 {
192 if ring.len() < 2 {
193 return 0.0;
194 }
195
196 let mut total = 0.0;
197 for i in 0..ring.len() {
198 let j = (i + 1) % ring.len();
199 let dx = ring[j].x - ring[i].x;
200 let dy = ring[j].y - ring[i].y;
201 total += (dx * dx + dy * dy).sqrt();
202 }
203 total
204}
205
206fn point_in_polygon(point: &Point, polygon: &[Point]) -> bool {
208 let mut inside = false;
209 let n = polygon.len();
210
211 for i in 0..n {
212 let j = (i + 1) % n;
213 let pi = &polygon[i];
214 let pj = &polygon[j];
215
216 if ((pi.y > point.y) != (pj.y > point.y))
217 && (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x)
218 {
219 inside = !inside;
220 }
221 }
222
223 inside
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct MultiPoint {
229 pub points: Vec<Point>,
231 pub srid: i32,
233}
234
235#[derive(Debug, Clone, Serialize, Deserialize)]
236pub struct MultiLineString {
238 pub lines: Vec<LineString>,
240 pub srid: i32,
242}
243
244#[derive(Debug, Clone, Serialize, Deserialize)]
245pub struct MultiPolygon {
247 pub polygons: Vec<Polygon>,
249 pub srid: i32,
251}
252
253pub trait SpatialOps {
257 fn contains(&self, other: &dyn SpatialOps) -> bool;
259
260 fn intersects(&self, other: &dyn SpatialOps) -> bool;
262
263 fn within(&self, other: &dyn SpatialOps) -> bool;
265
266 fn touches(&self, other: &dyn SpatialOps) -> bool;
268
269 fn distance(&self, other: &dyn SpatialOps) -> f64;
271
272 fn bbox(&self) -> BoundingBox;
274}
275
276#[derive(Debug, Clone)]
277pub struct BoundingBox {
279 pub min_x: f64,
281 pub min_y: f64,
283 pub max_x: f64,
285 pub max_y: f64,
287}
288
289impl BoundingBox {
290 pub fn contains_point(&self, point: &Point) -> bool {
293 point.x >= self.min_x
294 && point.x <= self.max_x
295 && point.y >= self.min_y
296 && point.y <= self.max_y
297 }
298 pub fn intersects(&self, other: &BoundingBox) -> bool {
301 !(self.max_x < other.min_x
302 || self.min_x > other.max_x
303 || self.max_y < other.min_y
304 || self.min_y > other.max_y)
305 }
306}
307
308pub enum SpatialLookup {
312 Contains(Point),
314 Within(Polygon),
316 Intersects(Polygon),
318 DWithin(Point, f64), BBContains(BoundingBox),
322 BBOverlaps(BoundingBox),
324}
325
326impl SpatialLookup {
327 pub fn to_sql(&self) -> String {
330 match self {
331 SpatialLookup::Contains(point) => {
332 format!(
333 "ST_Contains(geometry, ST_GeomFromText('POINT({} {})', 4326))",
334 point.x, point.y
335 )
336 }
337 SpatialLookup::DWithin(point, distance) => {
338 format!(
339 "ST_DWithin(geometry, ST_GeomFromText('POINT({} {})', 4326), {})",
340 point.x, point.y, distance
341 )
342 }
343 SpatialLookup::Within(polygon) => {
344 let coords = polygon
345 .exterior
346 .iter()
347 .map(|p| format!("{} {}", p.x, p.y))
348 .collect::<Vec<_>>()
349 .join(", ");
350 format!(
351 "ST_Within(geometry, ST_GeomFromText('POLYGON(({})))', 4326)",
352 coords
353 )
354 }
355 SpatialLookup::Intersects(polygon) => {
356 let coords = polygon
357 .exterior
358 .iter()
359 .map(|p| format!("{} {}", p.x, p.y))
360 .collect::<Vec<_>>()
361 .join(", ");
362 format!(
363 "ST_Intersects(geometry, ST_GeomFromText('POLYGON(({})))', 4326)",
364 coords
365 )
366 }
367 SpatialLookup::BBContains(bbox) => {
368 format!(
369 "geometry && ST_MakeEnvelope({}, {}, {}, {}, 4326)",
370 bbox.min_x, bbox.min_y, bbox.max_x, bbox.max_y
371 )
372 }
373 SpatialLookup::BBOverlaps(bbox) => {
374 format!(
375 "ST_Overlaps(geometry, ST_MakeEnvelope({}, {}, {}, {}, 4326))",
376 bbox.min_x, bbox.min_y, bbox.max_x, bbox.max_y
377 )
378 }
379 }
380 }
381}
382
383pub struct CoordinateTransform {
387 pub from_srid: i32,
389 pub to_srid: i32,
391}
392
393impl CoordinateTransform {
394 pub fn new(from_srid: i32, to_srid: i32) -> Self {
407 Self { from_srid, to_srid }
408 }
409 pub fn transform_point(&self, point: &Point) -> Point {
414 if self.from_srid == 4326 && self.to_srid == 3857 {
416 return wgs84_to_web_mercator(point);
417 }
418
419 if self.from_srid == 3857 && self.to_srid == 4326 {
421 return web_mercator_to_wgs84(point);
422 }
423
424 if self.from_srid == self.to_srid {
426 return point.clone();
427 }
428
429 eprintln!(
432 "Warning: Coordinate transformation from SRID {} to {} is not implemented. \
433 Consider using PROJ library for full support.",
434 self.from_srid, self.to_srid
435 );
436
437 Point {
438 x: point.x,
439 y: point.y,
440 srid: self.to_srid,
441 }
442 }
443 pub fn to_sql(&self, geometry_expr: &str) -> String {
446 format!("ST_Transform({}, {})", geometry_expr, self.to_srid)
447 }
448}
449
450fn wgs84_to_web_mercator(point: &Point) -> Point {
452 const EARTH_RADIUS: f64 = 6378137.0; let lon = point.x;
455 let lat = point.y;
456
457 let lat = lat.clamp(-85.0511, 85.0511);
459
460 let x = lon.to_radians() * EARTH_RADIUS;
461 let y = ((std::f64::consts::PI / 4.0 + lat.to_radians() / 2.0).tan()).ln() * EARTH_RADIUS;
462
463 Point { x, y, srid: 3857 }
464}
465
466fn web_mercator_to_wgs84(point: &Point) -> Point {
468 const EARTH_RADIUS: f64 = 6378137.0;
469
470 let x = point.x;
471 let y = point.y;
472
473 let lon = (x / EARTH_RADIUS).to_degrees();
474 let lat = (2.0 * ((y / EARTH_RADIUS).exp()).atan() - std::f64::consts::PI / 2.0).to_degrees();
475
476 Point {
477 x: lon,
478 y: lat,
479 srid: 4326,
480 }
481}
482
483pub struct GiSTIndex {
487 pub column: String,
489}
490
491impl GiSTIndex {
492 pub fn new(column: impl Into<String>) -> Self {
504 Self {
505 column: column.into(),
506 }
507 }
508 pub fn create_sql(&self, table: &str, index_name: &str) -> String {
511 format!(
512 "CREATE INDEX {} ON {} USING GIST ({})",
513 index_name, table, self.column
514 )
515 }
516}
517
518pub struct Distance {
522 value: f64,
523 unit: DistanceUnit,
524}
525
526#[derive(Debug, Clone, Copy)]
527pub enum DistanceUnit {
529 Meters,
531 Kilometers,
533 Miles,
535 Feet,
537}
538
539impl Distance {
540 pub fn new(value: f64, unit: DistanceUnit) -> Self {
551 Self { value, unit }
552 }
553 pub fn km(value: f64) -> Self {
556 Self::new(value, DistanceUnit::Kilometers)
557 }
558 pub fn m(value: f64) -> Self {
561 Self::new(value, DistanceUnit::Meters)
562 }
563 pub fn mi(value: f64) -> Self {
566 Self::new(value, DistanceUnit::Miles)
567 }
568 pub fn ft(value: f64) -> Self {
571 Self::new(value, DistanceUnit::Feet)
572 }
573 pub fn to_meters(&self) -> f64 {
576 match self.unit {
577 DistanceUnit::Meters => self.value,
578 DistanceUnit::Kilometers => self.value * 1000.0,
579 DistanceUnit::Miles => self.value * 1609.34,
580 DistanceUnit::Feet => self.value * 0.3048,
581 }
582 }
583}
584
585pub struct SpatialAggregate;
588
589impl SpatialAggregate {
590 pub fn union_sql(column: &str) -> String {
593 format!("ST_Union({})", column)
594 }
595 pub fn collect_sql(column: &str) -> String {
598 format!("ST_Collect({})", column)
599 }
600 pub fn extent_sql(column: &str) -> String {
603 format!("ST_Extent({})", column)
604 }
605}
606#[cfg(test)]
611mod tests {
612 use super::*;
613
614 #[test]
615 fn test_point_creation() {
616 let point = Point::new(10.0, 20.0);
617 assert_eq!(point.x, 10.0);
618 assert_eq!(point.y, 20.0);
619 assert_eq!(point.srid, 4326);
620 }
621
622 #[test]
623 fn test_point_distance() {
624 let p1 = Point::with_srid(0.0, 0.0, 0);
626 let p2 = Point::with_srid(3.0, 4.0, 0);
627 assert_eq!(p1.distance_to(&p2), 5.0);
628 }
629
630 #[test]
631 fn test_distance_conversions() {
632 let d = Distance::km(1.0);
633 assert_eq!(d.to_meters(), 1000.0);
634
635 let d2 = Distance::mi(1.0);
636 assert!((d2.to_meters() - 1609.34).abs() < 0.01);
637 }
638
639 #[test]
640 fn test_gist_index_sql() {
641 let index = GiSTIndex::new("location");
642 let sql = index.create_sql("places", "places_location_idx");
643 assert!(sql.contains("GIST"));
644 assert!(sql.contains("location"));
645 }
646
647 #[test]
648 fn test_spatial_lookup_sql() {
649 let point = Point::new(10.0, 20.0);
650 let lookup = SpatialLookup::Contains(point);
651 let sql = lookup.to_sql();
652 assert!(sql.contains("ST_Contains"));
653 assert!(sql.contains("POINT(10 20)"));
654 }
655
656 #[test]
658 fn test_point_with_custom_srid() {
659 let point = Point::with_srid(10.0, 20.0, 3857);
660 assert_eq!(point.srid, 3857);
661 assert_eq!(point.x, 10.0);
662 assert_eq!(point.y, 20.0);
663 }
664
665 #[test]
666 fn test_linestring_length() {
667 let line = LineString {
668 points: vec![
669 Point::new(0.0, 0.0),
670 Point::new(3.0, 0.0),
671 Point::new(3.0, 4.0),
672 ],
673 srid: 4326,
674 };
675 assert_eq!(line.length(), 7.0); }
677
678 #[test]
679 fn test_linestring_empty() {
680 let line = LineString {
681 points: vec![],
682 srid: 4326,
683 };
684 assert_eq!(line.length(), 0.0);
685 }
686
687 #[test]
688 fn test_polygon_area() {
689 let polygon = Polygon {
690 exterior: vec![
691 Point::new(0.0, 0.0),
692 Point::new(4.0, 0.0),
693 Point::new(4.0, 3.0),
694 Point::new(0.0, 3.0),
695 Point::new(0.0, 0.0),
696 ],
697 interiors: vec![],
698 srid: 4326,
699 };
700 assert_eq!(polygon.area(), 12.0);
701 }
702
703 #[test]
704 fn test_polygon_with_hole() {
705 let polygon = Polygon {
706 exterior: vec![
707 Point::new(0.0, 0.0),
708 Point::new(10.0, 0.0),
709 Point::new(10.0, 10.0),
710 Point::new(0.0, 10.0),
711 Point::new(0.0, 0.0),
712 ],
713 interiors: vec![vec![
714 Point::new(2.0, 2.0),
715 Point::new(8.0, 2.0),
716 Point::new(8.0, 8.0),
717 Point::new(2.0, 8.0),
718 Point::new(2.0, 2.0),
719 ]],
720 srid: 4326,
721 };
722 assert_eq!(polygon.interiors.len(), 1);
723 assert_eq!(polygon.interiors[0].len(), 5);
724 }
725
726 #[test]
727 fn test_multipoint_creation() {
728 let mp = MultiPoint {
729 points: vec![
730 Point::new(0.0, 0.0),
731 Point::new(1.0, 1.0),
732 Point::new(2.0, 2.0),
733 ],
734 srid: 4326,
735 };
736 assert_eq!(mp.points.len(), 3);
737 }
738
739 #[test]
740 fn test_bounding_box_contains_point() {
741 let bbox = BoundingBox {
742 min_x: 0.0,
743 min_y: 0.0,
744 max_x: 10.0,
745 max_y: 10.0,
746 };
747
748 assert!(bbox.contains_point(&Point::new(5.0, 5.0)));
749 assert!(bbox.contains_point(&Point::new(0.0, 0.0)));
750 assert!(bbox.contains_point(&Point::new(10.0, 10.0)));
751 assert!(!bbox.contains_point(&Point::new(-1.0, 5.0)));
752 assert!(!bbox.contains_point(&Point::new(11.0, 5.0)));
753 assert!(!bbox.contains_point(&Point::new(5.0, 11.0)));
754 }
755
756 #[test]
757 fn test_bounding_box_intersects() {
758 let bbox1 = BoundingBox {
759 min_x: 0.0,
760 min_y: 0.0,
761 max_x: 10.0,
762 max_y: 10.0,
763 };
764
765 let bbox2 = BoundingBox {
766 min_x: 5.0,
767 min_y: 5.0,
768 max_x: 15.0,
769 max_y: 15.0,
770 };
771
772 assert!(bbox1.intersects(&bbox2));
773
774 let bbox3 = BoundingBox {
775 min_x: 20.0,
776 min_y: 20.0,
777 max_x: 30.0,
778 max_y: 30.0,
779 };
780
781 assert!(!bbox1.intersects(&bbox3));
782 }
783
784 #[test]
785 fn test_distance_meters() {
786 let d = Distance::m(500.0);
787 assert_eq!(d.to_meters(), 500.0);
788 }
789
790 #[test]
791 fn test_distance_feet() {
792 let d = Distance::ft(100.0);
793 assert!((d.to_meters() - 30.48).abs() < 0.01);
794 }
795
796 #[test]
797 fn test_coordinate_transform_creation() {
798 let transform = CoordinateTransform {
799 from_srid: 4326,
800 to_srid: 3857,
801 };
802 assert_eq!(transform.from_srid, 4326);
803 assert_eq!(transform.to_srid, 3857);
804 }
805
806 #[test]
807 fn test_spatial_lookup_contains() {
808 let point = Point::new(5.0, 5.0);
809 let lookup = SpatialLookup::Contains(point);
810 matches!(lookup, SpatialLookup::Contains(_));
811 }
812
813 #[test]
814 fn test_spatial_lookup_within() {
815 let polygon = Polygon {
816 exterior: vec![
817 Point::new(0.0, 0.0),
818 Point::new(10.0, 0.0),
819 Point::new(10.0, 10.0),
820 Point::new(0.0, 10.0),
821 Point::new(0.0, 0.0),
822 ],
823 interiors: vec![],
824 srid: 4326,
825 };
826 let lookup = SpatialLookup::Within(polygon);
827 matches!(lookup, SpatialLookup::Within(_));
828 }
829
830 #[test]
831 fn test_spatial_lookup_intersects() {
832 let polygon = Polygon {
833 exterior: vec![
834 Point::new(0.0, 0.0),
835 Point::new(10.0, 0.0),
836 Point::new(10.0, 10.0),
837 Point::new(0.0, 10.0),
838 Point::new(0.0, 0.0),
839 ],
840 interiors: vec![],
841 srid: 4326,
842 };
843 let lookup = SpatialLookup::Intersects(polygon);
844 matches!(lookup, SpatialLookup::Intersects(_));
845 }
846
847 #[test]
848 fn test_spatial_lookup_dwithin() {
849 let point = Point::new(0.0, 0.0);
850 let lookup = SpatialLookup::DWithin(point, 1000.0);
851 matches!(lookup, SpatialLookup::DWithin(_, _));
852 }
853
854 #[test]
855 fn test_gist_index_with_different_column() {
856 let index = GiSTIndex::new("geometry");
857 let sql = index.create_sql("shapes", "shapes_geom_idx");
858 assert!(sql.contains("geometry"));
859 assert!(sql.contains("shapes"));
860 assert!(sql.contains("shapes_geom_idx"));
861 }
862
863 #[test]
864 fn test_multilinestring_total_length() {
865 let mls = MultiLineString {
866 lines: vec![
867 LineString {
868 points: vec![Point::new(0.0, 0.0), Point::new(5.0, 0.0)],
869 srid: 4326,
870 },
871 LineString {
872 points: vec![Point::new(0.0, 0.0), Point::new(0.0, 3.0)],
873 srid: 4326,
874 },
875 ],
876 srid: 4326,
877 };
878
879 let total_length: f64 = mls.lines.iter().map(|l| l.length()).sum();
880 assert_eq!(total_length, 8.0); }
882
883 #[test]
884 fn test_multipolygon_total_area() {
885 let mp = MultiPolygon {
886 polygons: vec![
887 Polygon {
888 exterior: vec![
889 Point::new(0.0, 0.0),
890 Point::new(5.0, 0.0),
891 Point::new(5.0, 5.0),
892 Point::new(0.0, 5.0),
893 Point::new(0.0, 0.0),
894 ],
895 interiors: vec![],
896 srid: 4326,
897 },
898 Polygon {
899 exterior: vec![
900 Point::new(10.0, 10.0),
901 Point::new(13.0, 10.0),
902 Point::new(13.0, 14.0),
903 Point::new(10.0, 14.0),
904 Point::new(10.0, 10.0),
905 ],
906 interiors: vec![],
907 srid: 4326,
908 },
909 ],
910 srid: 4326,
911 };
912
913 let total_area = mp.polygons.iter().map(|p| p.area()).sum::<f64>();
914 assert_eq!(total_area, 37.0); }
916
917 #[test]
918 fn test_point_distance_negative_coords() {
919 let p1 = Point::with_srid(-3.0, -4.0, 0);
921 let p2 = Point::with_srid(0.0, 0.0, 0);
922 assert_eq!(p1.distance_to(&p2), 5.0);
923 }
924
925 #[test]
926 fn test_point_distance_same_point() {
927 let p = Point::new(5.0, 5.0);
928 assert_eq!(p.distance_to(&p), 0.0);
929 }
930
931 #[test]
932 fn test_linestring_single_point() {
933 let line = LineString {
934 points: vec![Point::new(0.0, 0.0)],
935 srid: 4326,
936 };
937 assert_eq!(line.length(), 0.0);
938 }
939
940 #[test]
941 fn test_complex_polygon_with_multiple_holes() {
942 let polygon = Polygon {
943 exterior: vec![
944 Point::new(0.0, 0.0),
945 Point::new(20.0, 0.0),
946 Point::new(20.0, 20.0),
947 Point::new(0.0, 20.0),
948 Point::new(0.0, 0.0),
949 ],
950 interiors: vec![
951 vec![
952 Point::new(2.0, 2.0),
953 Point::new(5.0, 2.0),
954 Point::new(5.0, 5.0),
955 Point::new(2.0, 5.0),
956 Point::new(2.0, 2.0),
957 ],
958 vec![
959 Point::new(10.0, 10.0),
960 Point::new(15.0, 10.0),
961 Point::new(15.0, 15.0),
962 Point::new(10.0, 15.0),
963 Point::new(10.0, 10.0),
964 ],
965 ],
966 srid: 4326,
967 };
968
969 assert_eq!(polygon.interiors.len(), 2);
970 assert_eq!(polygon.area(), 366.0);
972 }
973
974 #[test]
975 fn test_spatial_reference_systems() {
976 let wgs84_point = Point::new(139.6917, 35.6895); assert_eq!(wgs84_point.srid, 4326);
978
979 let web_mercator_point = Point::with_srid(15540445.0, 4253018.0, 3857);
980 assert_eq!(web_mercator_point.srid, 3857);
981 }
982
983 #[test]
984 fn test_distance_unit_conversions() {
985 let km = Distance::km(1.0);
986 let m = Distance::m(1000.0);
987
988 assert_eq!(km.to_meters(), 1000.0);
989 assert_eq!(m.to_meters(), 1000.0);
990 }
991
992 #[test]
993 fn test_wgs84_to_web_mercator() {
994 let wgs84_point = Point::with_srid(0.0, 0.0, 4326); let transform = CoordinateTransform::new(4326, 3857);
997 let web_mercator = transform.transform_point(&wgs84_point);
998
999 assert_eq!(web_mercator.srid, 3857);
1000 assert!((web_mercator.x - 0.0).abs() < 0.01);
1001 assert!((web_mercator.y - 0.0).abs() < 0.01);
1002 }
1003
1004 #[test]
1005 fn test_web_mercator_to_wgs84() {
1006 let web_mercator = Point::with_srid(0.0, 0.0, 3857);
1008 let transform = CoordinateTransform::new(3857, 4326);
1009 let wgs84 = transform.transform_point(&web_mercator);
1010
1011 assert_eq!(wgs84.srid, 4326);
1012 assert!((wgs84.x - 0.0).abs() < 0.01);
1013 assert!((wgs84.y - 0.0).abs() < 0.01);
1014 }
1015
1016 #[test]
1017 fn test_coordinate_transform_tokyo() {
1018 let tokyo_wgs84 = Point::with_srid(139.6917, 35.6895, 4326);
1020 let transform = CoordinateTransform::new(4326, 3857);
1021 let tokyo_web_mercator = transform.transform_point(&tokyo_wgs84);
1022
1023 assert_eq!(tokyo_web_mercator.srid, 3857);
1024 assert!(tokyo_web_mercator.x > 15500000.0 && tokyo_web_mercator.x < 15600000.0);
1026 assert!(tokyo_web_mercator.y > 4200000.0 && tokyo_web_mercator.y < 4300000.0);
1027
1028 let transform_back = CoordinateTransform::new(3857, 4326);
1030 let tokyo_back = transform_back.transform_point(&tokyo_web_mercator);
1031 assert!((tokyo_back.x - 139.6917).abs() < 0.01);
1032 assert!((tokyo_back.y - 35.6895).abs() < 0.01);
1033 }
1034
1035 #[test]
1036 fn test_polygon_with_holes_area() {
1037 let polygon = Polygon {
1039 exterior: vec![
1040 Point::new(0.0, 0.0),
1041 Point::new(100.0, 0.0),
1042 Point::new(100.0, 100.0),
1043 Point::new(0.0, 100.0),
1044 Point::new(0.0, 0.0),
1045 ],
1046 interiors: vec![vec![
1047 Point::new(40.0, 40.0),
1048 Point::new(60.0, 40.0),
1049 Point::new(60.0, 60.0),
1050 Point::new(40.0, 60.0),
1051 Point::new(40.0, 40.0),
1052 ]],
1053 srid: 4326,
1054 };
1055
1056 assert_eq!(polygon.area(), 9600.0);
1058 assert_eq!(polygon.exterior_area(), 10000.0);
1059 }
1060
1061 #[test]
1062 fn test_polygon_perimeter() {
1063 let polygon = Polygon {
1065 exterior: vec![
1066 Point::new(0.0, 0.0),
1067 Point::new(3.0, 0.0),
1068 Point::new(3.0, 4.0),
1069 Point::new(0.0, 4.0),
1070 Point::new(0.0, 0.0),
1071 ],
1072 interiors: vec![],
1073 srid: 4326,
1074 };
1075
1076 assert_eq!(polygon.perimeter(), 14.0);
1078 }
1079
1080 #[test]
1081 fn test_point_in_polygon() {
1082 let polygon = Polygon {
1083 exterior: vec![
1084 Point::new(0.0, 0.0),
1085 Point::new(10.0, 0.0),
1086 Point::new(10.0, 10.0),
1087 Point::new(0.0, 10.0),
1088 Point::new(0.0, 0.0),
1089 ],
1090 interiors: vec![],
1091 srid: 4326,
1092 };
1093
1094 assert!(polygon.contains_point(&Point::new(5.0, 5.0)));
1095 assert!(!polygon.contains_point(&Point::new(15.0, 5.0)));
1096 assert!(!polygon.contains_point(&Point::new(-1.0, 5.0)));
1097 }
1098
1099 #[test]
1100 fn test_linestring_geodesic_length() {
1101 let line = LineString {
1103 points: vec![Point::new(0.0, 0.0), Point::new(0.0, 1.0)],
1104 srid: 4326,
1105 };
1106
1107 let planar = line.length();
1108 let geodesic = line.geodesic_length();
1109
1110 assert!(geodesic > 100000.0); assert!(planar < geodesic); }
1114}