1use crate::geomath::ang_diff;
2use crate::geomath::ang_normalize;
3use crate::Geodesic;
4
5use crate::geodesic_capability as caps;
6
7const POLYGONAREA_MASK: u64 =
8 caps::LATITUDE | caps::LONGITUDE | caps::DISTANCE | caps::AREA | caps::LONG_UNROLL;
9
10#[cfg(feature = "accurate")]
11use accurate::traits::*;
12
13#[derive(Debug, Default, Copy, Clone)]
18pub enum Winding {
19 Clockwise,
20 #[default]
21 CounterClockwise,
22}
23
24#[derive(Debug, Clone)]
26pub struct PolygonArea<'a> {
27 geoid: &'a Geodesic,
28 winding: Winding,
29 num: usize,
30
31 #[cfg(not(feature = "accurate"))]
32 areasum: f64,
33
34 #[cfg(feature = "accurate")]
35 areasum: accurate::sum::Sum2<f64>,
36
37 #[cfg(not(feature = "accurate"))]
38 perimetersum: f64,
39
40 #[cfg(feature = "accurate")]
41 perimetersum: accurate::sum::Sum2<f64>,
42
43 crossings: i64,
44 initial_lat: f64,
45 initial_lon: f64,
46 latest_lat: f64,
47 latest_lon: f64,
48}
49
50impl<'a> PolygonArea<'a> {
71 pub fn new(geoid: &'a Geodesic, winding: Winding) -> Self {
73 PolygonArea {
74 geoid,
75 winding,
76
77 num: 0,
78
79 #[cfg(not(feature = "accurate"))]
80 areasum: 0.0,
81
82 #[cfg(feature = "accurate")]
83 areasum: accurate::sum::Sum2::zero(),
84
85 #[cfg(not(feature = "accurate"))]
86 perimetersum: 0.0,
87
88 #[cfg(feature = "accurate")]
89 perimetersum: accurate::sum::Sum2::zero(),
90
91 crossings: 0,
92 initial_lat: 0.0,
93 initial_lon: 0.0,
94 latest_lat: 0.0,
95 latest_lon: 0.0,
96 }
97 }
98
99 pub fn add_point(&mut self, lat: f64, lon: f64) {
101 if self.num == 0 {
102 self.initial_lat = lat;
103 self.initial_lon = lon;
104 } else {
105 #[allow(non_snake_case)]
106 let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, S12) = self
107 .geoid
108 ._gen_inverse(self.latest_lat, self.latest_lon, lat, lon, POLYGONAREA_MASK);
109 self.perimetersum += s12;
110 self.areasum += S12;
111 self.crossings += PolygonArea::transit(self.latest_lon, lon);
112 }
113 self.latest_lat = lat;
114 self.latest_lon = lon;
115 self.num += 1;
116 }
117
118 pub fn add_edge(&mut self, azimuth: f64, distance: f64) {
123 if self.num == 0 {
124 panic!("PolygonArea::add_edge: No points added yet");
125 }
126
127 #[allow(non_snake_case)]
128 let (_a12, lat, lon, _azi2, _s12, _m12, _M12, _M21, S12) = self.geoid._gen_direct(
129 self.latest_lat,
130 self.latest_lon,
131 azimuth,
132 false,
133 distance,
134 POLYGONAREA_MASK,
135 );
136 self.perimetersum += distance;
137 self.areasum += S12;
138 self.crossings += PolygonArea::transitdirect(self.latest_lon, lon);
139 self.latest_lat = lat;
140 self.latest_lon = lon;
141 self.num += 1;
142 }
143
144 pub fn compute(mut self, sign: bool) -> (f64, f64, usize) {
180 #[allow(non_snake_case)]
181 let (_a12, s12, _salp1, _calp1, _salp2, _calp2, _m12, _M12, _M21, S12) =
182 self.geoid._gen_inverse(
183 self.latest_lat,
184 self.latest_lon,
185 self.initial_lat,
186 self.initial_lon,
187 POLYGONAREA_MASK,
188 );
189 self.perimetersum += s12;
190 self.areasum += S12;
191
192 let perimetersum;
193 let areasum;
194
195 #[cfg(not(feature = "accurate"))]
196 {
197 perimetersum = self.perimetersum;
198 areasum = self.areasum;
199 }
200 #[cfg(feature = "accurate")]
201 {
202 perimetersum = self.perimetersum.sum();
203 areasum = self.areasum.sum();
204 }
205
206 self.crossings += PolygonArea::transit(self.latest_lon, self.initial_lon);
207
208 let areasum = self.reduce_area(areasum, sign);
210
211 (perimetersum, areasum, self.num)
212 }
213
214 pub fn test_point(&self, lat: f64, lon: f64, sign: bool) -> (f64, f64, usize) {
216 let mut pa = self.clone();
217 pa.add_point(lat, lon);
218 pa.compute(sign)
219 }
220
221 pub fn test_edge(&self, azimuth: f64, distance: f64, sign: bool) -> (f64, f64, usize) {
223 let mut pa = self.clone();
224 pa.add_edge(azimuth, distance);
225 pa.compute(sign)
226 }
227
228 fn transit(lon1: f64, lon2: f64) -> i64 {
231 let (lon12, _lon12s) = ang_diff(lon1, lon2);
232 let lon1 = ang_normalize(lon1);
233 let lon2 = ang_normalize(lon2);
234
235 if lon12 > 0.0 && ((lon1 < 0.0 && lon2 >= 0.0) || (lon1 > 0.0 && lon2 == 0.0)) {
243 1
244 } else if lon12 < 0.0 && lon1 >= 0.0 && lon2 < 0.0 {
245 -1
246 } else {
247 0
248 }
249 }
250
251 fn transitdirect(lon1: f64, lon2: f64) -> i64 {
252 let lon1 = lon1 % 720.0;
254 let lon2 = lon2 % 720.0;
255
256 let a = if 0.0 <= lon2 && lon2 < 360.0 { 0 } else { 1 };
257
258 let b = if 0.0 <= lon1 && lon1 < 360.0 { 0 } else { 1 };
259
260 a - b
261 }
262
263 fn reduce_area(&self, area: f64, signed: bool) -> f64 {
264 let geoid_area = self.geoid.area(); let mut area = area % geoid_area;
266
267 if self.crossings % 2 != 0 {
270 if area < 0.0 {
271 area += geoid_area / 2.0;
272 } else {
273 area -= geoid_area / 2.0;
274 }
275 }
276
277 area = match self.winding {
279 Winding::Clockwise => area,
280 Winding::CounterClockwise => -area,
281 };
282
283 if signed {
284 if area > geoid_area / 2.0 {
286 area -= geoid_area;
287 } else if area <= -geoid_area / 2.0 {
288 area += geoid_area;
289 }
290 } else {
291 if area < 0.0 {
293 area += geoid_area;
294 }
295 }
296
297 area
298 }
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304 use crate::Geodesic;
305 use approx::assert_relative_eq;
306
307 #[test]
308 fn test_simple_polygonarea() {
309 let geoid = Geodesic::wgs84();
310 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
311
312 pa.add_point(0.0, 0.0);
313 pa.add_point(0.0, 1.0);
314 pa.add_point(1.0, 1.0);
315 pa.add_point(1.0, 0.0);
316
317 let (perimeter, area, _count) = pa.compute(true);
318
319 assert_relative_eq!(perimeter, 443770.917, epsilon = 1.0e-3);
320 assert_relative_eq!(area, 12308778361.469, epsilon = 1.0e-3);
321
322 let mut pa = PolygonArea::new(&geoid, Winding::Clockwise);
323
324 pa.add_point(0.0, 0.0);
325 pa.add_point(0.0, 1.0);
326 pa.add_point(1.0, 1.0);
327 pa.add_point(1.0, 0.0);
328
329 let (perimeter, area, _count) = pa.compute(true);
330
331 assert_relative_eq!(perimeter, 443770.917, epsilon = 1.0e-3);
332 assert_relative_eq!(area, -12308778361.469, epsilon = 1.0e-3);
333 }
334
335 #[test]
336 fn test_planimeter0() {
337 let geoid = Geodesic::wgs84();
340
341 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
342 pa.add_point(89.0, 0.0);
343 pa.add_point(89.0, 90.0);
344 pa.add_point(89.0, 180.0);
345 pa.add_point(89.0, 270.0);
346 let (perimeter, area, _count) = pa.compute(true);
347 assert_relative_eq!(perimeter, 631819.8745, epsilon = 1.0e-4);
348 assert_relative_eq!(area, 24952305678.0, epsilon = 1.0);
349
350 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
351 pa.add_point(-89.0, 0.0);
352 pa.add_point(-89.0, 90.0);
353 pa.add_point(-89.0, 180.0);
354 pa.add_point(-89.0, 270.0);
355 let (perimeter, area, _count) = pa.compute(true);
356 assert_relative_eq!(perimeter, 631819.8745, epsilon = 1.0e-4);
357 assert_relative_eq!(area, -24952305678.0, epsilon = 1.0);
358
359 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
360 pa.add_point(0.0, -1.0);
361 pa.add_point(-1.0, 0.0);
362 pa.add_point(0.0, 1.0);
363 pa.add_point(1.0, 0.0);
364 let (perimeter, area, _count) = pa.compute(true);
365 assert_relative_eq!(perimeter, 627598.2731, epsilon = 1.0e-4);
366 assert_relative_eq!(area, 24619419146.0, epsilon = 1.0);
367
368 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
369 pa.add_point(90.0, 0.0);
370 pa.add_point(0.0, 0.0);
371 pa.add_point(0.0, 90.0);
372 let (perimeter, area, _count) = pa.compute(true);
373 assert_relative_eq!(perimeter, 30022685.0, epsilon = 1.0);
374 assert_relative_eq!(area, 63758202715511.0, epsilon = 1.0);
375 }
376
377 #[test]
378 fn test_planimeter5() {
379 let geoid = Geodesic::wgs84();
382 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
383 pa.add_point(89.0, 0.1);
384 pa.add_point(89.0, 90.1);
385 pa.add_point(89.0, -179.9);
386 let (perimeter, area, _) = pa.compute(true);
387 assert_relative_eq!(perimeter, 539297.0, epsilon = 1.0);
388 assert_relative_eq!(area, 12476152838.5, epsilon = 1.0);
389 }
390
391 #[test]
392 fn test_planimeter6() {
393 let geoid = Geodesic::wgs84();
396
397 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
398 pa.add_point(9.0, -0.00000000000001);
399 pa.add_point(9.0, 180.0);
400 pa.add_point(9.0, 0.0);
401 let (perimeter, area, _) = pa.compute(true);
402 assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0);
403 assert_relative_eq!(area, 0.0, epsilon = 1.0);
404
405 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
406 pa.add_point(9.0, 0.00000000000001);
407 pa.add_point(9.0, 0.0);
408 pa.add_point(9.0, 180.0);
409 let (perimeter, area, _) = pa.compute(true);
410 assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0);
411 assert_relative_eq!(area, 0.0, epsilon = 1.0);
412
413 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
414 pa.add_point(9.0, 0.00000000000001);
415 pa.add_point(9.0, 180.0);
416 pa.add_point(9.0, 0.0);
417 let (perimeter, area, _) = pa.compute(true);
418 assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0);
419 assert_relative_eq!(area, 0.0, epsilon = 1.0);
420
421 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
422 pa.add_point(9.0, -0.00000000000001);
423 pa.add_point(9.0, 0.0);
424 pa.add_point(9.0, 180.0);
425 let (perimeter, area, _) = pa.compute(true);
426 assert_relative_eq!(perimeter, 36026861.0, epsilon = 1.0);
427 assert_relative_eq!(area, 0.0, epsilon = 1.0);
428 }
429
430 #[test]
431 fn test_planimeter12() {
432 let geoid = Geodesic::wgs84();
434
435 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
436 pa.add_point(66.562222222, 0.0);
437 pa.add_point(66.562222222, 180.0);
438 pa.add_point(66.562222222, 360.0);
439 let (perimeter, area, _) = pa.compute(true);
440 assert_relative_eq!(perimeter, 10465729.0, epsilon = 1.0);
441 assert_relative_eq!(area, 0.0);
442 }
443
444 #[test]
445 fn test_planimeter12r() {
446 let geoid = Geodesic::wgs84();
448
449 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
450
451 pa.add_point(66.562222222, -0.0);
452 pa.add_point(66.562222222, -180.0);
453 pa.add_point(66.562222222, -360.0);
454
455 let (perimeter, area, _) = pa.compute(true);
456 assert_relative_eq!(perimeter, 10465729.0, epsilon = 1.0);
457 assert_relative_eq!(area, 0.0);
458 }
459
460 #[test]
461 fn test_planimeter13() {
462 let geoid = Geodesic::wgs84();
465
466 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
467 pa.add_point(89.0, -360.0);
468 pa.add_point(89.0, -240.0);
469 pa.add_point(89.0, -120.0);
470 pa.add_point(89.0, 0.0);
471 pa.add_point(89.0, 120.0);
472 pa.add_point(89.0, 240.0);
473 let (perimeter, area, _) = pa.compute(true);
474 assert_relative_eq!(perimeter, 1160741.0, epsilon = 1.0);
475 assert_relative_eq!(area, 32415230256.0, epsilon = 1.0);
476 }
477
478 #[test]
479 fn test_planimeter15() {
480 let geoid = Geodesic::wgs84();
483
484 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
485 pa.add_point(2.0, 1.0);
486 pa.add_point(1.0, 2.0);
487 pa.add_point(3.0, 3.0);
488 let (_, area, _) = pa.compute(true);
489 assert_relative_eq!(area, 18454562325.45119, epsilon = 1.0e-4);
490
491 let mut pa = PolygonArea::new(&geoid, Winding::Clockwise);
493 pa.add_point(2.0, 1.0);
494 pa.add_point(1.0, 2.0);
495 pa.add_point(3.0, 3.0);
496 let (_, area, _) = pa.compute(true);
497 assert_relative_eq!(area, -18454562325.45119, epsilon = 1.0e-4);
498
499 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
501 pa.add_point(1.0, 2.0);
502 pa.add_point(2.0, 1.0);
503 pa.add_point(3.0, 3.0);
504 let (_, area, _) = pa.compute(true);
505 assert_relative_eq!(area, -18454562325.45119, epsilon = 1.0e-4);
506 }
507
508 #[test]
509 fn test_planimeter19() {
510 let geoid = Geodesic::wgs84();
515
516 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
517 let (perimeter, area, _) = pa.clone().compute(true);
518 assert_relative_eq!(perimeter, 0.0);
519 assert_relative_eq!(area, 0.0);
520
521 let (perimeter, area, _) = pa.test_point(1.0, 1.0, true);
522 assert_relative_eq!(perimeter, 0.0);
523 assert_relative_eq!(area, 0.0);
524
525 let result = std::panic::catch_unwind(|| {
526 let (_, _, _) = pa.test_edge(90.0, 1000.0, true);
527 });
528 assert!(result.is_err());
529
530 pa.add_point(1.0, 1.0);
531 let (perimeter, area, _) = pa.clone().compute(true);
532 assert_relative_eq!(perimeter, 0.0);
533 assert_relative_eq!(area, 0.0);
534 }
535
536 #[test]
537 fn test_planimeter21() {
538 let g = Geodesic::wgs84();
541
542 let lat = 45.0;
543 let azi = 39.2144607176828184218;
544 let s = 8420705.40957178156285;
545 let r = 39433884866571.4277; let a0 = g.area(); let mut pa_clockwise = PolygonArea::new(&g, Winding::Clockwise);
549 let mut pa_counter = PolygonArea::new(&g, Winding::CounterClockwise);
550
551 pa_clockwise.add_point(lat, 60.0);
552 pa_clockwise.add_point(lat, 180.0);
553 pa_clockwise.add_point(lat, -60.0);
554 pa_clockwise.add_point(lat, 60.0);
555 pa_clockwise.add_point(lat, 180.0);
556 pa_clockwise.add_point(lat, -60.0);
557
558 pa_counter.add_point(lat, 60.0);
559 pa_counter.add_point(lat, 180.0);
560 pa_counter.add_point(lat, -60.0);
561 pa_counter.add_point(lat, 60.0);
562 pa_counter.add_point(lat, 180.0);
563 pa_counter.add_point(lat, -60.0);
564
565 for i in 3..=4 {
566 pa_clockwise.add_point(lat, 60.0);
567 pa_clockwise.add_point(lat, 180.0);
568
569 pa_counter.add_point(lat, 60.0);
570 pa_counter.add_point(lat, 180.0);
571
572 let (_, area, _) = pa_counter.test_point(lat, -60.0, true);
573 assert_relative_eq!(area, i as f64 * r, epsilon = 0.5);
574
575 let (_, area, _) = pa_counter.test_point(lat, -60.0, false);
576 assert_relative_eq!(area, i as f64 * r, epsilon = 0.5);
577
578 let (_, area, _) = pa_clockwise.test_point(lat, -60.0, true);
579 assert_relative_eq!(area, -i as f64 * r, epsilon = 0.5);
580
581 let (_, area, _) = pa_clockwise.test_point(lat, -60.0, false);
582 assert_relative_eq!(area, (-i as f64 * r) + a0, epsilon = 0.5);
583
584 let (_, area, _) = pa_counter.test_edge(azi, s, true);
585 assert_relative_eq!(area, i as f64 * r, epsilon = 0.5);
586
587 let (_, area, _) = pa_counter.test_edge(azi, s, false);
588 assert_relative_eq!(area, i as f64 * r, epsilon = 0.5);
589
590 let (_, area, _) = pa_clockwise.test_edge(azi, s, true);
591 assert_relative_eq!(area, -i as f64 * r, epsilon = 0.5);
592
593 let (_, area, _) = pa_clockwise.test_edge(azi, s, false);
594 assert_relative_eq!(area, (-i as f64 * r) + a0, epsilon = 0.5);
595
596 pa_clockwise.add_point(lat, -60.0);
597 pa_counter.add_point(lat, -60.0);
598
599 let (_, area, _) = pa_counter.clone().compute(true);
600 assert_relative_eq!(area, r * i as f64, epsilon = 0.5);
601
602 let (_, area, _) = pa_counter.clone().compute(false);
603 assert_relative_eq!(area, r * i as f64, epsilon = 0.5);
604
605 let (_, area, _) = pa_clockwise.clone().compute(true);
606 assert_relative_eq!(area, -(r * i as f64), epsilon = 0.5);
607
608 let (_, area, _) = pa_clockwise.clone().compute(false);
609 assert_relative_eq!(area, -(r * i as f64) + a0, epsilon = 0.5);
610 }
611 }
612
613 #[test]
614 fn test_planimeter29() {
615 let geoid = Geodesic::wgs84();
619 let mut pa = PolygonArea::new(&geoid, Winding::CounterClockwise);
620 pa.add_point(0.0, 0.0);
621 pa.add_edge(90.0, 1000.0);
622 pa.add_edge(0.0, 1000.0);
623 pa.add_edge(-90.0, 1000.0);
624 let (_, area, _) = pa.compute(true);
625 assert_relative_eq!(area, 1000000.0, epsilon = 0.01);
626 }
627}