1use geonative_core::{Coord, LineString, Polygon};
24
25pub fn distance(a: Coord, b: Coord) -> f64 {
28 let dx = b.x - a.x;
29 let dy = b.y - a.y;
30 (dx * dx + dy * dy).sqrt()
31}
32
33pub fn length(ls: &LineString) -> f64 {
36 if ls.coords.len() < 2 {
37 return 0.0;
38 }
39 let mut total = 0.0;
40 for w in ls.coords.windows(2) {
41 total += distance(w[0], w[1]);
42 }
43 total
44}
45
46pub fn signed_area(ring: &[Coord]) -> f64 {
50 if ring.len() < 3 {
51 return 0.0;
52 }
53 let mut sum = 0.0;
54 let n = ring.len();
55 for i in 0..n {
56 let j = (i + 1) % n;
57 sum += ring[i].x * ring[j].y - ring[j].x * ring[i].y;
58 }
59 sum * 0.5
60}
61
62pub fn ring_area(ring: &[Coord]) -> f64 {
64 signed_area(ring).abs()
65}
66
67pub fn area(p: &Polygon) -> f64 {
71 let exterior = ring_area(&p.exterior.coords);
72 let holes: f64 = p.holes.iter().map(|h| ring_area(&h.coords)).sum();
73 (exterior - holes).max(0.0)
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 fn xy(x: f64, y: f64) -> Coord {
81 Coord {
82 x,
83 y,
84 z: None,
85 m: None,
86 }
87 }
88
89 #[test]
90 fn distance_matches_pythagoras() {
91 assert!((distance(xy(0.0, 0.0), xy(3.0, 4.0)) - 5.0).abs() < 1e-12);
92 assert_eq!(distance(xy(1.0, 1.0), xy(1.0, 1.0)), 0.0);
93 }
94
95 #[test]
96 fn length_of_two_segment_line() {
97 let ls = LineString::new(vec![xy(0.0, 0.0), xy(3.0, 0.0), xy(3.0, 4.0)]);
98 assert!((length(&ls) - 7.0).abs() < 1e-12);
99 }
100
101 #[test]
102 fn length_of_short_input_is_zero() {
103 assert_eq!(length(&LineString::default()), 0.0);
104 assert_eq!(length(&LineString::new(vec![xy(1.0, 1.0)])), 0.0);
105 }
106
107 #[test]
108 fn signed_area_unit_square_ccw_is_one() {
109 let ring = vec![
110 xy(0.0, 0.0),
111 xy(1.0, 0.0),
112 xy(1.0, 1.0),
113 xy(0.0, 1.0),
114 xy(0.0, 0.0),
115 ];
116 assert!((signed_area(&ring) - 1.0).abs() < 1e-12);
117 }
118
119 #[test]
120 fn signed_area_unit_square_cw_is_minus_one() {
121 let mut ring = vec![
122 xy(0.0, 0.0),
123 xy(1.0, 0.0),
124 xy(1.0, 1.0),
125 xy(0.0, 1.0),
126 xy(0.0, 0.0),
127 ];
128 ring.reverse();
129 assert!((signed_area(&ring) - (-1.0)).abs() < 1e-12);
130 }
131
132 #[test]
133 fn ring_area_is_unsigned() {
134 let ring = vec![
135 xy(0.0, 0.0),
136 xy(1.0, 0.0),
137 xy(1.0, 1.0),
138 xy(0.0, 1.0),
139 xy(0.0, 0.0),
140 ];
141 assert!((ring_area(&ring) - 1.0).abs() < 1e-12);
142 let mut reversed = ring.clone();
143 reversed.reverse();
144 assert!((ring_area(&reversed) - 1.0).abs() < 1e-12);
145 }
146
147 #[test]
148 fn polygon_area_subtracts_holes() {
149 let p = Polygon::new(
150 LineString::new(vec![
151 xy(0.0, 0.0),
152 xy(10.0, 0.0),
153 xy(10.0, 10.0),
154 xy(0.0, 10.0),
155 xy(0.0, 0.0),
156 ]),
157 vec![LineString::new(vec![
158 xy(2.0, 2.0),
159 xy(4.0, 2.0),
160 xy(4.0, 4.0),
161 xy(2.0, 4.0),
162 xy(2.0, 2.0),
163 ])],
164 );
165 assert!((area(&p) - 96.0).abs() < 1e-12);
167 }
168
169 #[test]
170 fn polygon_with_multiple_holes_sums_them() {
171 let p = Polygon::new(
172 LineString::new(vec![
173 xy(0.0, 0.0),
174 xy(10.0, 0.0),
175 xy(10.0, 10.0),
176 xy(0.0, 10.0),
177 xy(0.0, 0.0),
178 ]),
179 vec![
180 LineString::new(vec![
181 xy(1.0, 1.0),
182 xy(2.0, 1.0),
183 xy(2.0, 2.0),
184 xy(1.0, 2.0),
185 xy(1.0, 1.0),
186 ]),
187 LineString::new(vec![
188 xy(5.0, 5.0),
189 xy(8.0, 5.0),
190 xy(8.0, 8.0),
191 xy(5.0, 8.0),
192 xy(5.0, 5.0),
193 ]),
194 ],
195 );
196 assert!((area(&p) - 90.0).abs() < 1e-12);
198 }
199
200 #[test]
201 fn degenerate_ring_zero_area() {
202 assert_eq!(signed_area(&[]), 0.0);
203 assert_eq!(signed_area(&[xy(0.0, 0.0)]), 0.0);
204 assert_eq!(signed_area(&[xy(0.0, 0.0), xy(1.0, 1.0)]), 0.0);
205 }
206}