use crate::bmoc::MutableBmoc;
use crate::coords::LonLatT;
use crate::proj::{LAT_OF_SQUARE_CELL, best_starting_depth, has_best_starting_depth};
use crate::special_points_finder::arc_special_points;
use crate::sph_geom::cone::Cone;
use crate::sph_geom::coo3d::*;
use crate::sph_geom::frame::RefToLocalRotMatrix;
use crate::sph_geom::zone::Zone;
use crate::sph_geom::{ContainsSouthPoleMethod, Polygon};
use crate::{LonLat, TRANSITION_LATITUDE, TRANSITION_Z, unchecked};
use arrayvec::ArrayVec;
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4, PI, TAU};
use std::iter::once;
use std::sync::OnceLock;
const COS_LAT_OF_SQUARE_CELL: f64 = 0.921_317_731_923_561_3_f64;
impl super::Layer {
pub fn zone_coverage(
&self,
lon_min: f64,
lat_min: f64,
lon_max: f64,
lat_max: f64,
) -> MutableBmoc {
let zone = Zone::new(lon_min, lat_min, lon_max, lat_max);
let (depth_start, hashes_start) = zone
.smallest_enclosing_cone()
.map(|mec| {
let center = mec.center().lonlat();
let wider_than_180deg = zone.dlon() > PI;
if !wider_than_180deg
&& zone.contains(center.lon(), center.lat())
&& has_best_starting_depth(mec.radius())
{
let d = best_starting_depth(mec.radius()).min(self.depth);
let layer = Self::get(d);
let h = layer.hash(center);
let mut vals = layer
.neighbors(h)
.into_values()
.collect::<ArrayVec<u64, 9>>();
vals.push(h);
vals.sort_unstable();
(d, either::Left(vals.into_iter()))
} else {
(0, either::Right(0..12))
}
})
.unwrap_or((0, either::Right(0..12)));
let zone_perimeter = 2.0 * (zone.dlon() + zone.dlat());
let approx_cell_side_at_depth_max =
2.0 * (PI / unchecked::n_hash(self.depth) as f64).sqrt();
let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
6 * (2 + (zone_perimeter / approx_cell_side_at_depth_max) as usize),
);
let zone_vertices = zone.vertices();
let mut zone_vertices_hashes_flags = Vec::with_capacity(4);
if zone_vertices[0].1 > -FRAC_PI_2 {
let vertex_hash_sw = self.hash(zone_vertices[0]);
zone_vertices_hashes_flags.push((vertex_hash_sw, true));
}
if zone_vertices[1].1 < FRAC_PI_2 {
let (vertex_hash_nw, dx, dy) = self.hash_with_dxdy(zone_vertices[1]);
zone_vertices_hashes_flags.push((
vertex_hash_nw,
dx > 1e-15 && dy > 1e-15 && dx < 1.0 && dy < 1.0,
));
}
if zone_vertices[2].1 < FRAC_PI_2 {
let (vertex_hash_ne, dx, dy) = self.hash_with_dxdy(zone_vertices[2]);
zone_vertices_hashes_flags.push((
vertex_hash_ne,
dx > 1e-15 && dy > 1e-15 && dx < 1.0 && dy < 1.0,
));
}
if zone_vertices[3].1 > -FRAC_PI_2 {
let (vertex_hash_se, dx, dy) = self.hash_with_dxdy(zone_vertices[3]);
zone_vertices_hashes_flags.push((
vertex_hash_se,
dx > 1e-15 && dy > 1e-15 && dx < 1.0 && dy < 1.0,
));
}
for h in hashes_start {
self.zone_coverage_recur(
depth_start,
h,
&zone,
&zone_vertices_hashes_flags,
&mut bmoc_builder,
);
}
bmoc_builder.into_valid_unchecked()
}
fn zone_coverage_recur(
&self,
depth: u8,
hash: u64,
zone: &Zone,
zone_vertices_hashes_flags: &Vec<(u64, bool)>,
bmoc_builder: &mut MutableBmoc<false>,
) {
let shift = (self.depth - depth) << 1; let matches_zone_vertex_hash = |hash: u64| {
if shift == 0 {
for (h, flag) in zone_vertices_hashes_flags {
if *flag && *h == hash {
return true;
}
}
} else {
for (h, _) in zone_vertices_hashes_flags {
if *h >> shift == hash {
return true;
}
}
}
false
};
let [[l_s, b_s], [l_e, b_e], [l_n, b_n], [l_w, b_w]] =
Self::get(depth).vertices(hash).map(LonLat::as_f64s);
let n_in = zone.contains(l_s, b_s) as u8
+ zone.contains_exclusive(l_e, b_e) as u8
+ zone.contains_exclusive(l_n, b_n) as u8
+ zone.contains_exclusive(l_w, b_w) as u8;
if n_in == 4 && zone.is_lon_range_compatible(l_w, l_e) {
bmoc_builder.push_unchecked(depth, hash, true);
} else if n_in > 0 || matches_zone_vertex_hash(hash)
|| zone.crossed_vertically(l_n, b_s, b_n)
|| zone.crossed_vertically(l_s, b_s, b_n)
|| zone.crossed_horizontally(l_w, l_e, b_w)
|| zone.crossed_horizontally(l_w, l_e, b_e)
{
if depth == self.depth {
bmoc_builder.push_unchecked(depth, hash, false);
} else {
let hash = hash << 2;
let depth = depth + 1;
for mask in 0..4 {
self.zone_coverage_recur(
depth,
hash | mask,
zone,
zone_vertices_hashes_flags,
bmoc_builder,
);
}
}
}
}
pub fn cone_coverage_centers(&self, center: impl LonLatT, radius: f64) -> MutableBmoc {
self.cone_coverage_centers_impl(center.lon(), center.lat(), radius)
}
fn cone_coverage_centers_impl(
&self,
cone_lon: f64,
cone_lat: f64,
cone_radius: f64,
) -> MutableBmoc {
if cone_radius >= PI {
return self.allsky_bmoc_builder().into_valid_unchecked();
}
let cos_cone_lat = cone_lat.cos();
let shs_cone_radius = to_squared_half_segment(cone_radius);
if !has_best_starting_depth(cone_radius) {
let distances = largest_center_to_vertex_distances_with_radius(
0,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius,
);
let minmax_array = to_shs_min_max_array(cone_radius, &distances);
let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
self.n_moc_cell_in_cone_upper_bound(cone_radius),
);
for h in 0..12 {
self.cone_coverage_centers_recur(
0,
h,
shs_cone_radius,
&shs_computer(cone_lon, cone_lat, cos_cone_lat),
&minmax_array,
0,
&mut bmoc_builder,
);
}
return bmoc_builder.into_packed();
}
let depth_start = best_starting_depth(cone_radius);
if depth_start >= self.depth {
let center = self.hash([cone_lon, cone_lat]);
let mut neigs = self
.neighbors(center)
.into_values()
.filter(|&neigh| {
let [lon, lat] = self.center(neigh).as_f64s();
squared_half_segment(lon - cone_lon, lat - cone_lat, lon.cos(), cos_cone_lat)
<= shs_cone_radius
})
.collect::<Vec<_>>();
neigs.push(center);
neigs.sort_unstable();
let mut bmoc_builder = MutableBmoc::with_capacity(self.depth, neigs.len());
for neig in neigs {
bmoc_builder.push_unchecked(self.depth, neig, true);
}
bmoc_builder.into_packed()
} else {
let distances = largest_center_to_vertex_distances_with_radius(
depth_start,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius,
);
let minmax_array: Box<[MinMax]> = to_shs_min_max_array(cone_radius, &distances);
let root_layer = Self::get(depth_start);
let root_center_hash = root_layer.hash([cone_lon, cone_lat]);
let mut neigs: ArrayVec<u64, 9> = root_layer
.neighbors(root_center_hash)
.into_values()
.collect();
neigs.push(root_center_hash);
neigs.sort_unstable(); let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
self.n_moc_cell_in_cone_upper_bound(cone_radius),
);
for root_hash in neigs {
self.cone_coverage_centers_recur(
depth_start,
root_hash,
shs_cone_radius,
&shs_computer(cone_lon, cone_lat, cos_cone_lat),
&minmax_array,
0,
&mut bmoc_builder,
);
}
bmoc_builder.into_packed() }
}
#[allow(clippy::too_many_arguments)]
fn cone_coverage_centers_recur<F>(
&self,
depth: u8, hash: u64, shs_cone_radius: f64, shs_computer: &F, shs_minmax: &[MinMax], recur_depth: u8, bmoc_builder: &mut MutableBmoc<false>, ) where
F: Fn(LonLat) -> f64,
{
let center = Self::get(depth).center(hash);
let shs = shs_computer(center);
let MinMax { min, max } = shs_minmax[recur_depth as usize];
if shs <= min {
bmoc_builder.push_unchecked(depth, hash, true);
} else if shs <= max {
if depth == self.depth {
if shs <= shs_cone_radius {
bmoc_builder.push_unchecked(depth, hash, true);
} } else {
let hash = hash << 2;
let depth = depth + 1;
let recur_depth = recur_depth + 1;
self.cone_coverage_centers_recur(
depth,
hash,
shs_cone_radius,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_centers_recur(
depth,
hash | 1_u64,
shs_cone_radius,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_centers_recur(
depth,
hash | 2_u64,
shs_cone_radius,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_centers_recur(
depth,
hash | 3_u64,
shs_cone_radius,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
}
}
}
pub fn cone_coverage_fullin(&self, center: impl LonLatT, radius: f64) -> MutableBmoc {
self.cone_coverage_fullin_impl(center.lon(), center.lat(), radius)
}
pub fn cone_coverage_fullin_impl(
&self,
cone_lon: f64,
cone_lat: f64,
cone_radius: f64,
) -> MutableBmoc {
if cone_radius >= PI {
return self.allsky_bmoc_builder().into_valid_unchecked();
}
let cos_cone_lat = cone_lat.cos();
let shs_cone_radius = to_squared_half_segment(cone_radius);
if !has_best_starting_depth(cone_radius) {
let distances = largest_center_to_vertex_distances_with_radius(
0,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius,
);
let minmax_array: Box<[MinMax]> = to_shs_min_max_array(cone_radius, &distances);
let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
self.n_moc_cell_in_cone_upper_bound(cone_radius),
);
for h in 0..12 {
self.cone_coverage_fullin_recur(
0,
h,
shs_cone_radius,
&shs_computer(cone_lon, cone_lat, cos_cone_lat),
&minmax_array,
0,
&mut bmoc_builder,
);
}
return bmoc_builder.into_packed();
}
let depth_start = best_starting_depth(cone_radius);
if depth_start >= self.depth {
let center = self.hash([cone_lon, cone_lat]);
let mut neigs: ArrayVec<u64, 9> = self
.neighbors(center)
.into_values()
.filter(|neigh| {
let mut fully_in = true;
for LonLat { lon, lat } in self.vertices(*neigh) {
fully_in &= squared_half_segment(
lon - cone_lon,
lat - cone_lat,
lon.cos(),
cos_cone_lat,
) <= shs_cone_radius;
}
fully_in
})
.collect();
neigs.sort_unstable(); let mut bmoc_builder = MutableBmoc::with_capacity(self.depth, neigs.len());
for neig in neigs {
bmoc_builder.push_unchecked(self.depth, neig, true);
}
bmoc_builder.into_packed() } else {
let distances = largest_center_to_vertex_distances_with_radius(
depth_start,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius,
);
let minmax_array: Box<[MinMax]> = to_shs_min_max_array(cone_radius, &distances);
let root_layer = Self::get(depth_start);
let root_center_hash = root_layer.hash([cone_lon, cone_lat]);
let mut neigs: ArrayVec<u64, 9> = root_layer
.neighbors(root_center_hash)
.into_values()
.collect();
neigs.push(root_center_hash);
neigs.sort_unstable();
let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
self.n_moc_cell_in_cone_upper_bound(cone_radius),
);
for root_hash in neigs {
self.cone_coverage_fullin_recur(
depth_start,
root_hash,
shs_cone_radius,
&shs_computer(cone_lon, cone_lat, cos_cone_lat),
&minmax_array,
0,
&mut bmoc_builder,
);
}
bmoc_builder.into_packed()
}
}
#[allow(clippy::too_many_arguments)]
fn cone_coverage_fullin_recur<F>(
&self,
depth: u8, hash: u64, shs_cone_radius: f64, shs_computer: &F, shs_minmax: &[MinMax], recur_depth: u8, bmoc_builder: &mut MutableBmoc<false>, ) where
F: Fn(LonLat) -> f64,
{
let center = Self::get(depth).center(hash);
let shs = shs_computer(center);
let MinMax { min, max } = shs_minmax[recur_depth as usize];
if shs <= min {
bmoc_builder.push_unchecked(depth, hash, true);
} else if shs <= max {
if depth == self.depth {
if shs <= shs_cone_radius {
for vertex_coo in self.vertices(hash) {
if shs_computer(vertex_coo) > shs_cone_radius {
return; }
}
bmoc_builder.push_unchecked(depth, hash, true);
} } else {
let hash = hash << 2;
let depth = depth + 1;
let recur_depth = recur_depth + 1;
self.cone_coverage_fullin_recur(
depth,
hash,
shs_cone_radius,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_fullin_recur(
depth,
hash | 1_u64,
shs_cone_radius,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_fullin_recur(
depth,
hash | 2_u64,
shs_cone_radius,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_fullin_recur(
depth,
hash | 3_u64,
shs_cone_radius,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
}
}
}
pub fn ring_coverage_approx(
&self,
center: impl LonLatT,
radius_int: f64,
radius_ext: f64,
) -> MutableBmoc {
self.ring_coverage_approx_internal(center.lon(), center.lat(), radius_int, radius_ext)
.into_packed()
}
pub fn ring_coverage_approx_custom(
&self,
delta_depth: u8,
center: impl LonLatT,
radius_int: f64,
radius_ext: f64,
) -> MutableBmoc {
if delta_depth == 0 {
self.ring_coverage_approx(center, radius_int, radius_ext)
} else {
Self::get(self.depth + delta_depth)
.ring_coverage_approx_internal(center.lon(), center.lat(), radius_int, radius_ext)
.into_packed()
.lower_depth(self.depth)
.take()
}
}
fn ring_coverage_approx_internal(
&self,
cone_lon: f64,
cone_lat: f64,
cone_radius_int: f64,
cone_radius_ext: f64,
) -> MutableBmoc<false> {
assert!(cone_radius_ext <= PI);
assert!(
cone_radius_int < cone_radius_ext,
"{} >= {} ",
cone_radius_int,
cone_radius_ext
);
let cos_cone_lat = cone_lat.cos();
let shs_cone_radius_int = to_squared_half_segment(cone_radius_int);
if !has_best_starting_depth(cone_radius_ext) {
let distances_int = largest_center_to_vertex_distances_with_radius(
0,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius_int,
);
let minmax_int_array: Box<[MinMax]> =
to_shs_min_max_array(cone_radius_int, &distances_int);
let distances_ext = largest_center_to_vertex_distances_with_radius(
0,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius_ext,
);
let minmax_ext_array: Box<[MinMax]> =
to_shs_min_max_array(cone_radius_ext, &distances_ext);
let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
self.n_moc_cell_in_cone_upper_bound(cone_radius_ext),
);
for h in 0..12 {
self.ring_coverage_approx_recur(
0,
h,
&shs_computer(cone_lon, cone_lat, cos_cone_lat),
shs_cone_radius_int,
&minmax_int_array,
&minmax_ext_array,
0,
&mut bmoc_builder,
);
}
return bmoc_builder;
}
let depth_start = best_starting_depth(cone_radius_ext);
if depth_start >= self.depth {
let shs_max = to_squared_half_segment(
cone_radius_ext
+ largest_center_to_vertex_distance_with_radius(
depth_start,
cone_lon,
cone_lat,
cone_radius_ext,
),
);
let root_layer = Self::get(depth_start);
let center_hash_at_depth_start = root_layer.hash([cone_lon, cone_lat]);
let mut neigs: ArrayVec<u64, 8> = root_layer
.neighbors(center_hash_at_depth_start)
.values()
.filter(|neigh| {
for LonLat { lon, lat } in self.vertices(**neigh) {
if squared_half_segment(
lon - cone_lon,
lat - cone_lat,
lon.cos(),
cos_cone_lat,
) > shs_cone_radius_int
{
return true;
}
}
false
}) .map(h_to_h_and_shs(cone_lon, cone_lat, cos_cone_lat, root_layer))
.filter(shs_lower_than(shs_max))
.map(self.h_and_shs_to_lower_h(depth_start))
.collect();
neigs.sort_unstable(); let mut bmoc_builder = MutableBmoc::with_capacity(self.depth, neigs.len());
for neig in neigs {
bmoc_builder.push_unchecked(self.depth, neig, false);
}
bmoc_builder
} else {
let distances_int = largest_center_to_vertex_distances_with_radius(
depth_start,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius_int,
);
let minmax_int_array: Box<[MinMax]> =
to_shs_min_max_array(cone_radius_int, &distances_int);
let distances_ext = largest_center_to_vertex_distances_with_radius(
depth_start,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius_ext,
);
let minmax_ext_array: Box<[MinMax]> =
to_shs_min_max_array(cone_radius_ext, &distances_ext);
let root_layer = Self::get(depth_start);
let root_center_hash = root_layer.hash([cone_lon, cone_lat]);
let mut neigs: ArrayVec<u64, 9> = root_layer
.neighbors(root_center_hash)
.into_values()
.collect();
neigs.push(root_center_hash);
neigs.sort_unstable();
let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
self.n_moc_cell_in_cone_upper_bound(cone_radius_ext),
);
for root_hash in neigs {
self.ring_coverage_approx_recur(
depth_start,
root_hash,
&shs_computer(cone_lon, cone_lat, cos_cone_lat),
shs_cone_radius_int,
&minmax_int_array,
&minmax_ext_array,
0,
&mut bmoc_builder,
);
}
bmoc_builder
}
}
#[allow(clippy::too_many_arguments)]
fn ring_coverage_approx_recur<F>(
&self,
depth: u8, hash: u64, shs_computer: &F, shs_int_cone_radius: f64, shs_int_minmax: &[MinMax], shs_ext_minmax: &[MinMax], recur_depth: u8, bmoc_builder: &mut MutableBmoc<false>, ) where
F: Fn(LonLat) -> f64,
{
let center = Self::get(depth).center(hash);
let shs = shs_computer(center);
let MinMax {
min: min_int,
max: max_int,
} = shs_int_minmax[recur_depth as usize];
let MinMax {
min: min_ext,
max: max_ext,
} = shs_ext_minmax[recur_depth as usize];
if min_ext >= shs && shs >= max_int {
bmoc_builder.push_unchecked(depth, hash, true);
} else if max_ext >= shs && shs >= min_int {
if depth == self.depth {
for vertex_coo in self.vertices(hash) {
if shs_computer(vertex_coo) > shs_int_cone_radius {
bmoc_builder.push_unchecked(depth, hash, false); break;
}
}
} else {
let hash = hash << 2;
let depth = depth + 1;
let recur_depth = recur_depth + 1;
self.ring_coverage_approx_recur(
depth,
hash,
shs_computer,
shs_int_cone_radius,
shs_int_minmax,
shs_ext_minmax,
recur_depth,
bmoc_builder,
);
self.ring_coverage_approx_recur(
depth,
hash | 1_u64,
shs_computer,
shs_int_cone_radius,
shs_int_minmax,
shs_ext_minmax,
recur_depth,
bmoc_builder,
);
self.ring_coverage_approx_recur(
depth,
hash | 2_u64,
shs_computer,
shs_int_cone_radius,
shs_int_minmax,
shs_ext_minmax,
recur_depth,
bmoc_builder,
);
self.ring_coverage_approx_recur(
depth,
hash | 3_u64,
shs_computer,
shs_int_cone_radius,
shs_int_minmax,
shs_ext_minmax,
recur_depth,
bmoc_builder,
);
}
} }
pub fn cone_coverage_approx(
&self,
cone_lon: f64,
cone_lat: f64,
cone_radius: f64,
) -> MutableBmoc {
self.cone_coverage_approx_internal(cone_lon, cone_lat, cone_radius)
.into_packed()
}
pub fn cone_coverage_approx_custom(
&self,
delta_depth: u8,
cone_lon: f64,
cone_lat: f64,
cone_radius: f64,
) -> MutableBmoc {
if delta_depth == 0 {
self.cone_coverage_approx(cone_lon, cone_lat, cone_radius)
} else {
Self::get(self.depth + delta_depth)
.cone_coverage_approx_internal(cone_lon, cone_lat, cone_radius)
.into_packed()
.lower_depth(self.depth)
.take()
}
}
fn cone_coverage_approx_internal(
&self,
cone_lon: f64,
cone_lat: f64,
cone_radius: f64,
) -> MutableBmoc<false> {
if cone_radius >= PI {
return self.allsky_bmoc_builder();
}
let cos_cone_lat = cone_lat.cos();
if !has_best_starting_depth(cone_radius) {
let distances = largest_center_to_vertex_distances_with_radius(
0,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius,
);
let minmax_array: Box<[MinMax]> = to_shs_min_max_array(cone_radius, &distances);
let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
self.n_moc_cell_in_cone_upper_bound(cone_radius),
);
for h in 0..12 {
self.cone_coverage_approx_recur(
0,
h,
&shs_computer(cone_lon, cone_lat, cos_cone_lat),
&minmax_array,
0,
&mut bmoc_builder,
);
}
return bmoc_builder;
}
let depth_start = best_starting_depth(cone_radius);
if depth_start >= self.depth {
let shs_max = to_squared_half_segment(
cone_radius
+ largest_center_to_vertex_distance_with_radius(
depth_start,
cone_lon,
cone_lat,
cone_radius,
),
);
let root_layer = Self::get(depth_start);
let center_hash_at_depth_start = root_layer.hash([cone_lon, cone_lat]);
let mut neigs: Vec<u64> = root_layer
.neighbors(center_hash_at_depth_start)
.values()
.chain(once(¢er_hash_at_depth_start))
.map(h_to_h_and_shs(cone_lon, cone_lat, cos_cone_lat, root_layer))
.filter(shs_lower_than(shs_max))
.map(self.h_and_shs_to_lower_h(depth_start))
.collect();
neigs.sort_unstable(); neigs.dedup(); let mut bmoc_builder = MutableBmoc::with_capacity(self.depth, neigs.len());
for neig in neigs {
bmoc_builder.push_unchecked(self.depth, neig, false);
}
bmoc_builder
} else {
let distances = largest_center_to_vertex_distances_with_radius(
depth_start,
self.depth + 1,
cone_lon,
cone_lat,
cone_radius,
);
let minmax_array: Box<[MinMax]> = to_shs_min_max_array(cone_radius, &distances);
let root_layer = Self::get(depth_start);
let root_center_hash = root_layer.hash([cone_lon, cone_lat]);
let mut neigs: ArrayVec<u64, 9> = root_layer
.neighbors(root_center_hash)
.into_values()
.collect();
neigs.push(root_center_hash);
neigs.sort_unstable();
let mut bmoc_builder = MutableBmoc::with_capacity(
self.depth,
self.n_moc_cell_in_cone_upper_bound(cone_radius),
);
for root_hash in neigs {
self.cone_coverage_approx_recur(
depth_start,
root_hash,
&shs_computer(cone_lon, cone_lat, cos_cone_lat),
&minmax_array,
0,
&mut bmoc_builder,
);
}
bmoc_builder
}
}
fn cone_coverage_approx_recur<F>(
&self,
depth: u8, hash: u64, shs_computer: &F, shs_minmax: &[MinMax], recur_depth: u8, bmoc_builder: &mut MutableBmoc<false>, ) where
F: Fn(LonLat) -> f64,
{
let center = Self::get(depth).center(hash);
let shs = shs_computer(center);
let MinMax { min, max } = shs_minmax[recur_depth as usize];
if shs <= min {
bmoc_builder.push_unchecked(depth, hash, true);
} else if shs <= max {
if depth == self.depth {
bmoc_builder.push_unchecked(depth, hash, false);
} else {
let hash = hash << 2;
let depth = depth + 1;
let recur_depth = recur_depth + 1;
self.cone_coverage_approx_recur(
depth,
hash,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_approx_recur(
depth,
hash | 1_u64,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_approx_recur(
depth,
hash | 2_u64,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
self.cone_coverage_approx_recur(
depth,
hash | 3_u64,
shs_computer,
shs_minmax,
recur_depth,
bmoc_builder,
);
}
}
}
#[inline]
#[allow(dead_code)]
fn ncell_in_cone_upper_bound(&self, cone_radius: f64) -> usize {
let mut area_ratio = pow2(cone_radius) * ((self.n_hash >> 2) as f64);
area_ratio += 1.0_f64; let correction_factor = 1.25_f64 + 7.75_f64 / area_ratio;
(correction_factor * area_ratio) as usize
}
#[inline]
fn n_moc_cell_in_cone_upper_bound(&self, cone_radius: f64) -> usize {
const TWICE_SQRT_3: f64 = 2.0_f64 * 1.732_050_807_568_877_2_f64; 4_usize * (1_usize + (self.nside as f64 * TWICE_SQRT_3 * cone_radius + 0.99_f64) as usize)
}
pub fn box_coverage(&self, center: impl LonLatT, a: f64, b: f64, pa: f64) -> MutableBmoc {
let center = Coo3D::from_sph_coo(center.as_lonlat());
let vertices = Self::box2polygon(center.lon(), center.lat(), a, b, pa);
self.custom_polygon_coverage(
&vertices,
&ContainsSouthPoleMethod::ControlPointIn(center),
true,
)
}
fn box2polygon(lon: f64, lat: f64, a: f64, b: f64, pa: f64) -> Vec<(f64, f64)> {
assert!(
(0.0..TAU).contains(&lon),
"Expected: lon in [0, 2pi[. Actual: {}",
lon
);
assert!(
(-FRAC_PI_2..=FRAC_PI_2).contains(&lat),
"Expected: lat in [-pi/2, pi/2]. Actual: {}",
lat
);
assert!(
0.0 < a && a <= FRAC_PI_2,
"Expected: a in ]0, pi/2]. Actual: {}",
a
);
assert!(0.0 < b && b <= a, "Expected: b in ]0, a]. Actual: {}", b);
assert!(
(0.0..PI).contains(&pa),
"Expected: pa in [0, pi[. Actual: {}",
pa
);
let frame_rotation = RefToLocalRotMatrix::from_center(lon, lat);
let lon = a;
let (sin_lon, cos_lon) = lon.sin_cos();
let lat = (cos_lon * b.tan()).atan();
let (sin_lat, cos_lat) = lat.sin_cos();
let (sin_pa, cos_pa) = pa.sin_cos();
let (x1, y1, z1) = (cos_lon * cos_lat, sin_lon * cos_lat, sin_lat);
let (y2, z2) = (y1 * sin_pa - z1 * cos_pa, y1 * cos_pa + z1 * sin_pa);
let mut vertices = Vec::with_capacity(4);
vertices.push(frame_rotation.to_global_coo(x1, y2, z2));
let (y2, z2) = (y1 * sin_pa + z1 * cos_pa, y1 * cos_pa - z1 * sin_pa);
vertices.push(frame_rotation.to_global_coo(x1, y2, z2));
let (y2, z2) = (-y1 * sin_pa + z1 * cos_pa, -y1 * cos_pa - z1 * sin_pa);
vertices.push(frame_rotation.to_global_coo(x1, y2, z2));
let (y2, z2) = (-y1 * sin_pa - z1 * cos_pa, -y1 * cos_pa + z1 * sin_pa);
vertices.push(frame_rotation.to_global_coo(x1, y2, z2));
vertices
}
pub fn polygon_coverage(&self, vertices: &[(f64, f64)], exact_solution: bool) -> MutableBmoc {
self.custom_polygon_coverage(vertices, &ContainsSouthPoleMethod::Default, exact_solution)
}
pub fn custom_polygon_coverage(
&self,
vertices: &[(f64, f64)],
south_pole_method: &ContainsSouthPoleMethod,
exact_solution: bool,
) -> MutableBmoc {
let poly = Polygon::new_custom(
vertices
.iter()
.map(|(lon, lat)| LonLat {
lon: *lon,
lat: *lat,
})
.collect::<Vec<LonLat>>()
.into_boxed_slice(),
south_pole_method,
);
let bounding_cone: Cone = Cone::bounding_cone(poly.vertices());
let mut depth_start = 0;
let neigs = if let ContainsSouthPoleMethod::Default = south_pole_method {
if !has_best_starting_depth(bounding_cone.radius()) {
either::Right(0..12)
} else {
depth_start = best_starting_depth(bounding_cone.radius()).min(self.depth);
let root_layer = Self::get(depth_start);
let center = bounding_cone.center().lonlat();
let center_hash = root_layer.hash(center);
let mut neigs: ArrayVec<u64, 9> =
root_layer.neighbors(center_hash).into_values().collect();
neigs.push(center_hash);
neigs.sort_unstable();
either::Left(neigs.into_iter())
}
} else {
either::Right(0..12)
};
let mut sorted_poly_vertices_hash = self.hashes_vec(poly.vertices());
if exact_solution {
let vertices = poly.vertices();
let mut left = &vertices[vertices.len() - 1];
for right in vertices {
let special_lonlats = arc_special_points(left, right, 1.0e-14, 20);
sorted_poly_vertices_hash.append(&mut self.hashes_vec(&special_lonlats));
left = right;
}
}
sorted_poly_vertices_hash.sort_unstable();
sorted_poly_vertices_hash.dedup();
let sorted_poly_vertices_hash = sorted_poly_vertices_hash.into_boxed_slice();
let mut bmoc_builder = MutableBmoc::with_capacity(self.depth, 10_000_usize);
for root_hash in neigs {
self.polygon_coverage_recur(
&mut bmoc_builder,
depth_start,
root_hash,
&poly,
&sorted_poly_vertices_hash,
);
}
bmoc_builder.into_valid_unchecked()
}
fn polygon_coverage_recur(
&self,
moc_builder: &mut MutableBmoc<false>,
depth: u8,
hash: u64,
poly: &Polygon,
sorted_poly_vertices_hash: &[u64],
) {
if is_in_list(depth, hash, self.depth, sorted_poly_vertices_hash) {
if depth == self.depth {
moc_builder.push_unchecked(depth, hash, false);
} else {
let hash = hash << 2;
let depth = depth + 1;
self.polygon_coverage_recur(
moc_builder,
depth,
hash,
poly,
sorted_poly_vertices_hash,
);
self.polygon_coverage_recur(
moc_builder,
depth,
hash | 1,
poly,
sorted_poly_vertices_hash,
);
self.polygon_coverage_recur(
moc_builder,
depth,
hash | 2,
poly,
sorted_poly_vertices_hash,
);
self.polygon_coverage_recur(
moc_builder,
depth,
hash | 3,
poly,
sorted_poly_vertices_hash,
);
}
} else {
let (n_vertices_in_poly, poly_vertices) = n_vertices_in_poly(depth, hash, poly);
if (n_vertices_in_poly > 0 && n_vertices_in_poly < 4)
|| has_intersection(poly, poly_vertices)
{
if depth == self.depth {
moc_builder.push_unchecked(depth, hash, false);
} else {
let hash = hash << 2;
let depth = depth + 1;
self.polygon_coverage_recur(
moc_builder,
depth,
hash,
poly,
sorted_poly_vertices_hash,
);
self.polygon_coverage_recur(
moc_builder,
depth,
hash | 1,
poly,
sorted_poly_vertices_hash,
);
self.polygon_coverage_recur(
moc_builder,
depth,
hash | 2,
poly,
sorted_poly_vertices_hash,
);
self.polygon_coverage_recur(
moc_builder,
depth,
hash | 3,
poly,
sorted_poly_vertices_hash,
);
}
} else if n_vertices_in_poly == 4 {
moc_builder.push_unchecked(depth, hash, true);
}
}
}
fn hashes_vec<T: LonLatT + Copy>(&self, poly_vertices: &[T]) -> Vec<u64> {
poly_vertices
.iter()
.map(|coo| self.hash(*coo))
.collect::<Vec<u64>>()
}
fn allsky_bmoc_builder(&self) -> MutableBmoc<false> {
let mut bmoc_builder = MutableBmoc::with_capacity(self.depth, 12);
bmoc_builder.push_all_unchecked(0_u8, 0_u64, 12_u64, true);
bmoc_builder
}
#[inline]
fn h_and_shs_to_lower_h(&self, deeper_depth: u8) -> impl Fn((u64, f64)) -> u64 {
let twice_depth_diff = (deeper_depth - self.depth) << 1;
move |(h, _)| h >> twice_depth_diff
}
}
static CSTS_C2V: [OnceLock<ConstantsC2V>; 30] = [const { OnceLock::new() }; 30];
fn get_or_create(depth: u8) -> &'static ConstantsC2V {
CSTS_C2V[depth as usize].get_or_init(|| ConstantsC2V::new(depth))
}
struct ConstantsC2V {
slope_npc: f64,
intercept_npc: f64,
slope_eqr: f64,
intercept_eqr: f64,
coeff_x2_eqr: f64,
coeff_cst_eqr: f64,
}
impl ConstantsC2V {
fn new(depth: u8) -> ConstantsC2V {
let nside = unchecked::nside(depth);
let dist_cw = 1.0_f64 / (nside as f64); let one_min_dist_cw = 1.0_f64 - dist_cw;
let lat_north = f64::asin(1_f64 - (pow2(one_min_dist_cw) / 3_f64));
let d_min_npc = lat_north - TRANSITION_LATITUDE;
let d_max_npc: f64 = sphe_dist(squared_half_segment(
FRAC_PI_4 * dist_cw,
d_min_npc,
lat_north.cos(),
TRANSITION_LATITUDE.cos(),
));
debug_assert!(d_min_npc < d_max_npc);
let slope_npc: f64 = (d_max_npc - d_min_npc) / (FRAC_PI_4 * one_min_dist_cw); let intercept_npc: f64 = d_min_npc; let d_min_eqr_top = FRAC_PI_4 * dist_cw * COS_LAT_OF_SQUARE_CELL; let d_max_eqr_top = TRANSITION_LATITUDE - f64::asin(one_min_dist_cw * TRANSITION_Z);
debug_assert!(d_min_eqr_top < d_max_eqr_top);
let slope_eqr: f64 =
(d_max_eqr_top - d_min_eqr_top) / (TRANSITION_LATITUDE - LAT_OF_SQUARE_CELL); let intercept_eqr: f64 = d_min_eqr_top - slope_eqr * LAT_OF_SQUARE_CELL; let d_max = FRAC_PI_4 * dist_cw;
let coeff_cst_eqr: f64 = d_max;
let coeff_x2_eqr: f64 = (d_min_eqr_top - d_max) / pow2(LAT_OF_SQUARE_CELL);
ConstantsC2V {
slope_npc,
intercept_npc,
slope_eqr,
intercept_eqr,
coeff_x2_eqr,
coeff_cst_eqr,
}
}
}
#[inline]
fn sphe_dist(squared_half_segment: f64) -> f64 {
squared_half_segment.sqrt().asin() * 2.0
}
#[inline]
fn squared_half_segment(dlon: f64, dlat: f64, cos_lat1: f64, cos_lat2: f64) -> f64 {
let hsinla = (dlat * 0.5).sin();
let hsinlo = (dlon * 0.5).sin();
hsinla * hsinla + cos_lat1 * cos_lat2 * hsinlo * hsinlo
}
#[inline]
fn to_squared_half_segment(spherical_distance: f64) -> f64 {
let v = (spherical_distance * 0.5).sin();
v * v
}
#[inline]
fn pow2(x: f64) -> f64 {
x * x
}
pub fn largest_center_to_vertex_distance(depth: u8, lon: f64, lat: f64) -> f64 {
if depth == 0 {
return FRAC_PI_2 - TRANSITION_LATITUDE;
}
let lat_abs = lat.abs();
if lat_abs >= TRANSITION_LATITUDE {
largest_c2v_dist_in_npc(lon, get_or_create(depth))
} else if lat_abs >= LAT_OF_SQUARE_CELL {
largest_c2v_dist_in_eqr_top(lat_abs, get_or_create(depth))
} else {
largest_c2v_dist_in_eqr_bottom(lat_abs, get_or_create(depth))
}
}
pub fn largest_center_to_vertex_distance_with_radius(
depth: u8,
lon: f64,
lat: f64,
radius: f64,
) -> f64 {
if depth == 0 {
return FRAC_PI_2 - TRANSITION_LATITUDE;
}
let lat_abs = lat.abs();
let lat_max = lat_abs + radius;
let lat_min = lat_abs - radius;
if lat_max >= TRANSITION_LATITUDE {
largest_c2v_dist_in_npc_with_radius(lon, radius, get_or_create(depth))
} else if lat_min >= LAT_OF_SQUARE_CELL {
largest_c2v_dist_in_eqr_top_with_radius(lat_abs, radius, get_or_create(depth))
} else if lat_max <= LAT_OF_SQUARE_CELL {
largest_c2v_dist_in_eqr_bottom_with_radius(lat_abs, radius, get_or_create(depth))
} else {
let csts = get_or_create(depth);
f64::max(
largest_c2v_dist_in_eqr_top_with_radius(lat_abs, radius, csts),
largest_c2v_dist_in_eqr_bottom_with_radius(lat_abs, radius, csts),
)
}
}
pub fn largest_center_to_vertex_distances_with_radius(
mut from_depth: u8,
to_depth: u8,
lon: f64,
lat: f64,
radius: f64,
) -> Box<[f64]> {
let mut vec: Vec<f64> = Vec::with_capacity((to_depth - from_depth) as usize);
if from_depth == 0 {
vec.push(FRAC_PI_2 - TRANSITION_LATITUDE);
from_depth = 1_u8;
}
let lat_abs = lat.abs();
let lat_max = lat_abs + radius;
let lat_min = lat_abs - radius;
if lat_max >= TRANSITION_LATITUDE {
let mut lon = (FRAC_PI_4 - (lon % FRAC_PI_2)).abs();
lon = f64::min(lon + radius, FRAC_PI_4);
for depth in from_depth..to_depth {
let csts = get_or_create(depth);
vec.push(linear_approx(lon, csts.slope_npc, csts.intercept_npc));
}
} else if lat_min >= LAT_OF_SQUARE_CELL {
for depth in from_depth..to_depth {
vec.push(largest_c2v_dist_in_eqr_top(lat_max, get_or_create(depth)));
}
} else if lat_max <= LAT_OF_SQUARE_CELL {
let val_min = f64::max(lat_min, 0_f64);
for depth in from_depth..to_depth {
vec.push(largest_c2v_dist_in_eqr_bottom(
val_min,
get_or_create(depth),
));
}
} else {
let val_max = f64::min(lat_max, TRANSITION_LATITUDE);
let val_min = f64::max(lat_min, 0_f64);
for depth in from_depth..to_depth {
let csts = get_or_create(depth);
vec.push(f64::max(
largest_c2v_dist_in_eqr_top(val_max, csts),
largest_c2v_dist_in_eqr_bottom(val_min, csts),
));
}
}
vec.into_boxed_slice()
}
#[inline]
fn largest_c2v_dist_in_npc(lon: f64, csts: &ConstantsC2V) -> f64 {
let lon = (FRAC_PI_4 - (lon % FRAC_PI_2)).abs();
debug_assert!((0_f64..=FRAC_PI_4).contains(&lon));
linear_approx(lon, csts.slope_npc, csts.intercept_npc)
}
#[inline]
fn largest_c2v_dist_in_npc_with_radius(lon: f64, radius: f64, csts: &ConstantsC2V) -> f64 {
debug_assert!(0_f64 < radius);
let mut lon = (FRAC_PI_4 - (lon % FRAC_PI_2)).abs();
debug_assert!((0_f64..=FRAC_PI_4).contains(&lon));
lon = f64::min(lon + radius, FRAC_PI_4);
linear_approx(lon, csts.slope_npc, csts.intercept_npc)
}
#[inline]
fn largest_c2v_dist_in_eqr_top(lat_abs: f64, csts: &ConstantsC2V) -> f64 {
linear_approx(lat_abs, csts.slope_eqr, csts.intercept_eqr)
}
#[inline]
fn largest_c2v_dist_in_eqr_top_with_radius(lat_abs: f64, radius: f64, csts: &ConstantsC2V) -> f64 {
debug_assert!(0_f64 < radius);
largest_c2v_dist_in_eqr_top(f64::min(lat_abs + radius, TRANSITION_LATITUDE), csts)
}
#[inline]
fn largest_c2v_dist_in_eqr_bottom(lat_abs: f64, csts: &ConstantsC2V) -> f64 {
debug_assert!((0.0..=LAT_OF_SQUARE_CELL).contains(&lat_abs));
csts.coeff_x2_eqr * pow2(lat_abs) + csts.coeff_cst_eqr
}
#[inline]
fn largest_c2v_dist_in_eqr_bottom_with_radius(
lat_abs: f64,
radius: f64,
csts: &ConstantsC2V,
) -> f64 {
debug_assert!(0_f64 < radius);
debug_assert!((0.0..=LAT_OF_SQUARE_CELL).contains(&lat_abs));
largest_c2v_dist_in_eqr_bottom(f64::max(lat_abs - radius, 0_f64), csts)
}
#[inline]
fn linear_approx(x: f64, slope: f64, intercept: f64) -> f64 {
slope * x + intercept
}
#[inline]
fn to_shs_min_max_array(cone_radius: f64, distances: &[f64]) -> Box<[MinMax]> {
let v: Vec<MinMax> = distances
.iter()
.map(|d| to_shs_min_max(cone_radius, *d))
.collect();
v.into_boxed_slice()
}
#[inline]
fn to_shs_min_max(cone_radius: f64, distance: f64) -> MinMax {
MinMax {
min: if cone_radius < distance {
0.0
} else {
to_squared_half_segment(cone_radius - distance)
},
max: to_squared_half_segment(cone_radius + distance),
}
}
struct MinMax {
min: f64,
max: f64,
}
#[inline]
fn shs_lower_than(shs_max: f64) -> impl FnMut(&(u64, f64)) -> bool {
move |&(_, shs)| shs <= shs_max
}
#[inline]
fn shs_computer(cone_lon: f64, cone_lat: f64, cos_cone_lat: f64) -> impl Fn(LonLat) -> f64 {
move |LonLat { lon, lat }| {
squared_half_segment(lon - cone_lon, lat - cone_lat, lat.cos(), cos_cone_lat)
}
}
#[inline]
fn h_to_h_and_shs(
cone_lon: f64,
cone_lat: f64,
cos_cone_lat: f64,
layer: &'static super::Layer,
) -> impl FnMut(&u64) -> (u64, f64) {
move |&hash| {
let LonLat { lon, lat } = layer.center(hash);
(
hash,
squared_half_segment(lon - cone_lon, lat - cone_lat, lat.cos(), cos_cone_lat),
)
}
}
fn is_in_list(depth: u8, hash: u64, depth_hashs: u8, sorted_hashs: &[u64]) -> bool {
let twice_delta_depth = (depth_hashs - depth) << 1;
let hash_at_depth_max = hash << twice_delta_depth;
match sorted_hashs.binary_search(&hash_at_depth_max) {
Ok(_) => true,
Err(i) => {
(i < sorted_hashs.len() && (sorted_hashs[i] >> twice_delta_depth) == hash)
|| (i > 0_usize && (sorted_hashs[i - 1] >> twice_delta_depth) == hash)
}
}
}
fn n_vertices_in_poly(depth: u8, hash: u64, poly: &Polygon) -> (u8, [Coo3D; 4]) {
let vertices = super::get(depth).vertices(hash).map(Coo3D::from_sph_coo);
let n_vertices_in_poly = (poly.contains(&vertices[0]) as u8)
+ (poly.contains(&vertices[1]) as u8)
+ (poly.contains(&vertices[2]) as u8)
+ (poly.contains(&vertices[3]) as u8);
(n_vertices_in_poly, vertices)
}
fn has_intersection(poly: &Polygon, vertices: [Coo3D; 4]) -> bool {
poly.is_intersecting_great_circle_arc(&vertices[2], &vertices[1]) || poly.is_intersecting_great_circle_arc(&vertices[0], &vertices[1]) || poly.is_intersecting_great_circle_arc(&vertices[3], &vertices[2]) || poly.is_intersecting_great_circle_arc(&vertices[3], &vertices[0]) }