pub mod bmoc;
pub mod checked;
pub mod coords;
pub mod coverage;
pub mod dir;
pub mod geo;
mod hash;
pub mod neighbor;
pub mod proj;
mod special_points_finder;
pub mod sph_geom;
pub mod unchecked;
pub mod zoc;
#[cfg(test)]
mod tests;
use std::f64::consts::{FRAC_PI_2, FRAC_PI_4};
const SQRT_6: f64 = 2.449489743;
pub use coords::LonLat;
use coords::LonLatT;
use zoc::{ZOC, ZOrderCurve};
use dir::map::CardinalMap;
use dir::set::CardinalSet;
use dir::{Cardinal, Direction};
pub const MAX_DEPTH: u8 = 29;
pub const NSIDE_MAX: u32 = 536870912;
pub const TRANSITION_LATITUDE: f64 = 0.729_727_656_226_966_3_f64; pub const TRANSITION_Z: f64 = 2_f64 / 3_f64;
pub const ONE_OVER_TRANSITION_Z: f64 = 1.5_f64;
const F64_SIGN_BIT_MASK: u64 = 0x8000000000000000;
const F64_BUT_SIGN_BIT_MASK: u64 = 0x7FFFFFFFFFFFFFFF;
#[inline(always)]
pub const fn is_valid_depth(depth: u8) -> bool {
depth <= MAX_DEPTH
}
#[inline(always)]
pub const fn assert_valid_depth(depth: u8) {
debug_assert!(is_valid_depth(depth), "Invalid HEALPix depth");
}
const ONE_OVER_NSIDE: [f64; 30] = [
1.0,
0.5,
0.25,
0.125,
0.0625,
0.03125,
0.015625,
0.0078125,
0.00390625,
0.001953125,
0.0009765625,
0.00048828125,
0.000244140625,
0.0001220703125,
0.00006103515625,
0.000030517578125,
0.0000152587890625,
0.00000762939453125,
0.000003814697265625,
0.0000019073486328125,
0.00000095367431640625,
0.000000476837158203125,
0.0000002384185791015625,
0.00000011920928955078125,
0.00000005960464477539063,
0.000000029802322387695313,
0.000000014901161193847656,
0.000000007450580596923828,
0.000000003725290298461914,
0.000000001862645149230957,
];
const LAYERS: [Layer; 30] = [
Layer::new(0, ZOC::EMPTY),
Layer::new(1, ZOC::SMALL),
Layer::new(2, ZOC::SMALL),
Layer::new(3, ZOC::SMALL),
Layer::new(4, ZOC::SMALL),
Layer::new(5, ZOC::SMALL),
Layer::new(6, ZOC::SMALL),
Layer::new(7, ZOC::SMALL),
Layer::new(8, ZOC::SMALL),
Layer::new(9, ZOC::MEDIUM),
Layer::new(10, ZOC::MEDIUM),
Layer::new(11, ZOC::MEDIUM),
Layer::new(12, ZOC::MEDIUM),
Layer::new(13, ZOC::MEDIUM),
Layer::new(14, ZOC::MEDIUM),
Layer::new(15, ZOC::MEDIUM),
Layer::new(16, ZOC::MEDIUM),
Layer::new(17, ZOC::LARGE),
Layer::new(18, ZOC::LARGE),
Layer::new(19, ZOC::LARGE),
Layer::new(20, ZOC::LARGE),
Layer::new(21, ZOC::LARGE),
Layer::new(22, ZOC::LARGE),
Layer::new(23, ZOC::LARGE),
Layer::new(24, ZOC::LARGE),
Layer::new(25, ZOC::LARGE),
Layer::new(26, ZOC::LARGE),
Layer::new(27, ZOC::LARGE),
Layer::new(28, ZOC::LARGE),
Layer::new(29, ZOC::LARGE),
];
pub fn get(depth: u8) -> &'static Layer {
&LAYERS[depth as usize]
}
pub const fn to_range(hash: u64, delta_depth: u8) -> std::ops::Range<u64> {
let twice_delta_depth = delta_depth << 1;
(hash << twice_delta_depth)..((hash + 1) << twice_delta_depth)
}
#[inline]
pub const fn x_mask(depth: u8) -> u64 {
0x0555555555555555_u64 >> (60 - (depth << 1))
}
#[inline]
pub const fn y_mask(depth: u8) -> u64 {
0x0AAAAAAAAAAAAAAA_u64 >> (60 - (depth << 1))
}
#[inline]
pub const fn xy_mask(depth: u8) -> u64 {
(1_u64 << (depth << 1)) - 1_u64
}
#[inline(always)]
const fn check_lat(lat: f64) {
debug_assert!(
lat >= -FRAC_PI_2 && lat <= FRAC_PI_2,
"Latitude needs to be on [FRAC_PI_2, FRAC_PI_2]"
);
}
pub struct Layer {
depth: u8,
nside: u32,
nside_minus_1: u32,
n_hash: u64,
twice_depth: u8,
d0h_mask: u64,
x_mask: u64,
y_mask: u64,
xy_mask: u64,
time_half_nside: i64,
one_over_nside: f64,
z_order_curve: ZOC,
}
impl Layer {
const fn new(depth: u8, z_order_curve: ZOC) -> Layer {
let twice_depth: u8 = depth << 1u8;
let nside: u32 = unchecked::nside(depth);
Layer {
depth,
nside,
nside_minus_1: nside - 1,
n_hash: unchecked::n_hash(depth),
twice_depth,
d0h_mask: 15_u64 << twice_depth,
x_mask: x_mask(depth),
y_mask: y_mask(depth), xy_mask: xy_mask(depth),
time_half_nside: (depth as i64 - 1) << 52,
one_over_nside: ONE_OVER_NSIDE[depth as usize], z_order_curve,
}
}
pub const fn get(depth: u8) -> &'static Layer {
&LAYERS[depth as usize]
}
#[inline]
pub fn depth(&self) -> u8 {
self.depth
}
#[inline]
pub fn n_hash(&self) -> u64 {
self.n_hash
}
#[inline]
fn xpm1_and_q(lon: f64) -> (f64, u8) {
let lon_bits = lon.to_bits();
let lon_abs = f64::from_bits(lon_bits & F64_BUT_SIGN_BIT_MASK);
let lon_sign = lon_bits & F64_SIGN_BIT_MASK;
let x = lon_abs / FRAC_PI_4;
let q = x as u8 | 1_u8;
if lon_sign == 0 {
(x - (q as f64), (q & 7_u8) >> 1)
} else {
(q as f64 - x, 3 - ((q & 7_u8) >> 1))
}
}
#[inline]
pub fn vertex(&self, hash: u64, vertex_direction: Cardinal) -> LonLat {
let (x, y) = self.center_of_projected_cell(hash);
self.vertex_lonlat(x, y, vertex_direction)
}
#[inline]
pub fn vertices(&self, hash: u64) -> [LonLat; 4] {
let (x, y) = self.center_of_projected_cell(hash);
let t = self.one_over_nside;
[
proj::unproj(x, y - t), proj::unproj(x + t, y), proj::unproj(x, y + t), proj::unproj((x - t).rem_euclid(8.0), y), ]
}
#[inline]
pub fn projected_vertices(&self, hash: u64) -> [(f64, f64); 4] {
let (x, y) = self.center_of_projected_cell(hash);
let t = self.one_over_nside;
[
(x, y - t), (x + t, y), (x, y + t), (x - t, y), ]
}
#[inline]
pub fn vertices_map(&self, hash: u64, directions: CardinalSet) -> CardinalMap<LonLat> {
let (x, y) = self.center_of_projected_cell(hash);
let mut result_map = CardinalMap::new();
for direction in directions {
let vertex = self.vertex_lonlat(x, y, direction);
result_map.insert(direction, vertex);
}
result_map
}
pub fn path_along_cell_side(
&self,
hash: u64,
from_vertex: Cardinal,
to_vertex: Cardinal,
include_to_vertex: bool,
n_segments: u32,
) -> Box<[LonLat]> {
let n_points: usize = if include_to_vertex {
n_segments + 1
} else {
n_segments
} as usize;
let mut path_points = Vec::with_capacity(n_points);
let proj_center = self.center_of_projected_cell(hash);
self.path_along_cell_side_internal(
proj_center,
from_vertex,
to_vertex,
include_to_vertex,
n_segments,
&mut path_points,
);
path_points.into_boxed_slice()
}
fn path_along_cell_side_internal(
&self,
proj_center: (f64, f64),
from_vertex: Cardinal,
to_vertex: Cardinal,
include_to_vertex: bool,
n_segments: u32,
path_points: &mut Vec<LonLat>,
) {
let n_points: usize = if include_to_vertex {
n_segments + 1
} else {
n_segments
} as usize;
let from_offset_x = from_vertex.x() * self.one_over_nside;
let from_offset_y = from_vertex.y() * self.one_over_nside;
let step_x = (to_vertex.x() * self.one_over_nside - from_offset_x) / (n_segments as f64);
let step_y = (to_vertex.y() * self.one_over_nside - from_offset_y) / (n_segments as f64);
for i in 0..n_points {
let k = i as f64;
let x = proj_center.0 + from_offset_x + k * step_x;
let y = proj_center.1 + from_offset_y + k * step_y;
path_points.push(proj::unproj(x.rem_euclid(8.0), y));
}
}
pub fn path_along_cell_edge(
&self,
hash: u64,
starting_vertex: Cardinal,
clockwise_direction: bool,
n_segments_by_side: u32,
) -> Box<[LonLat]> {
let mut path_points = Vec::with_capacity((n_segments_by_side << 2) as usize);
let proj_center = self.center_of_projected_cell(hash);
let [v1, v2, v3, v4] = {
let mut buf = Cardinal::VALUES;
buf.rotate_left(starting_vertex.index() as _);
if !clockwise_direction {
buf.reverse();
}
buf
};
self.path_along_cell_side_internal(
proj_center,
v1,
v2,
false,
n_segments_by_side,
&mut path_points,
);
self.path_along_cell_side_internal(
proj_center,
v2,
v3,
false,
n_segments_by_side,
&mut path_points,
);
self.path_along_cell_side_internal(
proj_center,
v3,
v4,
false,
n_segments_by_side,
&mut path_points,
);
self.path_along_cell_side_internal(
proj_center,
v4,
v1,
false,
n_segments_by_side,
&mut path_points,
);
path_points.into_boxed_slice()
}
pub fn grid(&self, hash: u64, n_segments_by_side: u16) -> Box<[LonLat]> {
let n_points_per_side = (n_segments_by_side as usize) + 1;
let mut grid = Vec::with_capacity(n_points_per_side * n_points_per_side);
let proj_center = self.center_of_projected_cell(hash);
for i in 0..n_points_per_side {
let x = (i as f64) / (n_segments_by_side as f64); for j in 0..n_points_per_side {
let y = (j as f64) / (n_segments_by_side as f64); let l = x - y;
let h = x + y - 1.0;
grid.push(proj::unproj(
proj_center.0 + l * self.one_over_nside,
proj_center.1 + h * self.one_over_nside,
));
}
}
grid.into_boxed_slice()
}
pub fn center_of_projected_cell(&self, hash: u64) -> (f64, f64) {
self.check_hash(hash);
let h_parts: HashParts = self.decode_hash(hash);
let mut hl: (i32, i32) = (
h_parts.i as i32 - h_parts.j as i32,
(h_parts.i + h_parts.j) as i32,
);
self.shift_from_small_cell_center_to_base_cell_center(&mut hl);
let (mut x, mut y) = self.scale_to_proj_dividing_by_nside(hl);
let offset_y = 1 - (h_parts.d0h >> 2) as i8;
let mut offset_x = (h_parts.d0h & 0b11) << 1u8;
offset_x |= (offset_y & 1_i8) as u8;
x += offset_x as f64;
y += offset_y as f64;
x += ((x.to_bits() & F64_SIGN_BIT_MASK) >> 60) as f64;
(x, y)
}
#[inline]
fn vertex_lonlat(&self, center_x: f64, center_y: f64, vertex_direction: Cardinal) -> LonLat {
let x = center_x + vertex_direction.x() * self.one_over_nside;
let y = center_y + vertex_direction.y() * self.one_over_nside;
proj::unproj(x.rem_euclid(8.0), y)
}
#[inline]
fn is_hash(&self, hash: u64) -> bool {
hash < self.n_hash
}
#[inline]
fn check_hash(&self, hash: u64) {
assert!(self.is_hash(hash), "Wrong hash value: too large.");
}
#[inline]
fn h_2_d0h(&self, hash: u64) -> u8 {
(hash >> self.twice_depth) as u8
}
#[inline]
fn decode_hash(&self, hash: u64) -> HashParts {
let ij: u64 = self.z_order_curve.h2ij(hash & self.xy_mask);
HashParts {
d0h: self.h_2_d0h(hash),
i: self.z_order_curve.ij2i(ij),
j: self.z_order_curve.ij2j(ij),
}
}
#[inline]
fn pull_bits_appart(&self, hash: u64) -> HashBits {
HashBits {
d0h: hash & self.d0h_mask,
i: hash & self.x_mask,
j: hash & self.y_mask,
}
}
#[inline]
fn shift_from_small_cell_center_to_base_cell_center(&self, ij: &mut (i32, i32)) {
let (ref mut _i, ref mut j) = *ij;
*j -= self.nside_minus_1 as i32;
}
#[inline]
fn scale_to_proj_dividing_by_nside(&self, (x, y): (i32, i32)) -> (f64, f64) {
(
x as f64 * self.one_over_nside,
y as f64 * self.one_over_nside,
)
}
pub fn bilinear_interpolation(&self, coords: impl LonLatT) -> [(u64, f64); 4] {
self.bilinear_interpolation_impl(coords.lon(), coords.lat())
}
fn bilinear_interpolation_impl(&self, lon: f64, lat: f64) -> [(u64, f64); 4] {
let (h, dx, dy) = self.hash_with_dxdy([lon, lat]);
let neigbours_map = self.neighbors(h);
let xcoo = (dx > 0.5) as u8;
let ycoo = (dy > 0.5) as u8;
let quarter: u8 = (ycoo << 1) + xcoo;
match quarter {
0 => {
match neigbours_map.get(Direction::S) {
Some(nh) => [
(*nh, (0.5 - dx) * (0.5 - dy)),
(
*neigbours_map.get(Direction::SE).unwrap(),
(0.5 + dx) * (0.5 - dy),
),
(
*neigbours_map.get(Direction::SW).unwrap(),
(0.5 - dx) * (0.5 + dy),
),
(h, (0.5 + dx) * (0.5 + dy)),
],
None => [
(h, 0.0),
(
*neigbours_map.get(Direction::SE).unwrap(),
(0.5 - dy) * (0.75 + 0.5 * dx),
),
(
*neigbours_map.get(Direction::SW).unwrap(),
(0.5 - dx) * (0.75 + 0.5 * dy),
),
(h, (0.5 + dx) * (0.5 + dy)),
],
}
}
1 =>
{
match neigbours_map.get(Direction::E) {
Some(nh) => [
(
*neigbours_map.get(Direction::SE).unwrap(),
(1.5 - dx) * (0.5 - dy),
),
(*nh, (dx - 0.5) * (0.5 - dy)),
(h, (1.5 - dx) * (0.5 + dy)),
(
*neigbours_map.get(Direction::NE).unwrap(),
(dx - 0.5) * (0.5 + dy),
),
],
None => [
(
*neigbours_map.get(Direction::SE).unwrap(),
(0.5 - dy) * (1.25 - 0.5 * dx),
),
(h, 0.0),
(h, (1.5 - dx) * (0.5 + dy)),
(
*neigbours_map.get(Direction::NE).unwrap(),
(dx - 0.5) * (0.75 + 0.5 * dy),
),
],
}
}
2 =>
{
match neigbours_map.get(Direction::W) {
Some(nh) => [
(
*neigbours_map.get(Direction::SW).unwrap(),
(0.5 - dx) * (1.5 - dy),
),
(h, (dx + 0.5) * (1.5 - dy)),
(*nh, (0.5 - dx) * (dy - 0.5)),
(
*neigbours_map.get(Direction::NW).unwrap(),
(0.5 + dx) * (dy - 0.5),
),
],
None => [
(
*neigbours_map.get(Direction::SW).unwrap(),
(0.5 - dx) * (1.25 - 0.5 * dy),
),
(h, (dx + 0.5) * (1.5 - dy)),
(h, 0.0),
(
*neigbours_map.get(Direction::NW).unwrap(),
(dy - 0.5) * (0.5 * dx + 0.75),
),
],
}
}
3 =>
{
match neigbours_map.get(Direction::N) {
Some(nh) => [
(h, (1.5 - dx) * (1.5 - dy)),
(
*neigbours_map.get(Direction::NE).unwrap(),
(dx - 0.5) * (1.5 - dy),
),
(
*neigbours_map.get(Direction::NW).unwrap(),
(1.5 - dx) * (dy - 0.5),
),
(*nh, (dx - 0.5) * (dy - 0.5)),
],
None => [
(h, (1.5 - dx) * (1.5 - dy)),
(
*neigbours_map.get(Direction::NE).unwrap(),
(dx - 0.5) * (1.25 - 0.5 * dy),
),
(
*neigbours_map.get(Direction::NW).unwrap(),
(1.25 - 0.5 * dx) * (dy - 0.5),
),
(h, 0.0),
],
}
}
_ => unreachable!(),
}
}
}
struct HashParts {
d0h: u8, i: u32, j: u32, }
struct HashBits {
d0h: u64, i: u64, j: u64, }
pub fn from_uniq(uniq_hash: u64) -> (u8, u64) {
let depth = (60 - uniq_hash.leading_zeros()) >> 1;
let hash = uniq_hash & !(16_u64 << (depth << 1));
(depth as u8, hash)
}
pub fn from_uniq_ivoa(uniq_hash: u64) -> (u8, u64) {
let depth = (61 - uniq_hash.leading_zeros()) >> 1;
let hash = uniq_hash - (4_u64 << (depth << 1));
(depth as u8, hash)
}