1#![no_std]
29#![warn(missing_docs)]
30#![deny(unsafe_code)]
31
32pub mod bbox3d;
33pub mod geohash;
34pub mod linestring;
35pub mod mercator;
36pub mod ring;
37
38pub use bbox3d::BBox3D;
39pub use geohash::GeoHashFixed;
40pub use linestring::FixedLineString;
41pub use mercator::{mercator_forward, mercator_inverse};
42pub use ring::FixedRing;
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
48pub enum NoAllocError {
49 CapacityExceeded,
51 InvalidPrecision,
53 EmptyGeometry,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq)]
61pub struct Point2D {
62 pub x: f64,
64 pub y: f64,
66}
67
68impl Point2D {
69 #[must_use]
71 #[inline]
72 pub const fn new(x: f64, y: f64) -> Self {
73 Self { x, y }
74 }
75
76 #[must_use]
78 #[inline]
79 pub fn distance_to(&self, other: &Point2D) -> f64 {
80 let dx = self.x - other.x;
81 let dy = self.y - other.y;
82 libm_sqrt(dx * dx + dy * dy)
83 }
84
85 #[must_use]
87 #[inline]
88 pub fn midpoint(&self, other: &Point2D) -> Point2D {
89 Point2D::new((self.x + other.x) * 0.5, (self.y + other.y) * 0.5)
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq)]
97pub struct Point3D {
98 pub x: f64,
100 pub y: f64,
102 pub z: f64,
104}
105
106impl Point3D {
107 #[must_use]
109 #[inline]
110 pub const fn new(x: f64, y: f64, z: f64) -> Self {
111 Self { x, y, z }
112 }
113
114 #[must_use]
116 #[inline]
117 pub fn distance_to(&self, other: &Point3D) -> f64 {
118 let dx = self.x - other.x;
119 let dy = self.y - other.y;
120 let dz = self.z - other.z;
121 libm_sqrt(dx * dx + dy * dy + dz * dz)
122 }
123
124 #[must_use]
126 #[inline]
127 pub const fn to_2d(&self) -> Point2D {
128 Point2D::new(self.x, self.y)
129 }
130}
131
132#[derive(Debug, Clone, Copy, PartialEq)]
136pub struct BBox2D {
137 pub min_x: f64,
139 pub min_y: f64,
141 pub max_x: f64,
143 pub max_y: f64,
145}
146
147impl BBox2D {
148 #[must_use]
150 #[inline]
151 pub const fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
152 Self {
153 min_x,
154 min_y,
155 max_x,
156 max_y,
157 }
158 }
159
160 #[must_use]
162 #[inline]
163 pub fn is_valid(&self) -> bool {
164 self.min_x <= self.max_x && self.min_y <= self.max_y
165 }
166
167 #[must_use]
169 #[inline]
170 pub fn contains_point(&self, p: Point2D) -> bool {
171 p.x >= self.min_x && p.x <= self.max_x && p.y >= self.min_y && p.y <= self.max_y
172 }
173
174 #[must_use]
176 #[inline]
177 pub fn intersects(&self, other: &BBox2D) -> bool {
178 self.min_x <= other.max_x
179 && self.max_x >= other.min_x
180 && self.min_y <= other.max_y
181 && self.max_y >= other.min_y
182 }
183
184 #[must_use]
186 #[inline]
187 pub fn union(&self, other: &BBox2D) -> BBox2D {
188 BBox2D::new(
189 f64_min(self.min_x, other.min_x),
190 f64_min(self.min_y, other.min_y),
191 f64_max(self.max_x, other.max_x),
192 f64_max(self.max_y, other.max_y),
193 )
194 }
195
196 #[must_use]
198 #[inline]
199 pub fn area(&self) -> f64 {
200 let w = self.max_x - self.min_x;
201 let h = self.max_y - self.min_y;
202 if w < 0.0 || h < 0.0 { 0.0 } else { w * h }
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq)]
210pub struct LineSegment2D {
211 pub start: Point2D,
213 pub end: Point2D,
215}
216
217impl LineSegment2D {
218 #[must_use]
220 #[inline]
221 pub const fn new(start: Point2D, end: Point2D) -> Self {
222 Self { start, end }
223 }
224
225 #[must_use]
227 #[inline]
228 pub fn length(&self) -> f64 {
229 self.start.distance_to(&self.end)
230 }
231
232 #[must_use]
234 #[inline]
235 pub fn midpoint(&self) -> Point2D {
236 self.start.midpoint(&self.end)
237 }
238
239 #[must_use]
243 #[inline]
244 pub fn point_on_segment(&self, t: f64) -> Point2D {
245 Point2D::new(
246 self.start.x + t * (self.end.x - self.start.x),
247 self.start.y + t * (self.end.y - self.start.y),
248 )
249 }
250
251 #[must_use]
255 pub fn intersects(&self, other: &LineSegment2D) -> Option<Point2D> {
256 let dx1 = self.end.x - self.start.x;
257 let dy1 = self.end.y - self.start.y;
258 let dx2 = other.end.x - other.start.x;
259 let dy2 = other.end.y - other.start.y;
260
261 let denom = dx1 * dy2 - dy1 * dx2;
262
263 if denom.abs() < f64::EPSILON * 1e6 {
264 return None; }
266
267 let ox = other.start.x - self.start.x;
268 let oy = other.start.y - self.start.y;
269
270 let t = (ox * dy2 - oy * dx2) / denom;
271 let u = (ox * dy1 - oy * dx1) / denom;
272
273 if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
274 Some(self.point_on_segment(t))
275 } else {
276 None
277 }
278 }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq)]
285pub struct Triangle2D {
286 pub a: Point2D,
288 pub b: Point2D,
290 pub c: Point2D,
292}
293
294impl Triangle2D {
295 #[must_use]
297 #[inline]
298 pub const fn new(a: Point2D, b: Point2D, c: Point2D) -> Self {
299 Self { a, b, c }
300 }
301
302 #[must_use]
306 #[inline]
307 pub fn signed_area(&self) -> f64 {
308 0.5 * ((self.b.x - self.a.x) * (self.c.y - self.a.y)
309 - (self.c.x - self.a.x) * (self.b.y - self.a.y))
310 }
311
312 #[must_use]
314 #[inline]
315 pub fn area(&self) -> f64 {
316 self.signed_area().abs()
317 }
318
319 #[must_use]
321 #[inline]
322 pub fn is_clockwise(&self) -> bool {
323 self.signed_area() < 0.0
324 }
325
326 #[must_use]
328 #[inline]
329 pub fn centroid(&self) -> Point2D {
330 Point2D::new(
331 (self.a.x + self.b.x + self.c.x) / 3.0,
332 (self.a.y + self.b.y + self.c.y) / 3.0,
333 )
334 }
335
336 #[must_use]
338 pub fn perimeter(&self) -> f64 {
339 self.a.distance_to(&self.b) + self.b.distance_to(&self.c) + self.c.distance_to(&self.a)
340 }
341
342 #[must_use]
346 pub fn contains_point(&self, p: Point2D) -> bool {
347 let s_abc = self.signed_area();
349 if s_abc.abs() < f64::EPSILON {
350 return false; }
352
353 let s_pbc = Triangle2D::new(p, self.b, self.c).signed_area();
354 let s_apc = Triangle2D::new(self.a, p, self.c).signed_area();
355 let s_abp = Triangle2D::new(self.a, self.b, p).signed_area();
356
357 let same_sign = |a: f64, b: f64| (a >= 0.0) == (b >= 0.0);
358
359 same_sign(s_abc, s_pbc) && same_sign(s_abc, s_apc) && same_sign(s_abc, s_abp)
360 }
361}
362
363pub struct FixedPolygon<const N: usize> {
369 vertices: [Point2D; N],
370 len: usize,
371}
372
373impl<const N: usize> FixedPolygon<N> {
374 #[must_use]
376 #[inline]
377 pub fn new() -> Self {
378 Self {
379 vertices: [Point2D::new(0.0, 0.0); N],
380 len: 0,
381 }
382 }
383
384 #[inline]
386 pub fn try_push(&mut self, p: Point2D) -> bool {
387 if self.len >= N {
388 return false;
389 }
390 self.vertices[self.len] = p;
391 self.len += 1;
392 true
393 }
394
395 #[must_use]
397 #[inline]
398 pub fn len(&self) -> usize {
399 self.len
400 }
401
402 #[must_use]
404 #[inline]
405 pub fn is_empty(&self) -> bool {
406 self.len == 0
407 }
408
409 #[must_use]
411 #[inline]
412 pub fn vertices(&self) -> &[Point2D] {
413 &self.vertices[..self.len]
414 }
415
416 #[must_use]
418 pub fn signed_area(&self) -> f64 {
419 if self.len < 3 {
420 return 0.0;
421 }
422 let mut sum = 0.0_f64;
423 let verts = self.vertices();
424 for i in 0..self.len {
425 let j = (i + 1) % self.len;
426 sum += verts[i].x * verts[j].y;
427 sum -= verts[j].x * verts[i].y;
428 }
429 sum * 0.5
430 }
431
432 #[must_use]
434 #[inline]
435 pub fn area(&self) -> f64 {
436 self.signed_area().abs()
437 }
438
439 #[must_use]
441 pub fn perimeter(&self) -> f64 {
442 if self.len < 2 {
443 return 0.0;
444 }
445 let verts = self.vertices();
446 let mut total = 0.0_f64;
447 for i in 0..self.len {
448 let j = (i + 1) % self.len;
449 total += verts[i].distance_to(&verts[j]);
450 }
451 total
452 }
453
454 #[must_use]
456 pub fn bbox(&self) -> Option<BBox2D> {
457 if self.is_empty() {
458 return None;
459 }
460 let verts = self.vertices();
461 let mut min_x = verts[0].x;
462 let mut min_y = verts[0].y;
463 let mut max_x = verts[0].x;
464 let mut max_y = verts[0].y;
465 for v in verts.iter().skip(1) {
466 min_x = f64_min(min_x, v.x);
467 min_y = f64_min(min_y, v.y);
468 max_x = f64_max(max_x, v.x);
469 max_y = f64_max(max_y, v.y);
470 }
471 Some(BBox2D::new(min_x, min_y, max_x, max_y))
472 }
473
474 #[must_use]
476 pub fn centroid(&self) -> Option<Point2D> {
477 if self.is_empty() {
478 return None;
479 }
480 let verts = self.vertices();
481 let mut cx = 0.0_f64;
482 let mut cy = 0.0_f64;
483 for v in verts {
484 cx += v.x;
485 cy += v.y;
486 }
487 let n = self.len as f64;
488 Some(Point2D::new(cx / n, cy / n))
489 }
490}
491
492impl<const N: usize> Default for FixedPolygon<N> {
493 fn default() -> Self {
494 Self::new()
495 }
496}
497
498#[derive(Debug, Clone, Copy, PartialEq)]
509pub struct CoordTransform {
510 matrix: [f64; 6],
511}
512
513impl CoordTransform {
514 #[must_use]
516 #[inline]
517 pub const fn identity() -> Self {
518 Self {
519 matrix: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
520 }
521 }
522
523 #[must_use]
525 #[inline]
526 pub const fn scale(sx: f64, sy: f64) -> Self {
527 Self {
528 matrix: [sx, 0.0, 0.0, 0.0, sy, 0.0],
529 }
530 }
531
532 #[must_use]
534 #[inline]
535 pub const fn translate(tx: f64, ty: f64) -> Self {
536 Self {
537 matrix: [1.0, 0.0, tx, 0.0, 1.0, ty],
538 }
539 }
540
541 #[must_use]
543 pub fn rotate(angle_radians: f64) -> Self {
544 let cos_a = libm_cos(angle_radians);
545 let sin_a = libm_sin(angle_radians);
546 Self {
547 matrix: [cos_a, -sin_a, 0.0, sin_a, cos_a, 0.0],
548 }
549 }
550
551 #[must_use]
557 pub fn compose(self, other: &CoordTransform) -> Self {
558 let [a1, b1, c1, d1, e1, f1] = self.matrix;
566 let [a2, b2, c2, d2, e2, f2] = other.matrix;
567 Self {
568 matrix: [
569 a2 * a1 + b2 * d1,
570 a2 * b1 + b2 * e1,
571 a2 * c1 + b2 * f1 + c2,
572 d2 * a1 + e2 * d1,
573 d2 * b1 + e2 * e1,
574 d2 * c1 + e2 * f1 + f2,
575 ],
576 }
577 }
578
579 #[must_use]
581 #[inline]
582 pub fn apply(&self, p: Point2D) -> Point2D {
583 let [a, b, c, d, e, f] = self.matrix;
584 Point2D::new(a * p.x + b * p.y + c, d * p.x + e * p.y + f)
585 }
586
587 #[must_use]
589 #[inline]
590 pub fn apply3d(&self, p: Point3D) -> Point3D {
591 let p2 = self.apply(p.to_2d());
592 Point3D::new(p2.x, p2.y, p.z)
593 }
594}
595
596#[inline(always)]
617pub(crate) fn libm_sqrt(x: f64) -> f64 {
618 if x <= 0.0 {
619 return if x == 0.0 { 0.0 } else { f64::NAN };
620 }
621 let bits = x.to_bits();
627 let biased_exp = (bits >> 52) & 0x7FF;
629 let new_biased_exp = (biased_exp + 1023) >> 1;
630 let r_bits = new_biased_exp << 52;
632 let mut r = f64::from_bits(r_bits);
633 r = (r + x / r) * 0.5;
635 r = (r + x / r) * 0.5;
636 r = (r + x / r) * 0.5;
637 r = (r + x / r) * 0.5;
638 r = (r + x / r) * 0.5;
639 r = (r + x / r) * 0.5;
640 r
641}
642
643#[inline(always)]
647pub(crate) fn libm_sin(x: f64) -> f64 {
648 let (s, c) = sin_cos_core(x);
649 let _ = c;
650 s
651}
652
653#[inline(always)]
655pub(crate) fn libm_cos(x: f64) -> f64 {
656 let (s, c) = sin_cos_core(x);
657 let _ = s;
658 c
659}
660
661#[rustfmt::skip]
666#[inline(always)]
667pub(crate) fn sin_cos_core(x: f64) -> (f64, f64) {
668 let pi = core::f64::consts::PI;
669 let two_pi = 2.0 * pi;
670 let half_pi = pi * 0.5;
671 let quarter_pi = pi * 0.25;
672
673 let mut x = x % two_pi;
675 if x > pi {
676 x -= two_pi;
677 }
678 if x < -pi {
679 x += two_pi;
680 }
681
682 let ratio = x / half_pi;
685 let k_f = if ratio >= 0.0 {
687 (ratio + 0.5) as i64 as f64
688 } else {
689 (ratio - 0.5) as i64 as f64
690 };
691 let k = k_f as i64;
692 let r = x - k_f * half_pi; let _ = quarter_pi;
696
697 let r2 = r * r;
699 let sin_r = r
701 * (1.0
702 + r2 * (-1.0 / 6.0
703 + r2 * (1.0 / 120.0
704 + r2 * (-1.0 / 5040.0
705 + r2 * (1.0 / 362_880.0
706 + r2 * (-1.0 / 39_916_800.0 + r2 * (1.0 / 6_227_020_800.0)))))));
707 let cos_r = 1.0
709 + r2 * (-1.0 / 2.0
710 + r2 * (1.0 / 24.0
711 + r2 * (-1.0 / 720.0
712 + r2 * (1.0 / 40_320.0
713 + r2 * (-1.0 / 3_628_800.0 + r2 * (1.0 / 479_001_600.0))))));
714
715 let kmod = ((k % 4) + 4) as u64 % 4;
722 match kmod {
723 0 => (sin_r, cos_r),
724 1 => (cos_r, -sin_r),
725 2 => (-sin_r, -cos_r),
726 _ => (-cos_r, sin_r),
727 }
728}
729
730#[inline(always)]
732pub(crate) fn f64_min(a: f64, b: f64) -> f64 {
733 if a < b { a } else { b }
734}
735
736#[inline(always)]
738pub(crate) fn f64_max(a: f64, b: f64) -> f64 {
739 if a > b { a } else { b }
740}
741
742#[inline(always)]
744pub(crate) fn f64_abs(x: f64) -> f64 {
745 if x < 0.0 { -x } else { x }
746}
747
748#[inline(always)]
750pub(crate) fn f64_floor(x: f64) -> f64 {
751 let t = x as i64 as f64;
752 if t > x { t - 1.0 } else { t }
753}
754
755#[rustfmt::skip]
760#[inline(always)]
761pub(crate) fn libm_ln(x: f64) -> f64 {
762 if x.is_nan() {
763 return f64::NAN; }
765 if x < 0.0 {
766 return f64::NAN;
767 }
768 if x == 0.0 {
769 return f64::NEG_INFINITY;
770 }
771 if x == f64::INFINITY {
772 return f64::INFINITY;
773 }
774
775 let bits = x.to_bits();
777 let biased_exp = ((bits >> 52) & 0x7FF) as i64;
778 let mantissa_bits = bits & 0x000F_FFFF_FFFF_FFFF;
779
780 let m = f64::from_bits((1023_u64 << 52) | mantissa_bits);
782 let e = biased_exp - 1023; let t = (m - 1.0) / (m + 1.0);
789 let t2 = t * t;
790 let ln_m = 2.0
791 * t
792 * (1.0
793 + t2 * (1.0 / 3.0
794 + t2 * (1.0 / 5.0
795 + t2 * (1.0 / 7.0
796 + t2 * (1.0 / 9.0
797 + t2 * (1.0 / 11.0
798 + t2 * (1.0 / 13.0
799 + t2 * (1.0 / 15.0
800 + t2 * (1.0 / 17.0
801 + t2 * (1.0 / 19.0
802 + t2 * (1.0 / 21.0 + t2 * (1.0 / 23.0))))))))))));
803
804 e as f64 * core::f64::consts::LN_2 + ln_m
806}
807
808#[rustfmt::skip]
813#[inline(always)]
814pub(crate) fn libm_exp(x: f64) -> f64 {
815 if x.is_nan() {
816 return f64::NAN;
817 }
818 if x == f64::INFINITY {
819 return f64::INFINITY;
820 }
821 if x == f64::NEG_INFINITY {
822 return 0.0;
823 }
824
825 let n_f = f64_floor(x * core::f64::consts::LOG2_E + 0.5);
827 let n = n_f as i64;
828 let r = x - n_f * core::f64::consts::LN_2;
829
830 let r2 = r * r;
832 let exp_r = 1.0
833 + r * (1.0
834 + r * (1.0 / 2.0
835 + r * (1.0 / 6.0
836 + r * (1.0 / 24.0
837 + r * (1.0 / 120.0
838 + r * (1.0 / 720.0
839 + r * (1.0 / 5_040.0
840 + r * (1.0 / 40_320.0
841 + r * (1.0 / 362_880.0
842 + r * (1.0 / 3_628_800.0
843 + r * (1.0 / 39_916_800.0
844 + r * (1.0 / 479_001_600.0))))))))))));
845 let _ = r2; if n > 1023 {
850 return f64::INFINITY;
851 }
852 if n < -1074 {
853 return 0.0;
854 }
855
856 if n >= -1022 {
858 let pow2 = f64::from_bits(((n + 1023) as u64) << 52);
859 exp_r * pow2
860 } else {
861 let pow2a = f64::from_bits(1_u64 << 52); let pow2b = f64::from_bits(((n + 1023 + 1022) as u64) << 52);
864 exp_r * pow2a * pow2b
865 }
866}
867
868#[rustfmt::skip]
875#[inline(always)]
876pub(crate) fn libm_atan(x: f64) -> f64 {
877 if x.is_nan() {
878 return f64::NAN;
879 }
880
881 let neg = x < 0.0;
882 let mut a = f64_abs(x);
883 let mut hi = 0.0_f64;
884
885 let reciprocal = a > 1.0;
887 if reciprocal {
888 a = 1.0 / a;
889 hi = core::f64::consts::FRAC_PI_2;
890 }
891
892 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 {
897 let a_new = (a - INV_SQRT3) / (1.0 + a * INV_SQRT3);
898 if reciprocal {
899 hi -= core::f64::consts::FRAC_PI_6;
900 } else {
901 hi += core::f64::consts::FRAC_PI_6;
902 }
903 a = a_new;
904 }
905
906 let a2 = a * a;
910 let result = a
911 * (1.0
912 + a2 * (-1.0 / 3.0
913 + a2 * (1.0 / 5.0
914 + a2 * (-1.0 / 7.0
915 + a2 * (1.0 / 9.0
916 + a2 * (-1.0 / 11.0
917 + a2 * (1.0 / 13.0
918 + a2 * (-1.0 / 15.0
919 + a2 * (1.0 / 17.0 + a2 * (-1.0 / 19.0))))))))));
920
921 let val = if reciprocal { hi - result } else { hi + result };
922
923 if neg { -val } else { val }
924}