use s2rst::r1;
use s2rst::s1;
use s2rst::s2::{Cell, CellId, LatLng, Loop, Point, Rect};
const CSV: &str = include_str!("data/cpp_test_vectors.csv");
fn section_lines(name: &str) -> Vec<&'static str> {
let header = format!("# {name}");
let mut in_section = false;
let mut lines = Vec::new();
for line in CSV.lines() {
if line == header {
in_section = true;
continue;
}
if in_section {
if line.starts_with('#') || line.is_empty() {
break;
}
lines.push(line);
}
}
lines
}
fn parse_f64(s: &str) -> f64 {
s.trim().parse::<f64>().unwrap()
}
fn parse_u64(s: &str) -> u64 {
s.trim().parse::<u64>().unwrap()
}
fn parse_i32(s: &str) -> i32 {
s.trim().parse::<i32>().unwrap()
}
fn parse_bool(s: &str) -> bool {
s.trim() == "1"
}
fn assert_close(a: f64, b: f64, tol: f64, context: &str) {
let diff = (a - b).abs();
assert!(diff <= tol, "{context}: {a} vs {b}, diff={diff}, tol={tol}");
}
#[test]
fn test_cpp_cellid_from_point() {
for line in section_lines("CELLID_FROM_POINT") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 8, "bad line: {line}");
let x = parse_f64(f[0]);
let y = parse_f64(f[1]);
let z = parse_f64(f[2]);
let expected_id = parse_u64(f[3]);
let expected_face = s2rst::s2::Face::from_u8(parse_i32(f[4]) as u8);
let expected_level = parse_i32(f[5]) as u8;
let expected_valid = parse_bool(f[6]);
let expected_leaf = parse_bool(f[7]);
let p = Point::from_coords(x, y, z);
let id = CellId::from_point(&p);
assert_eq!(
id.id(),
expected_id,
"cell_id mismatch for point ({x},{y},{z})"
);
assert_eq!(
id.face(),
expected_face,
"face mismatch for point ({x},{y},{z})"
);
assert_eq!(
id.level(),
expected_level,
"level mismatch for point ({x},{y},{z})"
);
assert_eq!(
id.is_valid(),
expected_valid,
"is_valid mismatch for point ({x},{y},{z})"
);
assert_eq!(
id.is_leaf(),
expected_leaf,
"is_leaf mismatch for point ({x},{y},{z})"
);
}
}
#[test]
fn test_cpp_cellid_from_lat_lng() {
for line in section_lines("CELLID_FROM_LAT_LNG") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 4, "bad line: {line}");
let lat_deg = parse_f64(f[0]);
let lng_deg = parse_f64(f[1]);
let expected_id = parse_u64(f[2]);
let expected_face = s2rst::s2::Face::from_u8(parse_i32(f[3]) as u8);
let ll = LatLng::from_degrees(lat_deg, lng_deg);
let id = CellId::from_lat_lng(&ll);
assert_eq!(
id.id(),
expected_id,
"cell_id mismatch for ({lat_deg},{lng_deg})"
);
assert_eq!(
id.face(),
expected_face,
"face mismatch for ({lat_deg},{lng_deg})"
);
}
}
#[test]
fn test_cpp_cellid_to_point() {
for line in section_lines("CELLID_TO_POINT") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 4, "bad line: {line}");
let cell_id = parse_u64(f[0]);
let expected_x = parse_f64(f[1]);
let expected_y = parse_f64(f[2]);
let expected_z = parse_f64(f[3]);
let id = CellId(cell_id);
let p = id.to_point();
assert_close(p.x(), expected_x, 1e-15, &format!("x for cell {cell_id}"));
assert_close(p.y(), expected_y, 1e-15, &format!("y for cell {cell_id}"));
assert_close(p.z(), expected_z, 1e-15, &format!("z for cell {cell_id}"));
}
}
#[test]
fn test_cpp_cellid_hierarchy() {
for line in section_lines("CELLID_HIERARCHY") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 9, "bad line: {line}");
let cell_id = parse_u64(f[0]);
let level = parse_i32(f[1]) as u8;
let expected_parent = parse_u64(f[2]);
let expected_child0 = parse_u64(f[3]);
let expected_child1 = parse_u64(f[4]);
let expected_child2 = parse_u64(f[5]);
let expected_child3 = parse_u64(f[6]);
let expected_range_min = parse_u64(f[7]);
let expected_range_max = parse_u64(f[8]);
let id = CellId(cell_id);
assert_eq!(id.level(), level, "level mismatch for cell {cell_id}");
if level > 0 {
assert_eq!(
id.parent().id(),
expected_parent,
"parent mismatch for cell {cell_id}"
);
}
if !id.is_leaf() {
let children = id.children();
assert_eq!(
children[0].id(),
expected_child0,
"child0 mismatch for cell {cell_id}"
);
assert_eq!(
children[1].id(),
expected_child1,
"child1 mismatch for cell {cell_id}"
);
assert_eq!(
children[2].id(),
expected_child2,
"child2 mismatch for cell {cell_id}"
);
assert_eq!(
children[3].id(),
expected_child3,
"child3 mismatch for cell {cell_id}"
);
}
assert_eq!(
id.range_min().id(),
expected_range_min,
"range_min mismatch for cell {cell_id}"
);
assert_eq!(
id.range_max().id(),
expected_range_max,
"range_max mismatch for cell {cell_id}"
);
}
}
#[test]
fn test_cpp_cellid_tokens() {
for line in section_lines("CELLID_TOKENS") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 2, "bad line: {line}");
let cell_id = parse_u64(f[0]);
let expected_token = f[1].trim();
let id = CellId(cell_id);
let token = id.to_token();
assert_eq!(
token, expected_token,
"to_token mismatch for cell {cell_id}"
);
let back = CellId::from_token(&token);
assert_eq!(
back.id(),
cell_id,
"from_token roundtrip mismatch for token '{token}'"
);
}
}
#[test]
fn test_cpp_latlng_conversion() {
for line in section_lines("LATLNG_CONVERSION") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 7, "bad line: {line}");
let lat_deg = parse_f64(f[0]);
let lng_deg = parse_f64(f[1]);
let expected_px = parse_f64(f[2]);
let expected_py = parse_f64(f[3]);
let expected_pz = parse_f64(f[4]);
let expected_lat_rad = parse_f64(f[5]);
let expected_lng_rad = parse_f64(f[6]);
let ll = LatLng::from_degrees(lat_deg, lng_deg);
let p = ll.to_point();
let ctx = format!("({lat_deg},{lng_deg})");
assert_close(p.x(), expected_px, 1e-15, &format!("px for {ctx}"));
assert_close(p.y(), expected_py, 1e-15, &format!("py for {ctx}"));
assert_close(p.z(), expected_pz, 1e-15, &format!("pz for {ctx}"));
let back = LatLng::from_point(p);
assert_close(
back.lat.radians(),
expected_lat_rad,
1e-15,
&format!("lat_rad for {ctx}"),
);
assert_close(
back.lng.radians(),
expected_lng_rad,
1e-15,
&format!("lng_rad for {ctx}"),
);
}
}
#[test]
fn test_cpp_latlng_distance() {
for line in section_lines("LATLNG_DISTANCE") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 5, "bad line: {line}");
let lat1 = parse_f64(f[0]);
let lng1 = parse_f64(f[1]);
let lat2 = parse_f64(f[2]);
let lng2 = parse_f64(f[3]);
let expected_dist = parse_f64(f[4]);
let a = LatLng::from_degrees(lat1, lng1);
let b = LatLng::from_degrees(lat2, lng2);
let dist = a.get_distance(b).radians();
assert_close(
dist,
expected_dist,
1e-13,
&format!("distance ({lat1},{lng1})->({lat2},{lng2})"),
);
}
}
#[test]
fn test_cpp_point_distance() {
for line in section_lines("POINT_DISTANCE") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 7, "bad line: {line}");
let x1 = parse_f64(f[0]);
let y1 = parse_f64(f[1]);
let z1 = parse_f64(f[2]);
let x2 = parse_f64(f[3]);
let y2 = parse_f64(f[4]);
let z2 = parse_f64(f[5]);
let expected_angle = parse_f64(f[6]);
let a = Point::from_coords(x1, y1, z1);
let b = Point::from_coords(x2, y2, z2);
let angle = a.distance(b).radians();
assert_close(
angle,
expected_angle,
1e-15,
&format!("angle ({x1},{y1},{z1})->({x2},{y2},{z2})"),
);
}
}
#[test]
fn test_cpp_cell_area() {
for line in section_lines("CELL_AREA") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 4, "bad line: {line}");
let cell_id = parse_u64(f[0]);
let _level = parse_i32(f[1]) as u8;
let expected_approx = parse_f64(f[2]);
let expected_avg = parse_f64(f[3]);
let cell = Cell::from_cell_id(CellId(cell_id));
let approx = cell.approx_area();
let avg = cell.average_area();
assert_close(
approx,
expected_approx,
1e-13,
&format!("approx_area for cell {cell_id}"),
);
assert_close(
avg,
expected_avg,
1e-13,
&format!("average_area for cell {cell_id}"),
);
}
}
#[test]
fn test_cpp_cell_vertices() {
for line in section_lines("CELL_VERTICES") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 13, "bad line: {line}");
let cell_id = parse_u64(f[0]);
let cell = Cell::from_cell_id(CellId(cell_id));
for k in 0..4 {
let ex = parse_f64(f[1 + k * 3]);
let ey = parse_f64(f[2 + k * 3]);
let ez = parse_f64(f[3 + k * 3]);
let v = cell.vertex(k);
assert_close(
v.x(),
ex,
1e-15,
&format!("vertex {k} x for cell {cell_id}"),
);
assert_close(
v.y(),
ey,
1e-15,
&format!("vertex {k} y for cell {cell_id}"),
);
assert_close(
v.z(),
ez,
1e-15,
&format!("vertex {k} z for cell {cell_id}"),
);
}
}
}
#[test]
fn test_cpp_rect_operations() {
let deg2rad = |d: f64| d * std::f64::consts::PI / 180.0;
for line in section_lines("RECT_OPERATIONS") {
let f: Vec<&str> = line.split(',').collect();
assert_eq!(f.len(), 20, "bad line: {line}");
let lat_lo1 = parse_f64(f[0]);
let lat_hi1 = parse_f64(f[1]);
let lng_lo1 = parse_f64(f[2]);
let lng_hi1 = parse_f64(f[3]);
let lat_lo2 = parse_f64(f[4]);
let lat_hi2 = parse_f64(f[5]);
let lng_lo2 = parse_f64(f[6]);
let lng_hi2 = parse_f64(f[7]);
let expected_area1 = parse_f64(f[8]);
let expected_area2 = parse_f64(f[9]);
let expected_contains = parse_bool(f[10]);
let expected_intersects = parse_bool(f[11]);
let expected_u_lat_lo = parse_f64(f[12]);
let expected_u_lat_hi = parse_f64(f[13]);
let expected_u_lng_lo = parse_f64(f[14]);
let expected_u_lng_hi = parse_f64(f[15]);
let expected_i_lat_lo = parse_f64(f[16]);
let expected_i_lat_hi = parse_f64(f[17]);
let expected_i_lng_lo = parse_f64(f[18]);
let expected_i_lng_hi = parse_f64(f[19]);
let r1 = Rect::new(
r1::Interval::new(deg2rad(lat_lo1), deg2rad(lat_hi1)),
s1::Interval::new(deg2rad(lng_lo1), deg2rad(lng_hi1)),
);
let r2 = Rect::new(
r1::Interval::new(deg2rad(lat_lo2), deg2rad(lat_hi2)),
s1::Interval::new(deg2rad(lng_lo2), deg2rad(lng_hi2)),
);
let ctx = format!(
"rects [{lat_lo1},{lat_hi1},{lng_lo1},{lng_hi1}] vs [{lat_lo2},{lat_hi2},{lng_lo2},{lng_hi2}]"
);
assert_close(r1.area(), expected_area1, 1e-13, &format!("area1 {ctx}"));
assert_close(r2.area(), expected_area2, 1e-13, &format!("area2 {ctx}"));
assert_eq!(
r1.contains(r2),
expected_contains,
"contains mismatch: {ctx}"
);
assert_eq!(
r1.intersects(r2),
expected_intersects,
"intersects mismatch: {ctx}"
);
let u = r1.union(r2);
assert_close(
u.lat.lo,
deg2rad(expected_u_lat_lo),
1e-13,
&format!("union lat_lo {ctx}"),
);
assert_close(
u.lat.hi,
deg2rad(expected_u_lat_hi),
1e-13,
&format!("union lat_hi {ctx}"),
);
assert_close(
u.lng.lo,
deg2rad(expected_u_lng_lo),
1e-13,
&format!("union lng_lo {ctx}"),
);
assert_close(
u.lng.hi,
deg2rad(expected_u_lng_hi),
1e-13,
&format!("union lng_hi {ctx}"),
);
let i = r1.intersection(r2);
if expected_i_lat_lo > expected_i_lat_hi {
assert!(i.is_empty(), "expected empty intersection: {ctx}");
} else {
assert_close(
i.lat.lo,
deg2rad(expected_i_lat_lo),
1e-13,
&format!("isect lat_lo {ctx}"),
);
assert_close(
i.lat.hi,
deg2rad(expected_i_lat_hi),
1e-13,
&format!("isect lat_hi {ctx}"),
);
assert_close(
i.lng.lo,
deg2rad(expected_i_lng_lo),
1e-13,
&format!("isect lng_lo {ctx}"),
);
assert_close(
i.lng.hi,
deg2rad(expected_i_lng_hi),
1e-13,
&format!("isect lng_hi {ctx}"),
);
}
}
}
#[test]
fn test_cpp_loop_area() {
for line in section_lines("LOOP_AREA") {
let parts: Vec<&str> = line.rsplitn(7, ',').collect();
assert_eq!(parts.len(), 7, "bad line: {line}");
let is_normalized = parse_bool(parts[0]);
let cz = parse_f64(parts[1]);
let cy = parse_f64(parts[2]);
let cx = parse_f64(parts[3]);
let turning_angle = parse_f64(parts[4]);
let area = parse_f64(parts[5]);
let vertices_str = parts[6];
let vertex_strs: Vec<&str> = vertices_str.split(';').collect();
let mut vertices = Vec::new();
for vs in &vertex_strs {
let coords: Vec<&str> = vs.split(',').collect();
assert_eq!(coords.len(), 3, "bad vertex: {vs}");
let x = parse_f64(coords[0]);
let y = parse_f64(coords[1]);
let z = parse_f64(coords[2]);
vertices.push(Point::from_coords(x, y, z));
}
let l = Loop::new(vertices);
assert_close(
l.area(),
area,
1e-13,
&format!("area for loop with {} vertices", vertex_strs.len()),
);
assert_close(
l.turning_angle(),
turning_angle,
1e-13,
&format!("turning_angle for loop with {} vertices", vertex_strs.len()),
);
let centroid = l.centroid();
assert_close(
centroid.x(),
cx,
1e-13,
&format!("centroid x for loop with {} vertices", vertex_strs.len()),
);
assert_close(
centroid.y(),
cy,
1e-13,
&format!("centroid y for loop with {} vertices", vertex_strs.len()),
);
assert_close(
centroid.z(),
cz,
1e-13,
&format!("centroid z for loop with {} vertices", vertex_strs.len()),
);
assert_eq!(
l.is_normalized(),
is_normalized,
"is_normalized mismatch for loop with {} vertices",
vertex_strs.len()
);
}
}