#![no_std]
#![warn(missing_docs)]
#![deny(unsafe_code)]
pub mod bbox3d;
pub mod geohash;
pub mod linestring;
pub mod mercator;
pub mod ring;
pub use bbox3d::BBox3D;
pub use geohash::GeoHashFixed;
pub use linestring::FixedLineString;
pub use mercator::{mercator_forward, mercator_inverse};
pub use ring::FixedRing;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NoAllocError {
CapacityExceeded,
InvalidPrecision,
EmptyGeometry,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point2D {
pub x: f64,
pub y: f64,
}
impl Point2D {
#[must_use]
#[inline]
pub const fn new(x: f64, y: f64) -> Self {
Self { x, y }
}
#[must_use]
#[inline]
pub fn distance_to(&self, other: &Point2D) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
libm_sqrt(dx * dx + dy * dy)
}
#[must_use]
#[inline]
pub fn midpoint(&self, other: &Point2D) -> Point2D {
Point2D::new((self.x + other.x) * 0.5, (self.y + other.y) * 0.5)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Point3D {
pub x: f64,
pub y: f64,
pub z: f64,
}
impl Point3D {
#[must_use]
#[inline]
pub const fn new(x: f64, y: f64, z: f64) -> Self {
Self { x, y, z }
}
#[must_use]
#[inline]
pub fn distance_to(&self, other: &Point3D) -> f64 {
let dx = self.x - other.x;
let dy = self.y - other.y;
let dz = self.z - other.z;
libm_sqrt(dx * dx + dy * dy + dz * dz)
}
#[must_use]
#[inline]
pub const fn to_2d(&self) -> Point2D {
Point2D::new(self.x, self.y)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BBox2D {
pub min_x: f64,
pub min_y: f64,
pub max_x: f64,
pub max_y: f64,
}
impl BBox2D {
#[must_use]
#[inline]
pub const fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
Self {
min_x,
min_y,
max_x,
max_y,
}
}
#[must_use]
#[inline]
pub fn is_valid(&self) -> bool {
self.min_x <= self.max_x && self.min_y <= self.max_y
}
#[must_use]
#[inline]
pub fn contains_point(&self, p: Point2D) -> bool {
p.x >= self.min_x && p.x <= self.max_x && p.y >= self.min_y && p.y <= self.max_y
}
#[must_use]
#[inline]
pub fn intersects(&self, other: &BBox2D) -> bool {
self.min_x <= other.max_x
&& self.max_x >= other.min_x
&& self.min_y <= other.max_y
&& self.max_y >= other.min_y
}
#[must_use]
#[inline]
pub fn union(&self, other: &BBox2D) -> BBox2D {
BBox2D::new(
f64_min(self.min_x, other.min_x),
f64_min(self.min_y, other.min_y),
f64_max(self.max_x, other.max_x),
f64_max(self.max_y, other.max_y),
)
}
#[must_use]
#[inline]
pub fn area(&self) -> f64 {
let w = self.max_x - self.min_x;
let h = self.max_y - self.min_y;
if w < 0.0 || h < 0.0 { 0.0 } else { w * h }
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct LineSegment2D {
pub start: Point2D,
pub end: Point2D,
}
impl LineSegment2D {
#[must_use]
#[inline]
pub const fn new(start: Point2D, end: Point2D) -> Self {
Self { start, end }
}
#[must_use]
#[inline]
pub fn length(&self) -> f64 {
self.start.distance_to(&self.end)
}
#[must_use]
#[inline]
pub fn midpoint(&self) -> Point2D {
self.start.midpoint(&self.end)
}
#[must_use]
#[inline]
pub fn point_on_segment(&self, t: f64) -> Point2D {
Point2D::new(
self.start.x + t * (self.end.x - self.start.x),
self.start.y + t * (self.end.y - self.start.y),
)
}
#[must_use]
pub fn intersects(&self, other: &LineSegment2D) -> Option<Point2D> {
let dx1 = self.end.x - self.start.x;
let dy1 = self.end.y - self.start.y;
let dx2 = other.end.x - other.start.x;
let dy2 = other.end.y - other.start.y;
let denom = dx1 * dy2 - dy1 * dx2;
if denom.abs() < f64::EPSILON * 1e6 {
return None; }
let ox = other.start.x - self.start.x;
let oy = other.start.y - self.start.y;
let t = (ox * dy2 - oy * dx2) / denom;
let u = (ox * dy1 - oy * dx1) / denom;
if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
Some(self.point_on_segment(t))
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Triangle2D {
pub a: Point2D,
pub b: Point2D,
pub c: Point2D,
}
impl Triangle2D {
#[must_use]
#[inline]
pub const fn new(a: Point2D, b: Point2D, c: Point2D) -> Self {
Self { a, b, c }
}
#[must_use]
#[inline]
pub fn signed_area(&self) -> f64 {
0.5 * ((self.b.x - self.a.x) * (self.c.y - self.a.y)
- (self.c.x - self.a.x) * (self.b.y - self.a.y))
}
#[must_use]
#[inline]
pub fn area(&self) -> f64 {
self.signed_area().abs()
}
#[must_use]
#[inline]
pub fn is_clockwise(&self) -> bool {
self.signed_area() < 0.0
}
#[must_use]
#[inline]
pub fn centroid(&self) -> Point2D {
Point2D::new(
(self.a.x + self.b.x + self.c.x) / 3.0,
(self.a.y + self.b.y + self.c.y) / 3.0,
)
}
#[must_use]
pub fn perimeter(&self) -> f64 {
self.a.distance_to(&self.b) + self.b.distance_to(&self.c) + self.c.distance_to(&self.a)
}
#[must_use]
pub fn contains_point(&self, p: Point2D) -> bool {
let s_abc = self.signed_area();
if s_abc.abs() < f64::EPSILON {
return false; }
let s_pbc = Triangle2D::new(p, self.b, self.c).signed_area();
let s_apc = Triangle2D::new(self.a, p, self.c).signed_area();
let s_abp = Triangle2D::new(self.a, self.b, p).signed_area();
let same_sign = |a: f64, b: f64| (a >= 0.0) == (b >= 0.0);
same_sign(s_abc, s_pbc) && same_sign(s_abc, s_apc) && same_sign(s_abc, s_abp)
}
}
pub struct FixedPolygon<const N: usize> {
vertices: [Point2D; N],
len: usize,
}
impl<const N: usize> FixedPolygon<N> {
#[must_use]
#[inline]
pub fn new() -> Self {
Self {
vertices: [Point2D::new(0.0, 0.0); N],
len: 0,
}
}
#[inline]
pub fn try_push(&mut self, p: Point2D) -> bool {
if self.len >= N {
return false;
}
self.vertices[self.len] = p;
self.len += 1;
true
}
#[must_use]
#[inline]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
#[inline]
pub fn vertices(&self) -> &[Point2D] {
&self.vertices[..self.len]
}
#[must_use]
pub fn signed_area(&self) -> f64 {
if self.len < 3 {
return 0.0;
}
let mut sum = 0.0_f64;
let verts = self.vertices();
for i in 0..self.len {
let j = (i + 1) % self.len;
sum += verts[i].x * verts[j].y;
sum -= verts[j].x * verts[i].y;
}
sum * 0.5
}
#[must_use]
#[inline]
pub fn area(&self) -> f64 {
self.signed_area().abs()
}
#[must_use]
pub fn perimeter(&self) -> f64 {
if self.len < 2 {
return 0.0;
}
let verts = self.vertices();
let mut total = 0.0_f64;
for i in 0..self.len {
let j = (i + 1) % self.len;
total += verts[i].distance_to(&verts[j]);
}
total
}
#[must_use]
pub fn bbox(&self) -> Option<BBox2D> {
if self.is_empty() {
return None;
}
let verts = self.vertices();
let mut min_x = verts[0].x;
let mut min_y = verts[0].y;
let mut max_x = verts[0].x;
let mut max_y = verts[0].y;
for v in verts.iter().skip(1) {
min_x = f64_min(min_x, v.x);
min_y = f64_min(min_y, v.y);
max_x = f64_max(max_x, v.x);
max_y = f64_max(max_y, v.y);
}
Some(BBox2D::new(min_x, min_y, max_x, max_y))
}
#[must_use]
pub fn centroid(&self) -> Option<Point2D> {
if self.is_empty() {
return None;
}
let verts = self.vertices();
let mut cx = 0.0_f64;
let mut cy = 0.0_f64;
for v in verts {
cx += v.x;
cy += v.y;
}
let n = self.len as f64;
Some(Point2D::new(cx / n, cy / n))
}
}
impl<const N: usize> Default for FixedPolygon<N> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CoordTransform {
matrix: [f64; 6],
}
impl CoordTransform {
#[must_use]
#[inline]
pub const fn identity() -> Self {
Self {
matrix: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
}
}
#[must_use]
#[inline]
pub const fn scale(sx: f64, sy: f64) -> Self {
Self {
matrix: [sx, 0.0, 0.0, 0.0, sy, 0.0],
}
}
#[must_use]
#[inline]
pub const fn translate(tx: f64, ty: f64) -> Self {
Self {
matrix: [1.0, 0.0, tx, 0.0, 1.0, ty],
}
}
#[must_use]
pub fn rotate(angle_radians: f64) -> Self {
let cos_a = libm_cos(angle_radians);
let sin_a = libm_sin(angle_radians);
Self {
matrix: [cos_a, -sin_a, 0.0, sin_a, cos_a, 0.0],
}
}
#[must_use]
pub fn compose(self, other: &CoordTransform) -> Self {
let [a1, b1, c1, d1, e1, f1] = self.matrix;
let [a2, b2, c2, d2, e2, f2] = other.matrix;
Self {
matrix: [
a2 * a1 + b2 * d1,
a2 * b1 + b2 * e1,
a2 * c1 + b2 * f1 + c2,
d2 * a1 + e2 * d1,
d2 * b1 + e2 * e1,
d2 * c1 + e2 * f1 + f2,
],
}
}
#[must_use]
#[inline]
pub fn apply(&self, p: Point2D) -> Point2D {
let [a, b, c, d, e, f] = self.matrix;
Point2D::new(a * p.x + b * p.y + c, d * p.x + e * p.y + f)
}
#[must_use]
#[inline]
pub fn apply3d(&self, p: Point3D) -> Point3D {
let p2 = self.apply(p.to_2d());
Point3D::new(p2.x, p2.y, p.z)
}
}
#[inline(always)]
pub(crate) fn libm_sqrt(x: f64) -> f64 {
if x <= 0.0 {
return if x == 0.0 { 0.0 } else { f64::NAN };
}
let bits = x.to_bits();
let biased_exp = (bits >> 52) & 0x7FF;
let new_biased_exp = (biased_exp + 1023) >> 1;
let r_bits = new_biased_exp << 52;
let mut r = f64::from_bits(r_bits);
r = (r + x / r) * 0.5;
r = (r + x / r) * 0.5;
r = (r + x / r) * 0.5;
r = (r + x / r) * 0.5;
r = (r + x / r) * 0.5;
r = (r + x / r) * 0.5;
r
}
#[inline(always)]
pub(crate) fn libm_sin(x: f64) -> f64 {
let (s, c) = sin_cos_core(x);
let _ = c;
s
}
#[inline(always)]
pub(crate) fn libm_cos(x: f64) -> f64 {
let (s, c) = sin_cos_core(x);
let _ = s;
c
}
#[rustfmt::skip]
#[inline(always)]
pub(crate) fn sin_cos_core(x: f64) -> (f64, f64) {
let pi = core::f64::consts::PI;
let two_pi = 2.0 * pi;
let half_pi = pi * 0.5;
let quarter_pi = pi * 0.25;
let mut x = x % two_pi;
if x > pi {
x -= two_pi;
}
if x < -pi {
x += two_pi;
}
let ratio = x / half_pi;
let k_f = if ratio >= 0.0 {
(ratio + 0.5) as i64 as f64
} else {
(ratio - 0.5) as i64 as f64
};
let k = k_f as i64;
let r = x - k_f * half_pi;
let _ = quarter_pi;
let r2 = r * r;
let sin_r = r
* (1.0
+ r2 * (-1.0 / 6.0
+ r2 * (1.0 / 120.0
+ r2 * (-1.0 / 5040.0
+ r2 * (1.0 / 362_880.0
+ r2 * (-1.0 / 39_916_800.0 + r2 * (1.0 / 6_227_020_800.0)))))));
let cos_r = 1.0
+ r2 * (-1.0 / 2.0
+ r2 * (1.0 / 24.0
+ r2 * (-1.0 / 720.0
+ r2 * (1.0 / 40_320.0
+ r2 * (-1.0 / 3_628_800.0 + r2 * (1.0 / 479_001_600.0))))));
let kmod = ((k % 4) + 4) as u64 % 4;
match kmod {
0 => (sin_r, cos_r),
1 => (cos_r, -sin_r),
2 => (-sin_r, -cos_r),
_ => (-cos_r, sin_r),
}
}
#[inline(always)]
pub(crate) fn f64_min(a: f64, b: f64) -> f64 {
if a < b { a } else { b }
}
#[inline(always)]
pub(crate) fn f64_max(a: f64, b: f64) -> f64 {
if a > b { a } else { b }
}
#[inline(always)]
pub(crate) fn f64_abs(x: f64) -> f64 {
if x < 0.0 { -x } else { x }
}
#[inline(always)]
pub(crate) fn f64_floor(x: f64) -> f64 {
let t = x as i64 as f64;
if t > x { t - 1.0 } else { t }
}
#[rustfmt::skip]
#[inline(always)]
pub(crate) fn libm_ln(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN; }
if x < 0.0 {
return f64::NAN;
}
if x == 0.0 {
return f64::NEG_INFINITY;
}
if x == f64::INFINITY {
return f64::INFINITY;
}
let bits = x.to_bits();
let biased_exp = ((bits >> 52) & 0x7FF) as i64;
let mantissa_bits = bits & 0x000F_FFFF_FFFF_FFFF;
let m = f64::from_bits((1023_u64 << 52) | mantissa_bits);
let e = biased_exp - 1023;
let t = (m - 1.0) / (m + 1.0);
let t2 = t * t;
let ln_m = 2.0
* t
* (1.0
+ t2 * (1.0 / 3.0
+ t2 * (1.0 / 5.0
+ t2 * (1.0 / 7.0
+ t2 * (1.0 / 9.0
+ t2 * (1.0 / 11.0
+ t2 * (1.0 / 13.0
+ t2 * (1.0 / 15.0
+ t2 * (1.0 / 17.0
+ t2 * (1.0 / 19.0
+ t2 * (1.0 / 21.0 + t2 * (1.0 / 23.0))))))))))));
e as f64 * core::f64::consts::LN_2 + ln_m
}
#[rustfmt::skip]
#[inline(always)]
pub(crate) fn libm_exp(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
if x == f64::INFINITY {
return f64::INFINITY;
}
if x == f64::NEG_INFINITY {
return 0.0;
}
let n_f = f64_floor(x * core::f64::consts::LOG2_E + 0.5);
let n = n_f as i64;
let r = x - n_f * core::f64::consts::LN_2;
let r2 = r * r;
let exp_r = 1.0
+ r * (1.0
+ r * (1.0 / 2.0
+ r * (1.0 / 6.0
+ r * (1.0 / 24.0
+ r * (1.0 / 120.0
+ r * (1.0 / 720.0
+ r * (1.0 / 5_040.0
+ r * (1.0 / 40_320.0
+ r * (1.0 / 362_880.0
+ r * (1.0 / 3_628_800.0
+ r * (1.0 / 39_916_800.0
+ r * (1.0 / 479_001_600.0))))))))))));
let _ = r2;
if n > 1023 {
return f64::INFINITY;
}
if n < -1074 {
return 0.0;
}
if n >= -1022 {
let pow2 = f64::from_bits(((n + 1023) as u64) << 52);
exp_r * pow2
} else {
let pow2a = f64::from_bits(1_u64 << 52); let pow2b = f64::from_bits(((n + 1023 + 1022) as u64) << 52);
exp_r * pow2a * pow2b
}
}
#[rustfmt::skip]
#[inline(always)]
pub(crate) fn libm_atan(x: f64) -> f64 {
if x.is_nan() {
return f64::NAN;
}
let neg = x < 0.0;
let mut a = f64_abs(x);
let mut hi = 0.0_f64;
let reciprocal = a > 1.0;
if reciprocal {
a = 1.0 / a;
hi = core::f64::consts::FRAC_PI_2;
}
const TAN_PI_12: f64 = 0.267_949_192_431_122_7; const INV_SQRT3: f64 = 0.577_350_269_189_625_8; if a > TAN_PI_12 {
let a_new = (a - INV_SQRT3) / (1.0 + a * INV_SQRT3);
if reciprocal {
hi -= core::f64::consts::FRAC_PI_6;
} else {
hi += core::f64::consts::FRAC_PI_6;
}
a = a_new;
}
let a2 = a * a;
let result = a
* (1.0
+ a2 * (-1.0 / 3.0
+ a2 * (1.0 / 5.0
+ a2 * (-1.0 / 7.0
+ a2 * (1.0 / 9.0
+ a2 * (-1.0 / 11.0
+ a2 * (1.0 / 13.0
+ a2 * (-1.0 / 15.0
+ a2 * (1.0 / 17.0 + a2 * (-1.0 / 19.0))))))))));
let val = if reciprocal { hi - result } else { hi + result };
if neg { -val } else { val }
}