use s2rst::s2::shape::{Dimension, Shape};
use s2rst::s2::text_format;
use s2rst::s2::{Cell, CellId, LatLng, Loop, Point, Polygon, Region};
use std::f64::consts::PI;
fn p(lat: f64, lng: f64) -> Point {
LatLng::from_degrees(lat, lng).to_point()
}
fn approx_eq(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
#[test]
fn test_empty_polygon() {
let poly = Polygon::empty();
assert!(poly.is_empty_polygon());
assert!(!poly.is_full_polygon());
assert_eq!(poly.num_loops(), 0);
assert_eq!(poly.num_vertices(), 0);
assert_eq!(poly.area(), 0.0);
}
#[test]
fn test_full_polygon() {
let poly = Polygon::full();
assert!(!poly.is_empty_polygon());
assert!(poly.is_full_polygon());
assert_eq!(poly.num_loops(), 1);
let area = poly.area();
assert!(
approx_eq(area, 4.0 * PI, 1e-10),
"full polygon area should be ~4π, got {area}"
);
}
#[test]
fn test_polygon_from_single_loop() {
let vertices = vec![p(0.0, 0.0), p(0.0, 10.0), p(10.0, 0.0)];
let loop_ = Loop::new(vertices.clone());
let poly = Polygon::from_loops(vec![loop_]);
assert_eq!(poly.num_loops(), 1);
assert_eq!(poly.loop_at(0).num_vertices(), 3);
for (i, v) in vertices.iter().enumerate() {
let pv = poly.loop_at(0).vertex(i);
let dist = (pv.0 - v.0).norm();
assert!(dist < 1e-14, "vertex {i} mismatch: dist = {dist}");
}
assert!(
poly.area() > 0.0,
"single-loop polygon should have positive area"
);
}
#[test]
fn test_polygon_from_cell() {
let cell_id = CellId::from_face_pos_level(3, 0, 10);
let cell = Cell::from_cell_id(cell_id);
let poly = Polygon::from_cell(&cell);
assert_eq!(poly.num_loops(), 1);
assert_eq!(poly.loop_at(0).num_vertices(), 4);
assert!(poly.area() > 0.0, "cell polygon should have positive area");
}
#[test]
fn test_polygon_with_hole() {
let outer = Loop::new(vec![
p(-20.0, -20.0),
p(-20.0, 20.0),
p(20.0, 20.0),
p(20.0, -20.0),
]);
let outer_area = outer.area();
let inner = Loop::new(vec![p(-5.0, -5.0), p(-5.0, 5.0), p(5.0, 5.0), p(5.0, -5.0)]);
let poly = Polygon::from_loops(vec![outer, inner]);
assert!(
poly.has_holes(),
"polygon with hole should report has_holes"
);
assert_eq!(poly.num_loops(), 2);
assert!(
poly.area() < outer_area,
"polygon with hole should have less area than shell"
);
assert!(
poly.area() > 0.0,
"polygon with hole should have positive area"
);
assert!(
!poly.contains_point(&p(0.0, 0.0)),
"center should be in the hole"
);
assert!(
poly.contains_point(&p(15.0, 0.0)),
"point between shell and hole should be contained"
);
}
#[test]
fn test_polygon_contains_point() {
let poly = text_format::make_polygon("0:0, 0:10, 10:10, 10:0");
let inside = p(5.0, 5.0);
assert!(
poly.contains_point(&inside),
"polygon should contain interior point"
);
let outside = p(50.0, 50.0);
assert!(
!poly.contains_point(&outside),
"polygon should not contain exterior point"
);
}
#[test]
fn test_polygon_nesting() {
let poly = text_format::make_polygon("-20:-20, -20:20, 20:20, 20:-20; -5:-5, -5:5, 5:5, 5:-5");
assert_eq!(poly.num_loops(), 2);
assert_eq!(poly.loop_at(0).depth(), 0, "outer loop should have depth 0");
assert_eq!(poly.loop_at(1).depth(), 1, "inner loop should have depth 1");
assert_eq!(poly.parent(0), None, "outer shell should have no parent");
assert_eq!(
poly.parent(1),
Some(0),
"hole's parent should be the outer shell"
);
assert_eq!(
poly.last_descendant(0),
1,
"outer shell's last descendant should be 1"
);
assert_eq!(
poly.last_descendant(1),
1,
"hole's last descendant should be itself"
);
}
#[test]
fn test_polygon_shape_trait() {
let poly = text_format::make_polygon("0:0, 0:20, 20:20, 20:0; 5:5, 5:15, 15:15, 15:5");
assert_eq!(poly.dimension(), Dimension::Polygon);
let expected_edges: usize = (0..poly.num_loops())
.map(|k| poly.loop_at(k).num_vertices())
.sum();
assert_eq!(poly.num_edges(), expected_edges);
assert_eq!(poly.num_chains(), poly.num_loops());
for k in 0..poly.num_loops() {
let chain = poly.chain(k);
assert_eq!(chain.length, poly.loop_at(k).num_vertices());
}
assert!(poly.has_interior(), "polygon should have interior");
}
#[test]
fn test_polygon_rect_bound() {
let empty = Polygon::empty();
assert!(
empty.rect_bound().is_empty(),
"empty polygon's rect_bound should be empty"
);
let poly = text_format::make_polygon("10:20, 30:40, 50:60");
let rb = poly.rect_bound();
assert!(!rb.is_empty());
for i in 0..poly.loop_at(0).num_vertices() {
let ll = LatLng::from_point(poly.loop_at(0).vertex(i));
assert!(rb.contains_lat_lng(ll), "bound should contain vertex {i}");
}
}
#[test]
fn test_polygon_validate() {
let poly = text_format::make_polygon("0:0, 0:10, 10:0");
assert!(poly.validate().is_ok());
let poly2 = text_format::make_polygon("-20:-20, -20:20, 20:20, 20:-20; -5:-5, -5:5, 5:5, 5:-5");
assert!(poly2.validate().is_ok());
assert!(Polygon::empty().validate().is_ok());
assert!(Polygon::full().validate().is_ok());
}
#[test]
fn test_polygon_contains_small_cell() {
let poly = text_format::make_polygon("-30:-30, -30:30, 30:30, 30:-30");
let inside_id = CellId::from_point(&p(0.0, 0.0)).parent_at_level(20);
let inside_cell = Cell::from_cell_id(inside_id);
assert!(
poly.contains_cell(&inside_cell),
"polygon should contain small cell deep inside"
);
}
#[test]
fn test_polygon_does_not_contain_outside_cell() {
let poly = text_format::make_polygon("-10:-10, -10:10, 10:10, 10:-10");
let outside_id = CellId::from_point(&p(80.0, 80.0)).parent_at_level(16);
let outside_cell = Cell::from_cell_id(outside_id);
assert!(
!poly.contains_cell(&outside_cell),
"polygon should not contain cell far outside"
);
}