#![expect(
clippy::cast_possible_truncation,
reason = "cell level arithmetic — bounded by MAX_CELL_LEVEL"
)]
use crate::r2;
use crate::r3::Vector;
use crate::s1::ChordAngle;
use crate::s2::coords::{
Face, Level, POS_TO_IJ, POS_TO_ORIENTATION, face_uv_to_xyz, face_xyz_to_uv, face_xyz_to_uvw,
get_u_axis, get_u_norm, get_v_axis, get_v_norm, uv_to_st,
};
use crate::s2::{Cap, CellId, LatLng, Point, Rect, ij_level_to_bound_uv, size_ij};
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI};
const POLE_MIN_LAT: f64 = 0.615479708670387;
#[must_use]
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum CellEdge {
#[default]
Bottom = 0,
Right = 1,
Top = 2,
Left = 3,
}
impl CellEdge {
pub const ALL: [CellEdge; 4] = [
CellEdge::Bottom,
CellEdge::Right,
CellEdge::Top,
CellEdge::Left,
];
}
#[must_use]
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Cell {
face: Face,
level: Level,
orientation: u8,
id: CellId,
uv: r2::Rect,
}
impl PartialEq for Cell {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}
impl Default for Cell {
fn default() -> Self {
Self::from_cell_id(CellId::from_face(0))
}
}
impl Cell {
#[inline]
pub fn from_cell_id(id: CellId) -> Self {
let (f, i, j, o) = id.to_face_ij_orientation();
Cell {
face: f,
level: id.level(),
orientation: o,
id,
uv: ij_level_to_bound_uv(i, j, id.level()),
}
}
#[inline]
pub fn from_point(p: Point) -> Self {
Self::from_cell_id(CellId::from_point(&p))
}
#[inline]
pub fn from_lat_lng(ll: LatLng) -> Self {
Self::from_cell_id(CellId::from_lat_lng(&ll))
}
#[inline]
pub fn from_face(face: impl Into<u8>) -> Self {
Self::from_cell_id(CellId::from_face(face))
}
#[inline]
pub fn from_face_pos_level(face: impl Into<u8>, pos: u64, level: impl Into<Level>) -> Self {
Self::from_cell_id(CellId::from_face_pos_level(face, pos, level))
}
#[inline]
pub fn face(self) -> Face {
self.face
}
#[inline]
pub fn level(self) -> Level {
self.level
}
#[inline]
pub fn id(self) -> CellId {
self.id
}
#[inline]
pub fn orientation(self) -> u8 {
self.orientation
}
#[inline]
pub fn is_leaf(self) -> bool {
self.level == Level::MAX
}
#[inline]
pub fn size_ij(self) -> i32 {
size_ij(self.level)
}
#[inline]
pub fn size_st(self) -> f64 {
CellId::size_st(self.level)
}
#[inline]
pub fn bound_uv(self) -> r2::Rect {
self.uv
}
#[inline]
pub fn vertex(self, k: usize) -> Point {
Point(self.vertex_raw(k).vector().normalize())
}
#[inline]
pub fn vertex_raw(self, k: usize) -> Point {
let verts = self.uv.vertices();
Point(face_uv_to_xyz(self.face, verts[k].x, verts[k].y))
}
#[inline]
pub fn edge(self, k: CellEdge) -> Point {
Point(self.edge_raw(k).vector().normalize())
}
#[inline]
pub fn edge_raw(self, k: CellEdge) -> Point {
match k {
CellEdge::Bottom => Point(get_v_norm(self.face, self.uv.y.lo)),
CellEdge::Right => Point(get_u_norm(self.face, self.uv.x.hi)),
CellEdge::Top => Point(get_v_norm(self.face, self.uv.y.hi) * -1.0),
CellEdge::Left => Point(get_u_norm(self.face, self.uv.x.lo) * -1.0),
}
}
#[inline]
pub fn center(self) -> Point {
self.id.to_point()
}
#[inline]
pub fn center_raw(self) -> Point {
self.id.to_point_raw()
}
#[inline]
pub fn uv_coord_of_edge(self, k: CellEdge) -> f64 {
match k {
CellEdge::Bottom => self.uv.y.lo,
CellEdge::Right => self.uv.x.hi,
CellEdge::Top => self.uv.y.hi,
CellEdge::Left => self.uv.x.lo,
}
}
#[inline]
pub fn ij_coord_of_edge(self, k: CellEdge) -> i32 {
let limit_ij = f64::from(size_ij(0));
(limit_ij * uv_to_st(self.uv_coord_of_edge(k))).round() as i32
}
pub fn children(self) -> Option<[Cell; 4]> {
if self.id.is_leaf() {
return None;
}
let uv_mid = self.id.center_uv();
let mut cid = self.id.child_begin();
let mut children = [Cell::default(); 4];
for pos in 0..4 {
children[pos] = Cell {
face: self.face,
level: self.level + 1,
orientation: self.orientation ^ POS_TO_ORIENTATION[pos],
id: cid,
uv: r2::Rect::default(),
};
let ij = POS_TO_IJ[self.orientation as usize][pos];
let i = ij >> 1;
let j = ij & 1;
children[pos].uv.x = if i == 1 {
crate::r1::Interval::new(uv_mid.x, self.uv.x.hi)
} else {
crate::r1::Interval::new(self.uv.x.lo, uv_mid.x)
};
children[pos].uv.y = if j == 1 {
crate::r1::Interval::new(uv_mid.y, self.uv.y.hi)
} else {
crate::r1::Interval::new(self.uv.y.lo, uv_mid.y)
};
cid = cid.next();
}
Some(children)
}
pub fn average_area_for_level(level: impl Into<Level>) -> f64 {
4.0 * PI / (6.0 * (4.0_f64).powi(level.into().as_i32()))
}
pub fn average_area(self) -> f64 {
Cell::average_area_for_level(self.level)
}
pub fn approx_area(self) -> f64 {
if self.level < Level::new(2) {
return self.average_area();
}
let flat_area = 0.5
* (self.vertex(2).vector() - self.vertex(0).vector())
.cross(self.vertex(3).vector() - self.vertex(1).vector())
.norm();
flat_area * 2.0 / (1.0 + (1.0 - (flat_area / PI).min(1.0)).sqrt())
}
pub fn exact_area(self) -> f64 {
let v0 = self.vertex(0);
let v1 = self.vertex(1);
let v2 = self.vertex(2);
let v3 = self.vertex(3);
crate::s2::point_measures::point_area(v0, v1, v2)
+ crate::s2::point_measures::point_area(v0, v2, v3)
}
#[inline]
pub fn contains_point(self, p: Point) -> bool {
let uv = face_xyz_to_uv(self.face, &p.vector());
let Some((u, v)) = uv else {
return false;
};
let margin = (5.0 / 3.0) * f64::EPSILON;
self.uv
.expanded_by_margin(margin)
.contains_point(r2::Point::new(u, v))
}
#[inline]
pub fn contains_cell(self, other: Cell) -> bool {
self.id.contains(other.id)
}
#[inline]
pub fn intersects_cell(self, other: Cell) -> bool {
self.id.intersects(other.id)
}
pub fn cap_bound(self) -> Cap {
let uv_center = self.uv.center();
let cap_center = Point(face_uv_to_xyz(self.face, uv_center.x, uv_center.y).normalize());
let mut cap = Cap::from_point(cap_center);
for k in 0..4 {
cap = cap.add_point(self.vertex(k));
}
cap
}
pub fn rect_bound(self) -> Rect {
if self.level > Level::MIN {
let u = self.uv.x.lo + self.uv.x.hi;
let v = self.uv.y.lo + self.uv.y.hi;
let i = if get_u_axis(self.face).z == 0.0 {
if u < 0.0 { 1 } else { 0 }
} else if u > 0.0 {
1
} else {
0
};
let j = if get_v_axis(self.face).z == 0.0 {
if v < 0.0 { 1 } else { 0 }
} else if v > 0.0 {
1
} else {
0
};
let lat = crate::r1::Interval::from_point(self.latitude(i, j))
.add_point(self.latitude(1 - i, 1 - j));
let lng = crate::s1::Interval::empty()
.add_point(self.longitude(i, 1 - j))
.add_point(self.longitude(1 - i, j));
let eps2 = 2.0 * f64::EPSILON;
return Rect::new(lat, lng)
.expanded(LatLng::from_radians(eps2, eps2))
.polar_closure();
}
let bound = match self.face {
Face::F0 => Rect::new(
crate::r1::Interval::new(-FRAC_PI_4, FRAC_PI_4),
crate::s1::Interval::new(-FRAC_PI_4, FRAC_PI_4),
),
Face::F1 => Rect::new(
crate::r1::Interval::new(-FRAC_PI_4, FRAC_PI_4),
crate::s1::Interval::new(FRAC_PI_4, 3.0 * FRAC_PI_4),
),
Face::F2 => Rect::new(
crate::r1::Interval::new(POLE_MIN_LAT - 0.5 * f64::EPSILON, FRAC_PI_2),
crate::s1::Interval::full(),
),
Face::F3 => Rect::new(
crate::r1::Interval::new(-FRAC_PI_4, FRAC_PI_4),
crate::s1::Interval::new(3.0 * FRAC_PI_4, -3.0 * FRAC_PI_4),
),
Face::F4 => Rect::new(
crate::r1::Interval::new(-FRAC_PI_4, FRAC_PI_4),
crate::s1::Interval::new(-3.0 * FRAC_PI_4, -FRAC_PI_4),
),
Face::F5 => Rect::new(
crate::r1::Interval::new(-FRAC_PI_2, -(POLE_MIN_LAT - 0.5 * f64::EPSILON)),
crate::s1::Interval::full(),
),
};
bound.expanded(LatLng::from_radians(f64::EPSILON, 0.0))
}
pub fn cell_union_bound(self) -> Vec<CellId> {
vec![self.id]
}
#[inline]
pub fn distance_to_point(self, p: Point) -> ChordAngle {
self.distance_internal(p, true)
}
pub fn distance_to_edge(self, a: Point, b: Point) -> ChordAngle {
use crate::s1::ChordAngle;
use crate::s2::edge_distances;
if self.contains_point(a) || self.contains_point(b) {
return ChordAngle::ZERO;
}
let mut min_dist = ChordAngle::INFINITY;
for k in 0..4 {
let v0 = self.vertex(k);
let v1 = self.vertex((k + 1) % 4);
let (cp_a, cp_b) = edge_distances::edge_pair_closest_points(a, b, v0, v1);
let d = cp_a.chord_angle(cp_b);
if d < min_dist {
min_dist = d;
}
}
min_dist
}
pub fn distance_to_cell(self, other: Cell) -> ChordAngle {
use crate::s1::ChordAngle;
use crate::s2::edge_distances;
for k in 0..4 {
if self.contains_point(other.vertex(k)) || other.contains_point(self.vertex(k)) {
return ChordAngle::ZERO;
}
}
let mut min_dist = ChordAngle::INFINITY;
for i in 0..4 {
let a0 = self.vertex(i);
let a1 = self.vertex((i + 1) % 4);
for j in 0..4 {
let b0 = other.vertex(j);
let b1 = other.vertex((j + 1) % 4);
let (cp_a, cp_b) = edge_distances::edge_pair_closest_points(a0, a1, b0, b1);
let d = cp_a.chord_angle(cp_b);
if d < min_dist {
min_dist = d;
}
}
}
min_dist
}
pub fn max_distance_to_point(self, p: Point) -> ChordAngle {
use crate::s1::ChordAngle;
let target_uvw = face_xyz_to_uvw(self.face, &p.vector());
let max_dist = chord_max(
chord_max(
self.vertex_chord_dist(&target_uvw, 0, 0),
self.vertex_chord_dist(&target_uvw, 1, 0),
),
chord_max(
self.vertex_chord_dist(&target_uvw, 0, 1),
self.vertex_chord_dist(&target_uvw, 1, 1),
),
);
if max_dist <= ChordAngle::RIGHT {
return max_dist;
}
ChordAngle::STRAIGHT - self.distance_to_point(-p)
}
pub fn max_distance_to_edge(self, a: Point, b: Point) -> ChordAngle {
use crate::s1::ChordAngle;
let max_dist = chord_max(self.max_distance_to_point(a), self.max_distance_to_point(b));
if max_dist <= ChordAngle::RIGHT {
return max_dist;
}
ChordAngle::STRAIGHT - self.distance_to_edge(-a, -b)
}
pub fn max_distance_to_cell(self, other: Cell) -> ChordAngle {
use crate::s1::ChordAngle;
use crate::s2::edge_distances;
let opposite_face = other.face.opposite();
if self.face == opposite_face {
let antipodal_uv = r2::Rect {
x: other.uv.y,
y: other.uv.x,
};
if self.uv.intersects(antipodal_uv) {
return ChordAngle::STRAIGHT;
}
}
let va: [Point; 4] = std::array::from_fn(|i| self.vertex(i));
let vb: [Point; 4] = std::array::from_fn(|i| other.vertex(i));
let mut max_dist = ChordAngle::NEGATIVE;
for i in 0..4 {
for j in 0..4 {
let (d, updated) =
edge_distances::update_max_distance(va[i], vb[j], vb[(j + 1) & 3], max_dist);
if updated {
max_dist = d;
}
let (d, updated) =
edge_distances::update_max_distance(vb[i], va[j], va[(j + 1) & 3], max_dist);
if updated {
max_dist = d;
}
}
}
max_dist
}
#[inline]
pub fn boundary_distance(self, target: Point) -> ChordAngle {
self.distance_internal(target, false)
}
#[inline]
pub fn is_distance_less(self, target: Cell, limit: ChordAngle) -> bool {
self.distance_to_cell(target) < limit
}
#[inline]
pub fn is_distance_less_or_equal(self, target: Cell, limit: ChordAngle) -> bool {
self.distance_to_cell(target) <= limit
}
#[inline]
pub fn is_max_distance_less(self, target: Cell, limit: ChordAngle) -> bool {
self.max_distance_to_cell(target) < limit
}
#[inline]
pub fn is_max_distance_less_or_equal(self, target: Cell, limit: ChordAngle) -> bool {
self.max_distance_to_cell(target) <= limit
}
fn distance_internal(self, target_xyz: Point, to_interior: bool) -> ChordAngle {
let target = face_xyz_to_uvw(self.face, &target_xyz.vector());
let dir00 = target.x - target.z * self.uv.x.lo;
let dir01 = target.x - target.z * self.uv.x.hi;
let dir10 = target.y - target.z * self.uv.y.lo;
let dir11 = target.y - target.z * self.uv.y.hi;
let mut inside = true;
if dir00 < 0.0 {
inside = false;
if self.v_edge_is_closest(&target, 0) {
return Self::edge_distance(-dir00, self.uv.x.lo);
}
}
if dir01 > 0.0 {
inside = false;
if self.v_edge_is_closest(&target, 1) {
return Self::edge_distance(dir01, self.uv.x.hi);
}
}
if dir10 < 0.0 {
inside = false;
if self.u_edge_is_closest(&target, 0) {
return Self::edge_distance(-dir10, self.uv.y.lo);
}
}
if dir11 > 0.0 {
inside = false;
if self.u_edge_is_closest(&target, 1) {
return Self::edge_distance(dir11, self.uv.y.hi);
}
}
if inside {
if to_interior {
return ChordAngle::ZERO;
}
let d0 = Self::edge_distance(-dir00, self.uv.x.lo);
let d1 = Self::edge_distance(dir01, self.uv.x.hi);
let d2 = Self::edge_distance(-dir10, self.uv.y.lo);
let d3 = Self::edge_distance(dir11, self.uv.y.hi);
return chord_min(chord_min(d0, d1), chord_min(d2, d3));
}
let d0 = self.vertex_chord_dist(&target, 0, 0);
let d1 = self.vertex_chord_dist(&target, 1, 0);
let d2 = self.vertex_chord_dist(&target, 0, 1);
let d3 = self.vertex_chord_dist(&target, 1, 1);
chord_min(chord_min(d0, d1), chord_min(d2, d3))
}
fn vertex_chord_dist(self, p: &Vector, i: usize, j: usize) -> ChordAngle {
let u = if i == 0 { self.uv.x.lo } else { self.uv.x.hi };
let v = if j == 0 { self.uv.y.lo } else { self.uv.y.hi };
let vertex = Vector::new(u, v, 1.0).normalize();
Point(vertex).chord_angle(Point(*p))
}
fn u_edge_is_closest(self, p: &Vector, v_end: usize) -> bool {
let u0 = self.uv.x.lo;
let u1 = self.uv.x.hi;
let v = if v_end == 0 {
self.uv.y.lo
} else {
self.uv.y.hi
};
let dir0 = Vector::new(v * v + 1.0, -u0 * v, -u0);
let dir1 = Vector::new(v * v + 1.0, -u1 * v, -u1);
p.dot(dir0) > 0.0 && p.dot(dir1) < 0.0
}
fn v_edge_is_closest(self, p: &Vector, u_end: usize) -> bool {
let v0 = self.uv.y.lo;
let v1 = self.uv.y.hi;
let u = if u_end == 0 {
self.uv.x.lo
} else {
self.uv.x.hi
};
let dir0 = Vector::new(-u * v0, u * u + 1.0, -v0);
let dir1 = Vector::new(-u * v1, u * u + 1.0, -v1);
p.dot(dir0) > 0.0 && p.dot(dir1) < 0.0
}
fn edge_distance(dir_ij: f64, uv: f64) -> ChordAngle {
let pq2 = (dir_ij * dir_ij) / (1.0 + uv * uv);
let qr = 1.0 - (1.0 - pq2).sqrt();
ChordAngle::from_length2(pq2 + qr * qr)
}
fn latitude(self, i: usize, j: usize) -> f64 {
let u = if i == 0 { self.uv.x.lo } else { self.uv.x.hi };
let v = if j == 0 { self.uv.y.lo } else { self.uv.y.hi };
LatLng::latitude(Point(face_uv_to_xyz(self.face, u, v))).radians()
}
fn longitude(self, i: usize, j: usize) -> f64 {
let u = if i == 0 { self.uv.x.lo } else { self.uv.x.hi };
let v = if j == 0 { self.uv.y.lo } else { self.uv.y.hi };
LatLng::longitude(Point(face_uv_to_xyz(self.face, u, v))).radians()
}
}
#[inline]
fn chord_min(a: ChordAngle, b: ChordAngle) -> ChordAngle {
if a < b { a } else { b }
}
fn chord_max(a: ChordAngle, b: ChordAngle) -> ChordAngle {
if a > b { a } else { b }
}
impl From<CellId> for Cell {
fn from(id: CellId) -> Self {
Cell::from_cell_id(id)
}
}
impl std::fmt::Display for Cell {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.id)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn is_send_sync<T: Sized + Send + Sync + Unpin>() {}
#[test]
fn cell_is_send_sync() {
is_send_sync::<Cell>();
}
#[test]
fn test_from_cell_id() {
for face in Face::iter() {
let id = CellId::from_face(face);
let c = Cell::from_cell_id(id);
assert_eq!(c.face(), face);
assert_eq!(c.level(), 0);
assert_eq!(c.id(), id);
}
}
#[test]
fn test_from_point() {
let p = Point::from_coords(1.0, 0.0, 0.0);
let c = Cell::from_point(p);
assert!(c.is_leaf());
assert!(c.contains_point(p));
}
#[test]
fn test_vertex_count() {
let c = Cell::from_cell_id(CellId::from_face(0));
for k in 0..4 {
let v = c.vertex(k);
assert!(
(v.vector().norm() - 1.0).abs() < 1e-14,
"vertex {k} not unit length: {}",
v.vector().norm(),
);
}
}
#[test]
fn test_edge_raw_inward() {
let c = Cell::from_cell_id(CellId::from_face(0));
let center = c.center();
for k in CellEdge::ALL {
let e = c.edge_raw(k);
assert!(
center.vector().dot(e.vector()) > 0.0,
"edge {k:?} not pointing inward",
);
}
}
#[test]
fn test_children() {
let parent = Cell::from_cell_id(CellId::from_face(0));
let children = parent.children().expect("should have children");
for child in &children {
assert_eq!(child.level(), 1);
assert_eq!(child.face(), Face::F0);
assert!(parent.contains_cell(*child));
}
}
#[test]
fn test_children_leaf() {
let p = Point::from_coords(1.0, 0.0, 0.0);
let leaf = Cell::from_point(p);
assert!(leaf.is_leaf());
assert!(leaf.children().is_none());
}
#[test]
fn test_contains_point_center() {
for face in Face::iter() {
let c = Cell::from_cell_id(CellId::from_face(face));
assert!(c.contains_point(c.center()));
}
}
#[test]
fn test_contains_cell() {
let parent = Cell::from_cell_id(CellId::from_face(0));
let child = Cell::from_cell_id(CellId::from_face(0).children()[0]);
assert!(parent.contains_cell(child));
assert!(!child.contains_cell(parent));
}
#[test]
fn test_intersects_cell() {
let c0 = Cell::from_cell_id(CellId::from_face(0));
let c1 = Cell::from_cell_id(CellId::from_face(1));
assert!(!c0.intersects_cell(c1));
assert!(c0.intersects_cell(c0));
}
#[test]
fn test_cap_bound() {
for face in Face::iter() {
let c = Cell::from_cell_id(CellId::from_face(face));
let cap = c.cap_bound();
for k in 0..4 {
assert!(cap.contains_point(c.vertex(k)));
}
}
}
#[test]
fn test_rect_bound() {
for face in Face::iter() {
let c = Cell::from_cell_id(CellId::from_face(face));
let r = c.rect_bound();
assert!(r.is_valid());
for k in 0..4 {
assert!(
r.contains_point(c.vertex(k)),
"face {face}: rect_bound does not contain vertex {k}",
);
}
}
}
#[test]
fn test_rect_bound_subcells() {
let id = CellId::from_face(0);
for level in 1..=5u8 {
let child_id = id.child_begin_at_level(level);
let child = Cell::from_cell_id(child_id);
let r = child.rect_bound();
assert!(r.is_valid());
assert!(r.contains_point(child.center()));
}
}
#[test]
fn test_average_area() {
let c = Cell::from_cell_id(CellId::from_face(0));
let expected = 4.0 * PI / 6.0;
assert!((c.average_area() - expected).abs() < 1e-14);
}
#[test]
fn test_approx_area() {
let c = Cell::from_cell_id(CellId::from_face(0));
let avg = c.average_area();
let approx = c.approx_area();
assert!((approx - avg).abs() / avg < 0.05);
}
#[test]
fn test_cell_union_bound() {
let c = Cell::from_cell_id(CellId::from_face(0));
let bound = c.cell_union_bound();
assert_eq!(bound.len(), 1);
assert_eq!(bound[0], c.id());
}
#[test]
fn test_distance_to_point_inside() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(5));
let center = c.center();
assert_eq!(c.distance_to_point(center), ChordAngle::ZERO);
}
#[test]
fn test_distance_to_point_outside() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(10));
let far = Point::from_coords(-1.0, 0.0, 0.0);
let dist = c.distance_to_point(far);
assert!(
dist.to_angle().degrees() > 100.0,
"dist = {} deg",
dist.to_angle().degrees()
);
}
#[test]
fn test_distance_to_point_vertex() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(5));
let v = c.vertex(0);
assert!(c.distance_to_point(v) <= ChordAngle::from_length2(1e-20));
}
#[test]
fn test_distance_to_edge_crossing() {
let c = Cell::from_cell_id(CellId::from_face(0));
let a = Point::from_coords(1.0, -0.5, -0.5);
let b = Point::from_coords(1.0, 0.5, 0.5);
assert_eq!(c.distance_to_edge(a, b), ChordAngle::ZERO);
}
#[test]
fn test_distance_to_edge_far() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(10));
let a = Point::from_coords(-1.0, 0.0, -0.1);
let b = Point::from_coords(-1.0, 0.0, 0.1);
let dist = c.distance_to_edge(a, b);
assert!(dist.to_angle().degrees() > 100.0);
}
#[test]
fn test_distance_to_cell_same() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(5));
assert_eq!(c.distance_to_cell(c), ChordAngle::ZERO);
}
#[test]
fn test_distance_to_cell_adjacent() {
let id = CellId::from_face(0).child_begin_at_level(5);
let c1 = Cell::from_cell_id(id);
let c2 = Cell::from_cell_id(id.next());
let dist = c1.distance_to_cell(c2);
assert!(
dist.to_angle().degrees() < 0.01,
"adjacent cell dist = {} deg",
dist.to_angle().degrees()
);
}
#[test]
fn test_max_distance_to_point() {
let c = Cell::from_cell_id(CellId::from_face(0));
let p = c.center();
let max_dist = c.max_distance_to_point(p);
assert!(max_dist.to_angle().degrees() > 30.0);
}
#[test]
fn test_max_distance_to_cell() {
let c1 = Cell::from_cell_id(CellId::from_face(0));
let c2 = Cell::from_cell_id(CellId::from_face(3));
let max_dist = c1.max_distance_to_cell(c2);
assert!(max_dist.to_angle().degrees() > 90.0);
}
#[test]
fn test_center_raw() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(10));
let raw = c.center_raw();
let norm = c.center();
let normalized = Point(raw.vector().normalize());
let diff = (normalized.vector() - norm.vector()).norm();
assert!(diff < 1e-14);
}
#[test]
fn test_boundary_distance_outside() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(10));
let far = Point::from_coords(-1.0, 0.0, 0.0);
let bd = c.boundary_distance(far);
let dp = c.distance_to_point(far);
assert!(
(bd.length2() - dp.length2()).abs() < 1e-14,
"boundary dist {} != distance_to_point {}",
bd.length2(),
dp.length2()
);
}
#[test]
fn test_boundary_distance_inside() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(5));
let center = c.center();
assert_eq!(c.distance_to_point(center), ChordAngle::ZERO);
let bd = c.boundary_distance(center);
assert!(
bd > ChordAngle::ZERO,
"boundary_distance for interior point should be > 0, got {}",
bd.length2()
);
}
#[test]
fn test_is_distance_less() {
let c1 = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(5));
let c2 = Cell::from_cell_id(CellId::from_face(3).child_begin_at_level(5));
assert!(!c1.is_distance_less(c2, ChordAngle::from_degrees(10.0)));
assert!(c1.is_distance_less(c2, ChordAngle::STRAIGHT));
}
#[test]
fn test_is_distance_less_or_equal() {
let c = Cell::from_cell_id(CellId::from_face(0).child_begin_at_level(5));
assert!(c.is_distance_less_or_equal(c, ChordAngle::ZERO));
assert!(!c.is_distance_less(c, ChordAngle::ZERO));
}
#[test]
fn test_is_max_distance_less() {
let c1 = Cell::from_cell_id(CellId::from_face(0));
let c2 = Cell::from_cell_id(CellId::from_face(3));
assert!(!c1.is_max_distance_less(c2, ChordAngle::STRAIGHT));
}
#[test]
fn test_is_max_distance_less_or_equal() {
let c1 = Cell::from_cell_id(CellId::from_face(0));
let c2 = Cell::from_cell_id(CellId::from_face(0));
let max = c1.max_distance_to_cell(c2);
assert!(c1.is_max_distance_less_or_equal(c2, max));
}
#[test]
fn test_uv_coord_of_edge() {
let c = Cell::from_cell_id(CellId::from_face(0));
let uv = c.bound_uv();
assert!((c.uv_coord_of_edge(CellEdge::Bottom) - uv.y.lo).abs() < 1e-14);
assert!((c.uv_coord_of_edge(CellEdge::Right) - uv.x.hi).abs() < 1e-14);
assert!((c.uv_coord_of_edge(CellEdge::Top) - uv.y.hi).abs() < 1e-14);
assert!((c.uv_coord_of_edge(CellEdge::Left) - uv.x.lo).abs() < 1e-14);
}
#[test]
fn test_ij_coord_of_edge() {
let c = Cell::from_cell_id(CellId::from_face(0));
let ij0 = c.ij_coord_of_edge(CellEdge::Bottom);
let ij2 = c.ij_coord_of_edge(CellEdge::Top);
assert_eq!(ij0, 0, "bottom edge IJ should be 0");
assert_eq!(ij2, size_ij(0), "top edge IJ should be MAX_SIZE");
}
#[test]
fn test_from_face_constructor() {
for face in Face::iter() {
let c = Cell::from_face(face);
assert_eq!(c.face(), face);
assert_eq!(c.level(), 0);
}
}
#[test]
fn test_from_face_pos_level() {
let c1 = Cell::from_face_pos_level(0, 0, 1);
assert_eq!(c1.face(), Face::F0);
assert_eq!(c1.level(), 1);
}
#[test]
fn test_orientation() {
for face in Face::iter() {
let c = Cell::from_face(face);
assert!(c.orientation() < 4);
if let Some(children) = c.children() {
for child in &children {
assert!(child.orientation() < 4);
}
}
}
}
#[test]
fn test_size_ij_and_st() {
let c0 = Cell::from_face(0);
assert_eq!(c0.size_ij(), 1 << 30);
assert!((c0.size_st() - 1.0).abs() < 1e-14);
let leaf = Cell::from_point(Point::from_coords(1.0, 0.0, 0.0));
assert_eq!(leaf.size_ij(), 1);
assert!(leaf.size_st() > 0.0);
assert!(leaf.size_st() < 1e-8);
}
}
#[cfg(test)]
mod quickcheck_tests {
use super::*;
use quickcheck_macros::quickcheck;
#[quickcheck]
fn prop_cell_id_roundtrip(face: u8) -> bool {
let face = Face::from_u8(face % 6);
let id = CellId::from_face(face);
let c = Cell::from_cell_id(id);
c.id() == id && c.face() == face && c.level() == 0
}
#[test]
fn test_faces() {
use std::collections::HashMap;
let key = |p: Point| -> (i64, i64, i64) {
(
p.0.x.signum() as i64,
p.0.y.signum() as i64,
p.0.z.signum() as i64,
)
};
let mut vertex_counts: HashMap<(i64, i64, i64), i32> = HashMap::new();
for face in Face::iter() {
let id = CellId::from_face(face);
let cell = Cell::from_cell_id(id);
assert_eq!(cell.id(), id);
assert_eq!(cell.face(), face);
assert_eq!(cell.level(), 0);
assert!(!cell.is_leaf());
for k in 0..4 {
let vk = cell.vertex_raw(k);
*vertex_counts.entry(key(vk)).or_insert(0) += 1;
}
}
assert_eq!(vertex_counts.len(), 8, "should be 8 distinct cube vertices");
for count in vertex_counts.values() {
assert_eq!(*count, 3, "each vertex should be shared by exactly 3 faces");
}
}
#[test]
fn test_consistent_with_cell_id_from_point() {
let test_points = [
Point::from_coords(1.0, 0.0, 0.0),
Point::from_coords(0.0, 1.0, 0.0),
Point::from_coords(0.0, 0.0, 1.0),
Point::from_coords(1.0, 1.0, 1.0).normalize(),
Point::from_coords(-1.0, 0.5, 0.3).normalize(),
Point::from_coords(0.1, -0.9, 0.4).normalize(),
];
for p in &test_points {
let id = CellId::from_point(p);
let cell = Cell::from_cell_id(id);
assert!(
cell.contains_point(*p),
"Cell({id:?}) does not contain its source point",
);
}
}
#[test]
fn test_distance_to_point_zero_for_center() {
for face in Face::iter() {
let cell = Cell::from_cell_id(CellId::from_face(face));
let dist = cell.distance_to_point(cell.center());
assert_eq!(dist, ChordAngle::ZERO);
}
}
#[test]
fn test_distance_to_cell_self_is_zero() {
let cell = Cell::from_cell_id(CellId::from_face(0));
assert_eq!(cell.distance_to_cell(cell), ChordAngle::ZERO);
}
#[test]
fn test_max_distance_to_point_vertex() {
use crate::s1::ChordAngle;
let cell = Cell::from_cell_id(CellId::from_face(0));
let max_dist = cell.max_distance_to_point(cell.vertex(0));
assert!(
max_dist > ChordAngle::ZERO,
"max distance to vertex should be > 0"
);
}
#[test]
fn test_cell_union_bound_includes_only_self() {
let id = CellId::from_token("123456789");
let cell = Cell::from_cell_id(id);
let bound = cell.cell_union_bound();
assert_eq!(bound.len(), 1);
assert_eq!(bound[0], cell.id());
}
#[test]
fn test_consistent_with_cell_id_from_point_example1() {
let p = Point::from_coords(
0.38203141040035632,
0.030196609707941954,
0.9236558700239289,
);
let cell = Cell::from_cell_id(CellId::from_point(&p));
assert!(
cell.contains_point(p),
"Cell from point should contain that point"
);
}
#[test]
fn test_cap_bound_contains_all_vertices() {
for face in Face::iter() {
let cell = Cell::from_cell_id(CellId::from_face(face));
let cap = cell.cap_bound();
for k in 0..4 {
assert!(
cap.contains_point(cell.vertex(k)),
"cap bound of face {face} should contain vertex {k}",
);
}
}
}
#[test]
fn test_rect_bound_contains_center() {
for face in Face::iter() {
let cell = Cell::from_cell_id(CellId::from_face(face));
let rect = cell.rect_bound();
let center_ll = LatLng::from_point(cell.center());
assert!(
rect.contains_lat_lng(center_ll),
"rect bound of face {face} should contain its center",
);
}
}
#[quickcheck]
fn prop_contains_center(face: u8) -> bool {
let face = face % 6;
let c = Cell::from_cell_id(CellId::from_face(face));
c.contains_point(c.center())
}
#[quickcheck]
fn prop_vertex_unit_length(face: u8, k: u8) -> bool {
let face = face % 6;
let k = (k % 4) as usize;
let c = Cell::from_cell_id(CellId::from_face(face));
(c.vertex(k).vector().norm() - 1.0).abs() < 1e-14
}
#[cfg(feature = "serde")]
#[quickcheck]
fn prop_serde_roundtrip(face: u8) -> bool {
let face = face % 6;
let c = Cell::from_cell_id(CellId::from_face(face));
let json1 = serde_json::to_string(&c).unwrap();
let back: Cell = serde_json::from_str(&json1).unwrap();
let json2 = serde_json::to_string(&back).unwrap();
let back2: Cell = serde_json::from_str(&json2).unwrap();
back == back2
}
#[cfg(feature = "serde")]
#[test]
fn test_serde_cell_edge_roundtrip() {
for e in [
CellEdge::Bottom,
CellEdge::Right,
CellEdge::Top,
CellEdge::Left,
] {
let json = serde_json::to_string(&e).unwrap();
let back: CellEdge = serde_json::from_str(&json).unwrap();
assert_eq!(e, back);
}
}
}