use s2rst::s2::text_format;
use s2rst::s2::{Cell, CellId, LatLng, Loop, Region};
use std::f64::consts::PI;
fn approx_eq(a: f64, b: f64, eps: f64) -> bool {
(a - b).abs() < eps
}
#[test]
fn test_empty_loop() {
let l = Loop::empty();
assert!(l.is_empty_loop());
assert!(!l.is_full_loop());
assert!(approx_eq(l.area(), 0.0, 1e-10));
assert!(approx_eq(l.turning_angle(), -2.0 * PI, 1e-10));
}
#[test]
fn test_full_loop() {
let l = Loop::full();
assert!(!l.is_empty_loop());
assert!(l.is_full_loop());
assert!(approx_eq(l.area(), 4.0 * PI, 1e-10));
assert!(approx_eq(l.turning_angle(), 2.0 * PI, 1e-10));
}
#[test]
fn test_loop_from_cell() {
let cell = Cell::from_cell_id(CellId::from_face_pos_level(0, 0, 5));
let l = Loop::from_cell(&cell);
assert_eq!(l.num_vertices(), 4);
assert!(l.area() > 0.0);
for i in 0..4 {
for j in (i + 1)..4 {
assert!(
(l.vertex(i).0 - l.vertex(j).0).norm() > 1e-15,
"vertices {i} and {j} should be distinct"
);
}
}
}
#[test]
fn test_triangle_containment() {
let l = text_format::make_loop("0:0, 0:10, 10:0");
let inside = LatLng::from_degrees(2.0, 2.0).to_point();
assert!(l.contains_point(&inside), "should contain interior point");
assert!(
l.brute_force_contains_point(inside),
"brute force should agree"
);
let outside = LatLng::from_degrees(50.0, 50.0).to_point();
assert!(
!l.contains_point(&outside),
"should not contain exterior point"
);
assert!(
!l.brute_force_contains_point(outside),
"brute force should agree"
);
}
#[test]
fn test_quadrilateral_loop() {
let l = text_format::make_loop("-10:-10, -10:10, 10:10, 10:-10");
let inside = LatLng::from_degrees(0.0, 0.0).to_point();
assert!(l.contains_point(&inside), "center should be inside quad");
let outside = LatLng::from_degrees(50.0, 0.0).to_point();
assert!(
!l.contains_point(&outside),
"far point should be outside quad"
);
let area = l.area();
assert!(area > 0.1, "quad area should be substantial, got {area}");
assert!(area < 0.5, "quad area should be bounded, got {area}");
}
#[test]
fn test_small_loop_area() {
let l = text_format::make_loop("89:0, 89:120, 89:240");
let area = l.area();
assert!(area > 0.0, "area should be positive");
assert!(area < 0.01, "area should be small, got {area}");
}
#[test]
fn test_loop_rect_bound() {
let full = Loop::full();
let rb = full.rect_bound();
assert!(rb.is_full(), "full loop rect bound should be full");
let empty = Loop::empty();
let rb = empty.rect_bound();
assert!(rb.is_empty(), "empty loop rect bound should be empty");
let l = text_format::make_loop("10:20, 10:40, 30:40, 30:20");
let rb = l.rect_bound();
for i in 0..l.num_vertices() {
let ll = LatLng::from_point(l.vertex(i));
assert!(rb.contains_lat_lng(ll), "bound should contain vertex {i}");
}
}
#[test]
fn test_loop_turning_angle_ccw_triangle() {
let l = text_format::make_loop("0:0, 0:10, 10:0");
let area = l.area();
let ta = l.turning_angle();
let expected = 2.0 * PI - area;
assert!(
approx_eq(ta, expected, 1e-6),
"turning angle {ta} not close to 2pi - area = {expected}"
);
}
#[test]
fn test_area_consistent_with_turning_angle() {
let l = text_format::make_loop("10:20, 10:40, 30:40, 30:20");
let area = l.area();
let ta = l.turning_angle();
assert!(
approx_eq(area + ta, 2.0 * PI, 1e-6),
"area({area}) + turning_angle({ta}) should be 2pi"
);
}
#[test]
fn test_loop_sign_positive() {
let l = text_format::make_loop("0:0, 0:10, 10:0");
assert_eq!(l.sign(), 1, "CCW loop should have sign +1");
let mut hole = text_format::make_loop("0:0, 0:10, 10:0");
hole.set_depth(1);
assert_eq!(hole.sign(), -1, "hole loop should have sign -1");
}
#[test]
fn test_loop_validate_ok() {
let l = text_format::make_loop("0:0, 0:10, 10:0");
assert!(l.validate().is_ok(), "valid loop should pass validate()");
assert!(Loop::empty().validate().is_ok());
assert!(Loop::full().validate().is_ok());
}
#[test]
fn test_loop_contains_cell() {
let cell = Cell::from_cell_id(CellId::from_face_pos_level(0, 0, 10));
let full = Loop::full();
assert!(
full.contains_cell(&cell),
"full loop should contain any cell"
);
let empty = Loop::empty();
assert!(
!empty.contains_cell(&cell),
"empty loop should not contain any cell"
);
}
#[test]
fn test_loop_intersects_cell() {
let cell = Cell::from_cell_id(CellId::from_face_pos_level(0, 0, 10));
let full = Loop::full();
assert!(
full.intersects_cell(&cell),
"full loop should intersect any cell"
);
let empty = Loop::empty();
assert!(
!empty.intersects_cell(&cell),
"empty loop should not intersect any cell"
);
}
#[test]
fn test_loop_equal() {
let l1 = text_format::make_loop("0:0, 0:10, 10:0");
let l2 = text_format::make_loop("0:0, 0:10, 10:0");
assert!(l1.equal(&l2), "identical loops should be equal");
let l3 = text_format::make_loop("0:0, 0:20, 20:0");
assert!(!l1.equal(&l3), "different loops should not be equal");
}
#[test]
fn test_inverted_loop_area() {
let original = text_format::make_loop("0:0, 0:10, 10:0");
let original_area = original.area();
let mut inverted = text_format::make_loop("0:0, 0:10, 10:0");
inverted.invert();
let inverted_area = inverted.area();
assert!(
approx_eq(original_area + inverted_area, 4.0 * PI, 1e-6),
"original({original_area}) + inverted({inverted_area}) should equal 4π"
);
}
#[test]
fn test_loop_normalize() {
let mut l = text_format::make_loop("0:0, 10:0, 0:10");
l.normalize();
assert!(
l.area() <= 2.0 * PI + 1e-10,
"normalized area should be ≤ 2π"
);
}