1#![no_std]
21#![warn(missing_docs)]
22#![deny(unsafe_code)]
23
24pub mod geohash;
25
26pub use geohash::GeoHashFixed;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum NoAllocError {
33 CapacityExceeded,
35 InvalidPrecision,
37 EmptyGeometry,
39}
40
41#[derive(Debug, Clone, Copy, PartialEq)]
45pub struct Point2D {
46 pub x: f64,
48 pub y: f64,
50}
51
52impl Point2D {
53 #[must_use]
55 #[inline]
56 pub const fn new(x: f64, y: f64) -> Self {
57 Self { x, y }
58 }
59
60 #[must_use]
62 #[inline]
63 pub fn distance_to(&self, other: &Point2D) -> f64 {
64 let dx = self.x - other.x;
65 let dy = self.y - other.y;
66 libm_sqrt(dx * dx + dy * dy)
67 }
68
69 #[must_use]
71 #[inline]
72 pub fn midpoint(&self, other: &Point2D) -> Point2D {
73 Point2D::new((self.x + other.x) * 0.5, (self.y + other.y) * 0.5)
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq)]
81pub struct Point3D {
82 pub x: f64,
84 pub y: f64,
86 pub z: f64,
88}
89
90impl Point3D {
91 #[must_use]
93 #[inline]
94 pub const fn new(x: f64, y: f64, z: f64) -> Self {
95 Self { x, y, z }
96 }
97
98 #[must_use]
100 #[inline]
101 pub fn distance_to(&self, other: &Point3D) -> f64 {
102 let dx = self.x - other.x;
103 let dy = self.y - other.y;
104 let dz = self.z - other.z;
105 libm_sqrt(dx * dx + dy * dy + dz * dz)
106 }
107
108 #[must_use]
110 #[inline]
111 pub const fn to_2d(&self) -> Point2D {
112 Point2D::new(self.x, self.y)
113 }
114}
115
116#[derive(Debug, Clone, Copy, PartialEq)]
120pub struct BBox2D {
121 pub min_x: f64,
123 pub min_y: f64,
125 pub max_x: f64,
127 pub max_y: f64,
129}
130
131impl BBox2D {
132 #[must_use]
134 #[inline]
135 pub const fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
136 Self {
137 min_x,
138 min_y,
139 max_x,
140 max_y,
141 }
142 }
143
144 #[must_use]
146 #[inline]
147 pub fn is_valid(&self) -> bool {
148 self.min_x <= self.max_x && self.min_y <= self.max_y
149 }
150
151 #[must_use]
153 #[inline]
154 pub fn contains_point(&self, p: Point2D) -> bool {
155 p.x >= self.min_x && p.x <= self.max_x && p.y >= self.min_y && p.y <= self.max_y
156 }
157
158 #[must_use]
160 #[inline]
161 pub fn intersects(&self, other: &BBox2D) -> bool {
162 self.min_x <= other.max_x
163 && self.max_x >= other.min_x
164 && self.min_y <= other.max_y
165 && self.max_y >= other.min_y
166 }
167
168 #[must_use]
170 #[inline]
171 pub fn union(&self, other: &BBox2D) -> BBox2D {
172 BBox2D::new(
173 f64_min(self.min_x, other.min_x),
174 f64_min(self.min_y, other.min_y),
175 f64_max(self.max_x, other.max_x),
176 f64_max(self.max_y, other.max_y),
177 )
178 }
179
180 #[must_use]
182 #[inline]
183 pub fn area(&self) -> f64 {
184 let w = self.max_x - self.min_x;
185 let h = self.max_y - self.min_y;
186 if w < 0.0 || h < 0.0 { 0.0 } else { w * h }
187 }
188}
189
190#[derive(Debug, Clone, Copy, PartialEq)]
194pub struct LineSegment2D {
195 pub start: Point2D,
197 pub end: Point2D,
199}
200
201impl LineSegment2D {
202 #[must_use]
204 #[inline]
205 pub const fn new(start: Point2D, end: Point2D) -> Self {
206 Self { start, end }
207 }
208
209 #[must_use]
211 #[inline]
212 pub fn length(&self) -> f64 {
213 self.start.distance_to(&self.end)
214 }
215
216 #[must_use]
218 #[inline]
219 pub fn midpoint(&self) -> Point2D {
220 self.start.midpoint(&self.end)
221 }
222
223 #[must_use]
227 #[inline]
228 pub fn point_on_segment(&self, t: f64) -> Point2D {
229 Point2D::new(
230 self.start.x + t * (self.end.x - self.start.x),
231 self.start.y + t * (self.end.y - self.start.y),
232 )
233 }
234
235 #[must_use]
239 pub fn intersects(&self, other: &LineSegment2D) -> Option<Point2D> {
240 let dx1 = self.end.x - self.start.x;
241 let dy1 = self.end.y - self.start.y;
242 let dx2 = other.end.x - other.start.x;
243 let dy2 = other.end.y - other.start.y;
244
245 let denom = dx1 * dy2 - dy1 * dx2;
246
247 if denom.abs() < f64::EPSILON * 1e6 {
248 return None; }
250
251 let ox = other.start.x - self.start.x;
252 let oy = other.start.y - self.start.y;
253
254 let t = (ox * dy2 - oy * dx2) / denom;
255 let u = (ox * dy1 - oy * dx1) / denom;
256
257 if (0.0..=1.0).contains(&t) && (0.0..=1.0).contains(&u) {
258 Some(self.point_on_segment(t))
259 } else {
260 None
261 }
262 }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq)]
269pub struct Triangle2D {
270 pub a: Point2D,
272 pub b: Point2D,
274 pub c: Point2D,
276}
277
278impl Triangle2D {
279 #[must_use]
281 #[inline]
282 pub const fn new(a: Point2D, b: Point2D, c: Point2D) -> Self {
283 Self { a, b, c }
284 }
285
286 #[must_use]
290 #[inline]
291 pub fn signed_area(&self) -> f64 {
292 0.5 * ((self.b.x - self.a.x) * (self.c.y - self.a.y)
293 - (self.c.x - self.a.x) * (self.b.y - self.a.y))
294 }
295
296 #[must_use]
298 #[inline]
299 pub fn area(&self) -> f64 {
300 self.signed_area().abs()
301 }
302
303 #[must_use]
305 #[inline]
306 pub fn is_clockwise(&self) -> bool {
307 self.signed_area() < 0.0
308 }
309
310 #[must_use]
312 #[inline]
313 pub fn centroid(&self) -> Point2D {
314 Point2D::new(
315 (self.a.x + self.b.x + self.c.x) / 3.0,
316 (self.a.y + self.b.y + self.c.y) / 3.0,
317 )
318 }
319
320 #[must_use]
322 pub fn perimeter(&self) -> f64 {
323 self.a.distance_to(&self.b) + self.b.distance_to(&self.c) + self.c.distance_to(&self.a)
324 }
325
326 #[must_use]
330 pub fn contains_point(&self, p: Point2D) -> bool {
331 let s_abc = self.signed_area();
333 if s_abc.abs() < f64::EPSILON {
334 return false; }
336
337 let s_pbc = Triangle2D::new(p, self.b, self.c).signed_area();
338 let s_apc = Triangle2D::new(self.a, p, self.c).signed_area();
339 let s_abp = Triangle2D::new(self.a, self.b, p).signed_area();
340
341 let same_sign = |a: f64, b: f64| (a >= 0.0) == (b >= 0.0);
342
343 same_sign(s_abc, s_pbc) && same_sign(s_abc, s_apc) && same_sign(s_abc, s_abp)
344 }
345}
346
347pub struct FixedPolygon<const N: usize> {
353 vertices: [Point2D; N],
354 len: usize,
355}
356
357impl<const N: usize> FixedPolygon<N> {
358 #[must_use]
360 #[inline]
361 pub fn new() -> Self {
362 Self {
363 vertices: [Point2D::new(0.0, 0.0); N],
364 len: 0,
365 }
366 }
367
368 #[inline]
370 pub fn try_push(&mut self, p: Point2D) -> bool {
371 if self.len >= N {
372 return false;
373 }
374 self.vertices[self.len] = p;
375 self.len += 1;
376 true
377 }
378
379 #[must_use]
381 #[inline]
382 pub fn len(&self) -> usize {
383 self.len
384 }
385
386 #[must_use]
388 #[inline]
389 pub fn is_empty(&self) -> bool {
390 self.len == 0
391 }
392
393 #[must_use]
395 #[inline]
396 pub fn vertices(&self) -> &[Point2D] {
397 &self.vertices[..self.len]
398 }
399
400 #[must_use]
402 pub fn signed_area(&self) -> f64 {
403 if self.len < 3 {
404 return 0.0;
405 }
406 let mut sum = 0.0_f64;
407 let verts = self.vertices();
408 for i in 0..self.len {
409 let j = (i + 1) % self.len;
410 sum += verts[i].x * verts[j].y;
411 sum -= verts[j].x * verts[i].y;
412 }
413 sum * 0.5
414 }
415
416 #[must_use]
418 #[inline]
419 pub fn area(&self) -> f64 {
420 self.signed_area().abs()
421 }
422
423 #[must_use]
425 pub fn perimeter(&self) -> f64 {
426 if self.len < 2 {
427 return 0.0;
428 }
429 let verts = self.vertices();
430 let mut total = 0.0_f64;
431 for i in 0..self.len {
432 let j = (i + 1) % self.len;
433 total += verts[i].distance_to(&verts[j]);
434 }
435 total
436 }
437
438 #[must_use]
440 pub fn bbox(&self) -> Option<BBox2D> {
441 if self.is_empty() {
442 return None;
443 }
444 let verts = self.vertices();
445 let mut min_x = verts[0].x;
446 let mut min_y = verts[0].y;
447 let mut max_x = verts[0].x;
448 let mut max_y = verts[0].y;
449 for v in verts.iter().skip(1) {
450 min_x = f64_min(min_x, v.x);
451 min_y = f64_min(min_y, v.y);
452 max_x = f64_max(max_x, v.x);
453 max_y = f64_max(max_y, v.y);
454 }
455 Some(BBox2D::new(min_x, min_y, max_x, max_y))
456 }
457
458 #[must_use]
460 pub fn centroid(&self) -> Option<Point2D> {
461 if self.is_empty() {
462 return None;
463 }
464 let verts = self.vertices();
465 let mut cx = 0.0_f64;
466 let mut cy = 0.0_f64;
467 for v in verts {
468 cx += v.x;
469 cy += v.y;
470 }
471 let n = self.len as f64;
472 Some(Point2D::new(cx / n, cy / n))
473 }
474}
475
476impl<const N: usize> Default for FixedPolygon<N> {
477 fn default() -> Self {
478 Self::new()
479 }
480}
481
482#[derive(Debug, Clone, Copy, PartialEq)]
493pub struct CoordTransform {
494 matrix: [f64; 6],
495}
496
497impl CoordTransform {
498 #[must_use]
500 #[inline]
501 pub const fn identity() -> Self {
502 Self {
503 matrix: [1.0, 0.0, 0.0, 0.0, 1.0, 0.0],
504 }
505 }
506
507 #[must_use]
509 #[inline]
510 pub const fn scale(sx: f64, sy: f64) -> Self {
511 Self {
512 matrix: [sx, 0.0, 0.0, 0.0, sy, 0.0],
513 }
514 }
515
516 #[must_use]
518 #[inline]
519 pub const fn translate(tx: f64, ty: f64) -> Self {
520 Self {
521 matrix: [1.0, 0.0, tx, 0.0, 1.0, ty],
522 }
523 }
524
525 #[must_use]
527 pub fn rotate(angle_radians: f64) -> Self {
528 let cos_a = libm_cos(angle_radians);
529 let sin_a = libm_sin(angle_radians);
530 Self {
531 matrix: [cos_a, -sin_a, 0.0, sin_a, cos_a, 0.0],
532 }
533 }
534
535 #[must_use]
541 pub fn compose(self, other: &CoordTransform) -> Self {
542 let [a1, b1, c1, d1, e1, f1] = self.matrix;
550 let [a2, b2, c2, d2, e2, f2] = other.matrix;
551 Self {
552 matrix: [
553 a2 * a1 + b2 * d1,
554 a2 * b1 + b2 * e1,
555 a2 * c1 + b2 * f1 + c2,
556 d2 * a1 + e2 * d1,
557 d2 * b1 + e2 * e1,
558 d2 * c1 + e2 * f1 + f2,
559 ],
560 }
561 }
562
563 #[must_use]
565 #[inline]
566 pub fn apply(&self, p: Point2D) -> Point2D {
567 let [a, b, c, d, e, f] = self.matrix;
568 Point2D::new(a * p.x + b * p.y + c, d * p.x + e * p.y + f)
569 }
570
571 #[must_use]
573 #[inline]
574 pub fn apply3d(&self, p: Point3D) -> Point3D {
575 let p2 = self.apply(p.to_2d());
576 Point3D::new(p2.x, p2.y, p.z)
577 }
578}
579
580#[inline(always)]
601fn libm_sqrt(x: f64) -> f64 {
602 if x <= 0.0 {
603 return if x == 0.0 { 0.0 } else { f64::NAN };
604 }
605 let bits = x.to_bits();
611 let biased_exp = (bits >> 52) & 0x7FF;
613 let new_biased_exp = (biased_exp + 1023) >> 1;
614 let r_bits = new_biased_exp << 52;
616 let mut r = f64::from_bits(r_bits);
617 r = (r + x / r) * 0.5;
619 r = (r + x / r) * 0.5;
620 r = (r + x / r) * 0.5;
621 r = (r + x / r) * 0.5;
622 r = (r + x / r) * 0.5;
623 r = (r + x / r) * 0.5;
624 r
625}
626
627#[inline(always)]
631fn libm_sin(x: f64) -> f64 {
632 let (s, c) = sin_cos_core(x);
633 let _ = c;
634 s
635}
636
637#[inline(always)]
639fn libm_cos(x: f64) -> f64 {
640 let (s, c) = sin_cos_core(x);
641 let _ = s;
642 c
643}
644
645#[inline(always)]
650fn sin_cos_core(x: f64) -> (f64, f64) {
651 let pi = core::f64::consts::PI;
652 let two_pi = 2.0 * pi;
653 let half_pi = pi * 0.5;
654 let quarter_pi = pi * 0.25;
655
656 let mut x = x % two_pi;
658 if x > pi {
659 x -= two_pi;
660 }
661 if x < -pi {
662 x += two_pi;
663 }
664
665 let ratio = x / half_pi;
668 let k_f = if ratio >= 0.0 {
670 (ratio + 0.5) as i64 as f64
671 } else {
672 (ratio - 0.5) as i64 as f64
673 };
674 let k = k_f as i64;
675 let r = x - k_f * half_pi; let _ = quarter_pi;
679
680 let r2 = r * r;
682 let sin_r = r
684 * (1.0
685 + r2 * (-1.0 / 6.0
686 + r2 * (1.0 / 120.0
687 + r2 * (-1.0 / 5040.0
688 + r2 * (1.0 / 362_880.0
689 + r2 * (-1.0 / 39_916_800.0 + r2 * (1.0 / 6_227_020_800.0)))))));
690 let cos_r = 1.0
692 + r2 * (-1.0 / 2.0
693 + r2 * (1.0 / 24.0
694 + r2 * (-1.0 / 720.0
695 + r2 * (1.0 / 40_320.0
696 + r2 * (-1.0 / 3_628_800.0 + r2 * (1.0 / 479_001_600.0))))));
697
698 let kmod = ((k % 4) + 4) as u64 % 4;
705 match kmod {
706 0 => (sin_r, cos_r),
707 1 => (cos_r, -sin_r),
708 2 => (-sin_r, -cos_r),
709 _ => (-cos_r, sin_r),
710 }
711}
712
713#[inline(always)]
715fn f64_min(a: f64, b: f64) -> f64 {
716 if a < b { a } else { b }
717}
718
719#[inline(always)]
721fn f64_max(a: f64, b: f64) -> f64 {
722 if a > b { a } else { b }
723}