1use crate::error::{AlgorithmError, Result};
26use oxigdal_core::vector::{Coordinate, Polygon};
27
28use geographiclib_rs::{
33 Geodesic as GeodesicEngine, InverseGeodesic, PolygonArea as GeodesicPolygonArea, Winding,
34};
35
36#[derive(Debug, Clone)]
55pub struct Geodesic {
56 engine: GeodesicEngine,
57}
58
59#[derive(Debug, Clone, Copy)]
61pub struct InverseResult {
62 pub s12: f64,
64 pub azi1: f64,
66 pub azi2: f64,
68 pub s12_area: f64,
70}
71
72#[derive(Debug, Clone, Copy)]
74pub struct PolygonAreaResult {
75 pub area: f64,
77 pub perimeter: f64,
79 pub num_vertices: usize,
81}
82
83impl Geodesic {
84 pub fn wgs84() -> Self {
86 Self {
87 engine: GeodesicEngine::wgs84(),
88 }
89 }
90
91 pub fn new(a: f64, f: f64) -> Self {
98 Self {
99 engine: GeodesicEngine::new(a, f),
100 }
101 }
102
103 pub fn ellipsoid_area(&self) -> f64 {
105 self.engine.area()
106 }
107
108 pub fn inverse(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> Result<InverseResult> {
126 if lat1.abs() > 90.0 + 1e-10 || lat2.abs() > 90.0 + 1e-10 {
128 return Err(AlgorithmError::InvalidParameter {
129 parameter: "latitude",
130 message: "latitude must be between -90 and 90 degrees".to_string(),
131 });
132 }
133
134 let lat1 = lat1.clamp(-90.0, 90.0);
136 let lat2 = lat2.clamp(-90.0, 90.0);
137
138 #[allow(non_snake_case)]
140 let (_a12, s12, azi1, _calp1, azi2, _calp2, _m12, _M12, _M21, S12) = self
141 .engine
142 ._gen_inverse(lat1, lon1, lat2, lon2, Self::full_mask());
143
144 Ok(InverseResult {
145 s12,
146 azi1: Self::atan2d(azi1, _calp1),
147 azi2: Self::atan2d(azi2, _calp2),
148 s12_area: S12,
149 })
150 }
151
152 pub fn distance(&self, lat1: f64, lon1: f64, lat2: f64, lon2: f64) -> Result<f64> {
167 if lat1.abs() > 90.0 + 1e-10 || lat2.abs() > 90.0 + 1e-10 {
168 return Err(AlgorithmError::InvalidParameter {
169 parameter: "latitude",
170 message: "latitude must be between -90 and 90 degrees".to_string(),
171 });
172 }
173
174 let lat1 = lat1.clamp(-90.0, 90.0);
175 let lat2 = lat2.clamp(-90.0, 90.0);
176
177 let s12: f64 = self.engine.inverse(lat1, lon1, lat2, lon2);
178 Ok(s12)
179 }
180
181 pub fn polygon_area(&self, coords: &[Coordinate]) -> Result<PolygonAreaResult> {
199 if coords.len() < 3 {
200 return Err(AlgorithmError::InsufficientData {
201 operation: "polygon_area_karney",
202 message: "polygon must have at least 3 coordinates".to_string(),
203 });
204 }
205
206 for (i, coord) in coords.iter().enumerate() {
208 if coord.y.abs() > 90.0 + 1e-10 {
209 return Err(AlgorithmError::InvalidParameter {
210 parameter: "latitude",
211 message: format!(
212 "coordinate {} has invalid latitude {} (must be -90..90)",
213 i, coord.y
214 ),
215 });
216 }
217 }
218
219 let n = coords.len();
221 let is_closed = n > 3
222 && (coords[0].x - coords[n - 1].x).abs() < 1e-12
223 && (coords[0].y - coords[n - 1].y).abs() < 1e-12;
224
225 let num_verts = if is_closed { n - 1 } else { n };
227
228 let mut pa = GeodesicPolygonArea::new(&self.engine, Winding::CounterClockwise);
230
231 for i in 0..num_verts {
232 let lat = coords[i].y.clamp(-90.0, 90.0);
233 let lon = coords[i].x;
234 pa.add_point(lat, lon);
236 }
237
238 let (perimeter, signed_area, count) = pa.compute(true);
240
241 let area = signed_area.abs();
247
248 Ok(PolygonAreaResult {
249 area,
250 perimeter,
251 num_vertices: count,
252 })
253 }
254
255 pub fn polygon_area_signed(&self, coords: &[Coordinate]) -> Result<f64> {
272 if coords.len() < 3 {
273 return Err(AlgorithmError::InsufficientData {
274 operation: "polygon_area_signed_karney",
275 message: "polygon must have at least 3 coordinates".to_string(),
276 });
277 }
278
279 for (i, coord) in coords.iter().enumerate() {
281 if coord.y.abs() > 90.0 + 1e-10 {
282 return Err(AlgorithmError::InvalidParameter {
283 parameter: "latitude",
284 message: format!(
285 "coordinate {} has invalid latitude {} (must be -90..90)",
286 i, coord.y
287 ),
288 });
289 }
290 }
291
292 let n = coords.len();
293 let is_closed = n > 3
294 && (coords[0].x - coords[n - 1].x).abs() < 1e-12
295 && (coords[0].y - coords[n - 1].y).abs() < 1e-12;
296 let num_verts = if is_closed { n - 1 } else { n };
297
298 let mut pa = GeodesicPolygonArea::new(&self.engine, Winding::CounterClockwise);
299 for i in 0..num_verts {
300 let lat = coords[i].y.clamp(-90.0, 90.0);
301 let lon = coords[i].x;
302 pa.add_point(lat, lon);
303 }
304
305 let (_perimeter, area, _count) = pa.compute(true);
307 Ok(area)
308 }
309
310 pub fn polygon_area_full(&self, polygon: &Polygon) -> Result<f64> {
327 let ext_result = self.polygon_area(&polygon.exterior.coords)?;
328 let mut area = ext_result.area;
329
330 for hole in &polygon.interiors {
331 let hole_result = self.polygon_area(&hole.coords)?;
332 area -= hole_result.area;
333 }
334
335 Ok(area.abs())
336 }
337
338 fn full_mask() -> u64 {
344 use geographiclib_rs::geodesic_capability as caps;
345 caps::LATITUDE
346 | caps::LONGITUDE
347 | caps::DISTANCE
348 | caps::AREA
349 | caps::LONG_UNROLL
350 | caps::AZIMUTH
351 }
352
353 fn atan2d(sinx: f64, cosx: f64) -> f64 {
355 sinx.atan2(cosx).to_degrees()
356 }
357}
358
359pub fn ring_area_karney(coords: &[Coordinate]) -> Result<f64> {
379 let geod = Geodesic::wgs84();
380 let result = geod.polygon_area(coords)?;
381 Ok(result.area)
382}
383
384pub fn area_polygon_karney(polygon: &Polygon) -> Result<f64> {
400 let geod = Geodesic::wgs84();
401 geod.polygon_area_full(polygon)
402}
403
404#[cfg(test)]
409mod tests {
410 use super::*;
411 use oxigdal_core::vector::LineString;
412
413 fn make_polygon(vertices: &[(f64, f64)]) -> Result<Polygon> {
416 let mut coords: Vec<Coordinate> = vertices
417 .iter()
418 .map(|&(lon, lat)| Coordinate::new_2d(lon, lat))
419 .collect();
420 if let Some(&first) = vertices.first() {
422 coords.push(Coordinate::new_2d(first.0, first.1));
423 }
424 let exterior = LineString::new(coords).map_err(AlgorithmError::Core)?;
425 Polygon::new(exterior, vec![]).map_err(AlgorithmError::Core)
426 }
427
428 #[test]
429 fn test_wgs84_construction() {
430 let geod = Geodesic::wgs84();
431 let area = geod.ellipsoid_area();
432 assert!(area > 5.0e14);
434 assert!(area < 5.2e14);
435 }
436
437 #[test]
438 fn test_inverse_same_point() {
439 let geod = Geodesic::wgs84();
440 let result = geod.inverse(0.0, 0.0, 0.0, 0.0);
441 assert!(result.is_ok());
442 if let Ok(inv) = result {
443 assert!(inv.s12.abs() < 1e-6);
444 }
445 }
446
447 #[test]
448 fn test_inverse_equator_short() {
449 let geod = Geodesic::wgs84();
450 let result = geod.inverse(0.0, 0.0, 0.0, 1.0);
452 assert!(result.is_ok());
453 if let Ok(inv) = result {
454 assert!(
455 inv.s12 > 111_000.0 && inv.s12 < 112_000.0,
456 "equatorial 1-degree distance = {}, expected ~111319",
457 inv.s12
458 );
459 }
460 }
461
462 #[test]
463 fn test_inverse_meridian() {
464 let geod = Geodesic::wgs84();
465 let result = geod.inverse(0.0, 0.0, 1.0, 0.0);
467 assert!(result.is_ok());
468 if let Ok(inv) = result {
469 assert!(
470 inv.s12 > 110_000.0 && inv.s12 < 112_000.0,
471 "meridional 1-degree distance = {}, expected ~110574",
472 inv.s12
473 );
474 }
475 }
476
477 #[test]
478 fn test_inverse_symmetry() {
479 let geod = Geodesic::wgs84();
480 let r1 = geod.inverse(10.0, 20.0, 30.0, 40.0);
481 let r2 = geod.inverse(30.0, 40.0, 10.0, 20.0);
482
483 assert!(r1.is_ok());
484 assert!(r2.is_ok());
485
486 if let (Ok(inv1), Ok(inv2)) = (r1, r2) {
487 let rel_err = (inv1.s12 - inv2.s12).abs() / inv1.s12.max(1.0);
488 assert!(
489 rel_err < 1e-12,
490 "inverse distance not symmetric: {} vs {}",
491 inv1.s12,
492 inv2.s12
493 );
494 }
495 }
496
497 #[test]
498 fn test_inverse_invalid_latitude() {
499 let geod = Geodesic::wgs84();
500 let result = geod.inverse(0.0, 0.0, 95.0, 0.0);
502 assert!(result.is_err());
503 }
504
505 #[test]
506 fn test_distance_convenience() {
507 let geod = Geodesic::wgs84();
508 let d = geod.distance(0.0, 0.0, 0.0, 1.0);
509 assert!(d.is_ok());
510 if let Ok(dist) = d {
511 assert!(dist > 111_000.0 && dist < 112_000.0);
512 }
513 }
514
515 #[test]
520 fn test_polygon_area_unit_square_equator() {
521 let geod = Geodesic::wgs84();
524 let coords = vec![
525 Coordinate::new_2d(0.0, 0.0),
526 Coordinate::new_2d(1.0, 0.0),
527 Coordinate::new_2d(1.0, 1.0),
528 Coordinate::new_2d(0.0, 1.0),
529 Coordinate::new_2d(0.0, 0.0),
530 ];
531 let result = geod.polygon_area(&coords);
532 assert!(result.is_ok(), "polygon_area failed: {:?}", result.err());
533 if let Ok(pa) = result {
534 let reference = 12_308_778_361.469;
535 let rel_error = (pa.area - reference).abs() / reference;
536 assert!(
537 rel_error < 1e-6,
538 "Unit square area {:.3} m^2, expected ~{:.3} m^2, relative error: {:.10}",
539 pa.area,
540 reference,
541 rel_error
542 );
543 }
544 }
545
546 #[test]
547 fn test_polygon_area_full_earth() {
548 let geod = Geodesic::wgs84();
551 let total_area = geod.ellipsoid_area();
552
553 let coords = vec![
555 Coordinate::new_2d(0.0, -89.0),
556 Coordinate::new_2d(90.0, -89.0),
557 Coordinate::new_2d(180.0, -89.0),
558 Coordinate::new_2d(-90.0, -89.0),
559 Coordinate::new_2d(0.0, -89.0),
560 ];
561 let result = geod.polygon_area(&coords);
562 assert!(result.is_ok(), "polygon_area failed: {:?}", result.err());
563 if let Ok(pa) = result {
564 assert!(pa.area > 0.0);
567 assert!(pa.area < total_area);
568 }
569 }
570
571 #[test]
572 fn test_polygon_area_ellipsoid_area() {
573 let geod = Geodesic::wgs84();
575 let total_area = geod.ellipsoid_area();
576 let reference = 5.10065621724089e14;
577 let rel_error = (total_area - reference).abs() / reference;
578 assert!(
579 rel_error < 1e-8,
580 "Ellipsoid area {:.6e}, expected {:.6e}, rel err: {:.10}",
581 total_area,
582 reference,
583 rel_error
584 );
585 }
586
587 #[test]
588 fn test_polygon_area_high_latitude_triangle() {
589 let geod = Geodesic::wgs84();
591 let coords = vec![
592 Coordinate::new_2d(0.0, 89.0),
593 Coordinate::new_2d(90.0, 89.0),
594 Coordinate::new_2d(180.0, 89.0),
595 Coordinate::new_2d(270.0, 89.0),
596 Coordinate::new_2d(0.0, 89.0),
597 ];
598 let result = geod.polygon_area(&coords);
599 assert!(result.is_ok(), "polygon_area failed: {:?}", result.err());
600 if let Ok(pa) = result {
601 let reference = 24_952_305_678.0;
603 let rel_error = (pa.area - reference).abs() / reference;
604 assert!(
605 rel_error < 1e-6,
606 "High-lat area {:.0} m^2, expected {:.0} m^2, rel err: {:.10}",
607 pa.area,
608 reference,
609 rel_error
610 );
611 }
612 }
613
614 #[test]
615 fn test_polygon_area_antimeridian_crossing() {
616 let geod = Geodesic::wgs84();
618 let coords = vec![
619 Coordinate::new_2d(179.0, 0.0),
620 Coordinate::new_2d(-179.0, 0.0),
621 Coordinate::new_2d(-179.0, 1.0),
622 Coordinate::new_2d(179.0, 1.0),
623 Coordinate::new_2d(179.0, 0.0),
624 ];
625 let result = geod.polygon_area(&coords);
626 assert!(result.is_ok(), "polygon_area failed: {:?}", result.err());
627 if let Ok(pa) = result {
628 let reference_approx = 2.0 * 12_308_778_361.0;
631 let rel_error = (pa.area - reference_approx).abs() / reference_approx;
632 assert!(
633 rel_error < 0.01,
634 "Antimeridian area {:.3} m^2, expected ~{:.3} m^2, rel error: {:.6}",
635 pa.area,
636 reference_approx,
637 rel_error
638 );
639 }
640 }
641
642 #[test]
643 fn test_polygon_area_polar_enclosing() {
644 let geod = Geodesic::wgs84();
649 let coords = vec![
650 Coordinate::new_2d(0.0, -89.0),
651 Coordinate::new_2d(270.0, -89.0),
652 Coordinate::new_2d(180.0, -89.0),
653 Coordinate::new_2d(90.0, -89.0),
654 Coordinate::new_2d(0.0, -89.0),
655 ];
656 let result = geod.polygon_area(&coords);
657 assert!(result.is_ok(), "polygon_area failed: {:?}", result.err());
658 if let Ok(pa) = result {
659 assert!(pa.area > 0.0, "polar area should be positive");
660 let reference = 24_952_305_678.0;
662 let rel_error = (pa.area - reference).abs() / reference;
663 assert!(
664 rel_error < 1e-4,
665 "Polar enclosing area {:.0}, expected ~{:.0}, rel err {:.8}",
666 pa.area,
667 reference,
668 rel_error
669 );
670 }
671 }
672
673 #[test]
674 fn test_polygon_area_cw_ccw_same_absolute() {
675 let geod = Geodesic::wgs84();
677 let ccw = vec![
678 Coordinate::new_2d(0.0, 0.0),
679 Coordinate::new_2d(1.0, 0.0),
680 Coordinate::new_2d(1.0, 1.0),
681 Coordinate::new_2d(0.0, 1.0),
682 Coordinate::new_2d(0.0, 0.0),
683 ];
684 let cw = vec![
686 Coordinate::new_2d(0.0, 0.0),
687 Coordinate::new_2d(0.0, 1.0),
688 Coordinate::new_2d(1.0, 1.0),
689 Coordinate::new_2d(1.0, 0.0),
690 Coordinate::new_2d(0.0, 0.0),
691 ];
692
693 let result_ccw = geod.polygon_area(&ccw);
694 let result_cw = geod.polygon_area(&cw);
695
696 assert!(result_ccw.is_ok());
697 assert!(result_cw.is_ok());
698
699 if let (Ok(pa_ccw), Ok(pa_cw)) = (result_ccw, result_cw) {
700 let diff = (pa_ccw.area - pa_cw.area).abs();
701 let mean = (pa_ccw.area + pa_cw.area) / 2.0;
702 assert!(
703 diff / mean < 1e-10,
704 "CW/CCW areas differ: {} vs {}",
705 pa_ccw.area,
706 pa_cw.area
707 );
708 }
709 }
710
711 #[test]
712 fn test_polygon_area_signed_winding() {
713 let geod = Geodesic::wgs84();
715
716 let ccw = vec![
718 Coordinate::new_2d(0.0, 0.0),
719 Coordinate::new_2d(1.0, 0.0),
720 Coordinate::new_2d(1.0, 1.0),
721 Coordinate::new_2d(0.0, 1.0),
722 Coordinate::new_2d(0.0, 0.0),
723 ];
724
725 let cw = vec![
727 Coordinate::new_2d(0.0, 0.0),
728 Coordinate::new_2d(0.0, 1.0),
729 Coordinate::new_2d(1.0, 1.0),
730 Coordinate::new_2d(1.0, 0.0),
731 Coordinate::new_2d(0.0, 0.0),
732 ];
733
734 let signed_ccw = geod.polygon_area_signed(&ccw);
735 let signed_cw = geod.polygon_area_signed(&cw);
736
737 assert!(signed_ccw.is_ok());
738 assert!(signed_cw.is_ok());
739
740 if let (Ok(area_ccw), Ok(area_cw)) = (signed_ccw, signed_cw) {
741 assert!(
742 area_ccw > 0.0,
743 "CCW signed area should be positive: {}",
744 area_ccw
745 );
746 assert!(
747 area_cw < 0.0,
748 "CW signed area should be negative: {}",
749 area_cw
750 );
751 assert!(
752 (area_ccw.abs() - area_cw.abs()).abs() / area_ccw.abs() < 1e-10,
753 "absolute values should match"
754 );
755 }
756 }
757
758 #[test]
759 fn test_polygon_area_degenerate_collinear() {
760 let geod = Geodesic::wgs84();
762 let coords = vec![
763 Coordinate::new_2d(0.0, 0.0),
764 Coordinate::new_2d(1.0, 0.0),
765 Coordinate::new_2d(2.0, 0.0),
766 Coordinate::new_2d(0.0, 0.0),
767 ];
768 let result = geod.polygon_area(&coords);
769 assert!(result.is_ok());
770 if let Ok(pa) = result {
771 assert!(
772 pa.area < 1.0,
773 "collinear polygon area should be ~0, got {}",
774 pa.area
775 );
776 }
777 }
778
779 #[test]
780 fn test_polygon_area_convenience_function() {
781 let coords = vec![
782 Coordinate::new_2d(0.0, 0.0),
783 Coordinate::new_2d(1.0, 0.0),
784 Coordinate::new_2d(1.0, 1.0),
785 Coordinate::new_2d(0.0, 1.0),
786 Coordinate::new_2d(0.0, 0.0),
787 ];
788 let result = ring_area_karney(&coords);
789 assert!(result.is_ok());
790 if let Ok(area) = result {
791 let reference = 12_308_778_361.469;
792 let rel_error = (area - reference).abs() / reference;
793 assert!(
794 rel_error < 1e-6,
795 "convenience area {:.3} m^2, expected ~{:.3} m^2",
796 area,
797 reference
798 );
799 }
800 }
801
802 #[test]
803 fn test_area_polygon_karney_with_hole() {
804 let outer = make_polygon(&[(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)]);
805 let inner = make_polygon(&[(2.0, 2.0), (8.0, 2.0), (8.0, 8.0), (2.0, 8.0)]);
806
807 assert!(outer.is_ok());
808 assert!(inner.is_ok());
809
810 if let (Ok(outer_p), Ok(inner_p)) = (outer, inner) {
811 let exterior = outer_p.exterior.clone();
812 let hole = inner_p.exterior.clone();
813 let poly_with_hole = Polygon::new(exterior, vec![hole]);
814 assert!(poly_with_hole.is_ok());
815
816 if let Ok(p) = poly_with_hole {
817 let result = area_polygon_karney(&p);
818 assert!(result.is_ok());
819 if let Ok(area) = result {
820 assert!(area > 0.0);
822 assert!(
825 area > 1e11,
826 "area with hole should be substantial: {}",
827 area
828 );
829 }
830 }
831 }
832 }
833
834 #[test]
835 fn test_polygon_area_insufficient_coords() {
836 let geod = Geodesic::wgs84();
837 let coords = vec![Coordinate::new_2d(0.0, 0.0), Coordinate::new_2d(1.0, 0.0)];
838 let result = geod.polygon_area(&coords);
839 assert!(result.is_err());
840 }
841
842 #[test]
843 fn test_polygon_area_invalid_latitude() {
844 let geod = Geodesic::wgs84();
845 let coords = vec![
846 Coordinate::new_2d(0.0, 0.0),
847 Coordinate::new_2d(1.0, 0.0),
848 Coordinate::new_2d(1.0, 95.0), Coordinate::new_2d(0.0, 0.0),
850 ];
851 let result = geod.polygon_area(&coords);
852 assert!(result.is_err());
853 }
854
855 #[test]
856 fn test_polygon_area_open_ring() {
857 let geod = Geodesic::wgs84();
859 let coords = vec![
860 Coordinate::new_2d(0.0, 0.0),
861 Coordinate::new_2d(1.0, 0.0),
862 Coordinate::new_2d(1.0, 1.0),
863 Coordinate::new_2d(0.0, 1.0),
864 ];
865 let result = geod.polygon_area(&coords);
866 assert!(result.is_ok());
867 if let Ok(pa) = result {
868 let reference = 12_308_778_361.469;
870 let rel_error = (pa.area - reference).abs() / reference;
871 assert!(
872 rel_error < 1e-6,
873 "open ring area {:.3}, expected ~{:.3}",
874 pa.area,
875 reference
876 );
877 }
878 }
879
880 #[test]
881 fn test_custom_ellipsoid() {
882 let r = 6_371_000.0;
884 let geod = Geodesic::new(r, 0.0);
885 let area = geod.ellipsoid_area();
886 use core::f64::consts::PI;
887 let expected = 4.0 * PI * r * r;
888 let rel_error = (area - expected).abs() / expected;
889 assert!(
890 rel_error < 1e-10,
891 "sphere area {:.6e}, expected {:.6e}",
892 area,
893 expected
894 );
895 }
896
897 #[test]
898 fn test_diamond_polygon() {
899 let geod = Geodesic::wgs84();
901 let coords = vec![
902 Coordinate::new_2d(-1.0, 0.0),
903 Coordinate::new_2d(0.0, -1.0),
904 Coordinate::new_2d(1.0, 0.0),
905 Coordinate::new_2d(0.0, 1.0),
906 Coordinate::new_2d(-1.0, 0.0),
907 ];
908 let result = geod.polygon_area(&coords);
909 assert!(result.is_ok());
910 if let Ok(pa) = result {
911 let reference = 24_619_419_146.0;
913 let rel_error = (pa.area - reference).abs() / reference;
914 assert!(
915 rel_error < 1e-5,
916 "diamond area {:.0}, expected {:.0}, rel err {:.8}",
917 pa.area,
918 reference,
919 rel_error
920 );
921 }
922 }
923
924 #[test]
925 fn test_perimeter_unit_square() {
926 let geod = Geodesic::wgs84();
929 let coords = vec![
930 Coordinate::new_2d(0.0, 0.0),
931 Coordinate::new_2d(1.0, 0.0),
932 Coordinate::new_2d(1.0, 1.0),
933 Coordinate::new_2d(0.0, 1.0),
934 Coordinate::new_2d(0.0, 0.0),
935 ];
936 let result = geod.polygon_area(&coords);
937 assert!(result.is_ok());
938 if let Ok(pa) = result {
939 let reference = 443_770.917;
940 let rel_error = (pa.perimeter - reference).abs() / reference;
941 assert!(
942 rel_error < 1e-5,
943 "perimeter {:.3}, expected {:.3}",
944 pa.perimeter,
945 reference
946 );
947 }
948 }
949
950 #[test]
951 fn test_area_method_karney_via_area_module() {
952 let coords = vec![
954 Coordinate::new_2d(0.0, 0.0),
955 Coordinate::new_2d(1.0, 0.0),
956 Coordinate::new_2d(1.0, 1.0),
957 Coordinate::new_2d(0.0, 1.0),
958 Coordinate::new_2d(0.0, 0.0),
959 ];
960 let exterior = LineString::new(coords);
961 assert!(exterior.is_ok());
962
963 if let Ok(ext) = exterior {
964 let poly = Polygon::new(ext, vec![]);
965 assert!(poly.is_ok());
966
967 if let Ok(p) = poly {
968 let result = crate::vector::area::area_polygon(
969 &p,
970 crate::vector::area::AreaMethod::KarneyGeodesic,
971 );
972 assert!(result.is_ok());
973 if let Ok(area) = result {
974 let reference = 12_308_778_361.469;
975 let rel_error = (area - reference).abs() / reference;
976 assert!(
977 rel_error < 1e-6,
978 "AreaMethod::KarneyGeodesic: area {:.3}, expected ~{:.3}",
979 area,
980 reference
981 );
982 }
983 }
984 }
985 }
986}