1use core::f64::consts::PI;
53
54pub const STEP_MAX: u8 = 26;
56pub const LON_MIN: f64 = -180.0;
58pub const LON_MAX: f64 = 180.0;
60pub const LAT_MIN: f64 = -85.051_128_78;
65pub const LAT_MAX: f64 = 85.051_128_78;
67
68const EARTH_RADIUS: f64 = 6_372_797.560_856;
72const MERCATOR_MAX: f64 = 20_037_726.37;
74const DEG_TO_RAD: f64 = PI / 180.0;
76
77const ALPHABET: &[u8; 32] = b"0123456789bcdefghjkmnpqrstuvwxyz";
82
83pub const HASH_CHARS: usize = 11;
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq)]
88pub enum Unit {
89 M,
91 Km,
93 Ft,
95 Mi,
97}
98
99impl Unit {
100 #[must_use]
106 pub const fn metres(self) -> f64 {
107 match self {
108 Unit::M => 1.0,
109 Unit::Km => 1000.0,
110 Unit::Ft => 0.3048,
111 Unit::Mi => 1609.34,
112 }
113 }
114
115 #[must_use]
117 pub fn parse(word: &[u8]) -> Option<Unit> {
118 let mut buf = [0u8; 2];
119 if word.len() > 2 {
120 return None;
121 }
122 for (i, b) in word.iter().enumerate() {
123 buf[i] = b.to_ascii_lowercase();
124 }
125 match &buf[..word.len()] {
126 b"m" => Some(Unit::M),
127 b"km" => Some(Unit::Km),
128 b"ft" => Some(Unit::Ft),
129 b"mi" => Some(Unit::Mi),
130 _ => None,
131 }
132 }
133}
134
135#[derive(Debug, Clone, Copy, PartialEq, Eq)]
140pub struct Hash {
141 pub bits: u64,
143 pub step: u8,
145}
146
147#[derive(Debug, Clone, Copy, PartialEq)]
149pub struct Area {
150 pub lon: (f64, f64),
152 pub lat: (f64, f64),
154}
155
156impl Area {
157 #[must_use]
159 pub fn centre(&self) -> (f64, f64) {
160 let lon = ((self.lon.0 + self.lon.1) / 2.0).clamp(LON_MIN, LON_MAX);
161 let lat = ((self.lat.0 + self.lat.1) / 2.0).clamp(LAT_MIN, LAT_MAX);
162 (lon, lat)
163 }
164}
165
166#[derive(Debug, Clone, Copy)]
168pub struct Shape {
169 pub lon: f64,
171 pub lat: f64,
173 pub kind: Kind,
175 pub unit: Unit,
177}
178
179#[derive(Debug, Clone, Copy)]
181pub enum Kind {
182 Circle {
184 radius: f64,
186 },
187 Rect {
190 width: f64,
192 height: f64,
194 },
195}
196
197impl Shape {
198 #[must_use]
203 pub fn reach(&self) -> f64 {
204 let m = self.unit.metres();
205 match self.kind {
206 Kind::Circle { radius } => radius * m,
207 Kind::Rect { width, height } => {
208 let (w, h) = (width / 2.0 * m, height / 2.0 * m);
209 (w * w + h * h).sqrt()
210 }
211 }
212 }
213
214 #[must_use]
216 fn half(&self) -> (f64, f64) {
217 let m = self.unit.metres();
218 match self.kind {
219 Kind::Circle { radius } => (radius * m, radius * m),
220 Kind::Rect { width, height } => (width / 2.0 * m, height / 2.0 * m),
221 }
222 }
223
224 #[must_use]
231 pub fn bounds(&self) -> Area {
232 let (width, height) = self.half();
233 let lat_delta = (height / EARTH_RADIUS) / DEG_TO_RAD;
234 let top = (width / EARTH_RADIUS / ((self.lat + lat_delta) * DEG_TO_RAD).cos()) / DEG_TO_RAD;
235 let bottom =
236 (width / EARTH_RADIUS / ((self.lat - lat_delta) * DEG_TO_RAD).cos()) / DEG_TO_RAD;
237 let lon_delta = if self.lat < 0.0 { bottom } else { top };
238 Area {
239 lon: (self.lon - lon_delta, self.lon + lon_delta),
240 lat: (self.lat - lat_delta, self.lat + lat_delta),
241 }
242 }
243
244 #[must_use]
250 pub fn covers(&self, lon: f64, lat: f64) -> Option<f64> {
251 match self.kind {
252 Kind::Circle { radius } => {
253 let d = distance(self.lon, self.lat, lon, lat);
254 (d <= radius * self.unit.metres()).then_some(d)
255 }
256 Kind::Rect { width, height } => {
257 let m = self.unit.metres();
258 if lat_distance(lat, self.lat) > height * m / 2.0 {
261 return None;
262 }
263 if distance(lon, lat, self.lon, lat) > width * m / 2.0 {
264 return None;
265 }
266 Some(distance(self.lon, self.lat, lon, lat))
267 }
268 }
269 }
270}
271
272#[derive(Debug, Clone, Copy)]
274pub struct Search {
275 pub boxes: [Hash; 9],
278 pub bounds: Area,
281}
282
283#[must_use]
288fn interleave(x: u32, y: u32) -> u64 {
289 const B: [u64; 5] = [
290 0x5555_5555_5555_5555,
291 0x3333_3333_3333_3333,
292 0x0f0f_0f0f_0f0f_0f0f,
293 0x00ff_00ff_00ff_00ff,
294 0x0000_ffff_0000_ffff,
295 ];
296 let mut x = u64::from(x);
297 let mut y = u64::from(y);
298 for (shift, mask) in [(16, B[4]), (8, B[3]), (4, B[2]), (2, B[1]), (1, B[0])] {
299 x = (x | (x << shift)) & mask;
300 y = (y | (y << shift)) & mask;
301 }
302 x | (y << 1)
303}
304
305#[must_use]
307fn deinterleave(bits: u64) -> (u32, u32) {
308 const B: [u64; 6] = [
309 0x5555_5555_5555_5555,
310 0x3333_3333_3333_3333,
311 0x0f0f_0f0f_0f0f_0f0f,
312 0x00ff_00ff_00ff_00ff,
313 0x0000_ffff_0000_ffff,
314 0x0000_0000_ffff_ffff,
315 ];
316 let mut x = bits;
317 let mut y = bits >> 1;
318 for (shift, mask) in [
319 (0, B[0]),
320 (1, B[1]),
321 (2, B[2]),
322 (4, B[3]),
323 (8, B[4]),
324 (16, B[5]),
325 ] {
326 x = (x | (x >> shift)) & mask;
327 y = (y | (y >> shift)) & mask;
328 }
329 (x as u32, y as u32)
330}
331
332#[must_use]
334pub fn in_range(lon: f64, lat: f64) -> bool {
335 (LON_MIN..=LON_MAX).contains(&lon) && (LAT_MIN..=LAT_MAX).contains(&lat)
336}
337
338#[must_use]
343pub fn encode(lon: f64, lat: f64, step: u8) -> Option<Hash> {
344 encode_in(lon, lat, step, (LON_MIN, LON_MAX), (LAT_MIN, LAT_MAX))
345}
346
347#[must_use]
354pub fn encode_in(
355 lon: f64,
356 lat: f64,
357 step: u8,
358 lon_range: (f64, f64),
359 lat_range: (f64, f64),
360) -> Option<Hash> {
361 if step == 0 || step > 32 || !in_range(lon, lat) {
362 return None;
363 }
364 let lat_offset = (lat - lat_range.0) / (lat_range.1 - lat_range.0);
365 let lon_offset = (lon - lon_range.0) / (lon_range.1 - lon_range.0);
366 let scale = (1u64 << step) as f64;
367 let bits = interleave((lat_offset * scale) as u32, (lon_offset * scale) as u32);
368 Some(Hash { bits, step })
369}
370
371#[must_use]
373pub fn area(hash: Hash) -> Area {
374 let (ilat, ilon) = deinterleave(hash.bits);
375 let scale = (1u64 << hash.step) as f64;
376 let lon_scale = LON_MAX - LON_MIN;
377 let lat_scale = LAT_MAX - LAT_MIN;
378 Area {
379 lon: (
380 LON_MIN + (f64::from(ilon) / scale) * lon_scale,
381 LON_MIN + (f64::from(ilon + 1) / scale) * lon_scale,
382 ),
383 lat: (
384 LAT_MIN + (f64::from(ilat) / scale) * lat_scale,
385 LAT_MIN + (f64::from(ilat + 1) / scale) * lat_scale,
386 ),
387 }
388}
389
390#[must_use]
395pub const fn align(hash: Hash) -> u64 {
396 hash.bits << (52 - hash.step * 2)
397}
398
399#[must_use]
401pub const fn range(hash: Hash) -> (u64, u64) {
402 let low = align(hash);
403 let high = align(Hash {
404 bits: hash.bits + 1,
405 step: hash.step,
406 });
407 (low, high)
408}
409
410#[must_use]
412pub fn score(lon: f64, lat: f64) -> Option<u64> {
413 encode(lon, lat, STEP_MAX).map(align)
414}
415
416#[must_use]
422pub fn decode(raw: f64) -> Option<(f64, f64)> {
423 if !raw.is_finite() || raw < 0.0 || raw >= (1u64 << 52) as f64 {
424 return None;
425 }
426 let bits = raw as u64;
427 if bits == 0 {
428 return None;
432 }
433 Some(
434 area(Hash {
435 bits,
436 step: STEP_MAX,
437 })
438 .centre(),
439 )
440}
441
442#[must_use]
448pub fn lat_distance(lat1: f64, lat2: f64) -> f64 {
449 EARTH_RADIUS * ((lat2 - lat1) * DEG_TO_RAD).abs()
450}
451
452#[must_use]
454pub fn distance(lon1: f64, lat1: f64, lon2: f64, lat2: f64) -> f64 {
455 let v = ((lon2 * DEG_TO_RAD - lon1 * DEG_TO_RAD) / 2.0).sin();
456 if v == 0.0 {
457 return lat_distance(lat1, lat2);
458 }
459 let (lat1r, lat2r) = (lat1 * DEG_TO_RAD, lat2 * DEG_TO_RAD);
460 let u = ((lat2r - lat1r) / 2.0).sin();
461 let a = u * u + lat1r.cos() * lat2r.cos() * v * v;
462 2.0 * EARTH_RADIUS * a.sqrt().asin()
463}
464
465#[must_use]
472pub fn steps_for(mut metres: f64, lat: f64) -> u8 {
473 if metres == 0.0 {
474 return STEP_MAX;
475 }
476 let mut step = 1i32;
477 while metres < MERCATOR_MAX {
478 metres *= 2.0;
479 step += 1;
480 }
481 step -= 2;
482 if !(-66.0..=66.0).contains(&lat) {
483 step -= 1;
484 if !(-80.0..=80.0).contains(&lat) {
485 step -= 1;
486 }
487 }
488 step.clamp(1, i32::from(STEP_MAX)) as u8
489}
490
491#[must_use]
498fn move_lon(hash: Hash, dir: i8) -> Hash {
499 if dir == 0 {
500 return hash;
501 }
502 let width = 64 - u32::from(hash.step) * 2;
503 let mut x = hash.bits & 0xaaaa_aaaa_aaaa_aaaa;
504 let y = hash.bits & 0x5555_5555_5555_5555;
505 let zz = 0x5555_5555_5555_5555u64 >> width;
506 if dir > 0 {
507 x = x.wrapping_add(zz + 1);
508 } else {
509 x |= zz;
510 x = x.wrapping_sub(zz + 1);
511 }
512 x &= 0xaaaa_aaaa_aaaa_aaaau64 >> width;
513 Hash {
514 bits: x | y,
515 step: hash.step,
516 }
517}
518
519#[must_use]
521fn move_lat(hash: Hash, dir: i8) -> Hash {
522 if dir == 0 {
523 return hash;
524 }
525 let width = 64 - u32::from(hash.step) * 2;
526 let x = hash.bits & 0xaaaa_aaaa_aaaa_aaaa;
527 let mut y = hash.bits & 0x5555_5555_5555_5555;
528 let zz = 0xaaaa_aaaa_aaaa_aaaau64 >> width;
529 if dir > 0 {
530 y = y.wrapping_add(zz + 1);
531 } else {
532 y |= zz;
533 y = y.wrapping_sub(zz + 1);
534 }
535 y &= 0x5555_5555_5555_5555u64 >> width;
536 Hash {
537 bits: x | y,
538 step: hash.step,
539 }
540}
541
542#[must_use]
548fn neighbours(hash: Hash) -> [Hash; 8] {
549 [
550 move_lat(hash, 1),
551 move_lat(hash, -1),
552 move_lon(hash, 1),
553 move_lon(hash, -1),
554 move_lon(move_lat(hash, 1), 1),
555 move_lon(move_lat(hash, 1), -1),
556 move_lon(move_lat(hash, -1), 1),
557 move_lon(move_lat(hash, -1), -1),
558 ]
559}
560
561#[must_use]
570pub fn areas(shape: &Shape) -> Search {
571 let bounds = shape.bounds();
572 let mut step = steps_for(shape.reach(), shape.lat);
573 let Some(mut hash) = encode(shape.lon, shape.lat, step) else {
574 return Search {
575 boxes: [Hash { bits: 0, step: 0 }; 9],
576 bounds,
577 };
578 };
579 let mut near = neighbours(hash);
580
581 let short = area(near[0]).lat.1 < bounds.lat.1
582 || area(near[1]).lat.0 > bounds.lat.0
583 || area(near[2]).lon.1 < bounds.lon.1
584 || area(near[3]).lon.0 > bounds.lon.0;
585 if step > 1 && short {
586 step -= 1;
587 hash = encode(shape.lon, shape.lat, step).unwrap_or(hash);
588 near = neighbours(hash);
589 }
590
591 if step >= 2 {
594 let own = area(hash);
595 let zero = Hash { bits: 0, step: 0 };
596 if own.lat.0 < bounds.lat.0 {
597 near[1] = zero;
598 near[7] = zero;
599 near[6] = zero;
600 }
601 if own.lat.1 > bounds.lat.1 {
602 near[0] = zero;
603 near[4] = zero;
604 near[5] = zero;
605 }
606 if own.lon.0 < bounds.lon.0 {
607 near[3] = zero;
608 near[7] = zero;
609 near[5] = zero;
610 }
611 if own.lon.1 > bounds.lon.1 {
612 near[2] = zero;
613 near[6] = zero;
614 near[4] = zero;
615 }
616 }
617
618 Search {
619 boxes: [
620 hash, near[0], near[1], near[2], near[3], near[4], near[5], near[6], near[7],
621 ],
622 bounds,
623 }
624}
625
626#[must_use]
634pub fn geohash(lon: f64, lat: f64) -> Option<[u8; HASH_CHARS]> {
635 let hash = encode_in(lon, lat, STEP_MAX, (LON_MIN, LON_MAX), (-90.0, 90.0))?;
636 let mut out = [b'0'; HASH_CHARS];
637 for (i, slot) in out.iter_mut().enumerate().take(HASH_CHARS - 1) {
638 let idx = (hash.bits >> (52 - (i + 1) * 5)) & 0x1f;
639 *slot = ALPHABET[idx as usize];
640 }
641 Some(out)
642}
643
644#[cfg(test)]
645mod tests {
646 use super::*;
647
648 const PALERMO: (f64, f64, u64) = (13.361389, 38.115556, 3_479_099_956_230_698);
651 const CATANIA: (f64, f64, u64) = (15.087269, 37.502669, 3_479_447_370_796_909);
652
653 #[test]
654 fn a_point_scores_what_a_real_server_scores_it() {
655 assert_eq!(score(PALERMO.0, PALERMO.1), Some(PALERMO.2));
656 assert_eq!(score(CATANIA.0, CATANIA.1), Some(CATANIA.2));
657 }
658
659 #[test]
660 fn a_score_decodes_back_to_where_the_point_nearly_was() {
661 let (lon, lat) = decode(PALERMO.2 as f64).expect("a real score decodes");
665 assert_eq!(format!("{lon}"), "13.361389338970184");
666 assert_eq!(format!("{lat}"), "38.1155563954963");
667 let (lon, lat) = decode(CATANIA.2 as f64).expect("a real score decodes");
668 assert_eq!(format!("{lon}"), "15.087267458438873");
669 assert_eq!(format!("{lat}"), "37.50266842333162");
670 }
671
672 #[test]
673 fn a_point_outside_the_projection_has_no_score() {
674 assert_eq!(score(181.0, 38.0), None);
675 assert_eq!(score(13.0, 86.0), None);
676 assert_eq!(score(-180.1, 0.0), None);
677 assert!(score(180.0, LAT_MAX).is_some());
679 assert!(score(-180.0, LAT_MIN).is_some());
680 }
681
682 #[test]
683 fn interleaving_is_its_own_inverse() {
684 for (x, y) in [(0u32, 0u32), (1, 0), (0, 1), (0x03ff_ffff, 0x0155_5555)] {
685 assert_eq!(deinterleave(interleave(x, y)), (x, y));
686 }
687 }
688
689 #[test]
690 fn the_distances_are_the_ones_a_real_server_answers() {
691 let d = distance(PALERMO.0, PALERMO.1, CATANIA.0, CATANIA.1);
692 let (a, b) = (
695 decode(PALERMO.2 as f64).expect("a score"),
696 decode(CATANIA.2 as f64).expect("a score"),
697 );
698 let stored = distance(a.0, a.1, b.0, b.1);
699 assert_eq!(format!("{stored:.4}"), "166274.1516");
700 assert_eq!(format!("{:.4}", stored / Unit::Km.metres()), "166.2742");
701 assert_eq!(format!("{:.4}", stored / Unit::Mi.metres()), "103.3182");
702 assert_eq!(format!("{:.4}", stored / Unit::Ft.metres()), "545518.8700");
703 assert!((d - stored).abs() < 3.0, "{d} against {stored}");
706 }
707
708 #[test]
709 fn two_points_on_one_meridian_take_the_short_path() {
710 let a = decode(score(10.0, 40.0).expect("in range") as f64).expect("a score");
714 let b = decode(score(10.0, 41.0).expect("in range") as f64).expect("a score");
715 assert_eq!(a.0, b.0);
716 let d = distance(a.0, a.1, b.0, b.1);
717 assert_eq!(format!("{d:.4}"), "111226.3808");
718 assert_eq!(lat_distance(a.1, b.1), d);
719 }
720
721 #[test]
722 fn the_geohash_strings_are_the_ones_a_real_server_writes() {
723 let (lon, lat) = decode(PALERMO.2 as f64).expect("a score");
724 assert_eq!(&geohash(lon, lat).expect("in range"), b"sqc8b49rny0");
725 let (lon, lat) = decode(CATANIA.2 as f64).expect("a score");
726 assert_eq!(&geohash(lon, lat).expect("in range"), b"sqdtr74hyu0");
727 }
728
729 #[test]
730 fn a_unit_is_read_in_any_case_and_nothing_else_is() {
731 assert_eq!(Unit::parse(b"m"), Some(Unit::M));
732 assert_eq!(Unit::parse(b"KM"), Some(Unit::Km));
733 assert_eq!(Unit::parse(b"Ft"), Some(Unit::Ft));
734 assert_eq!(Unit::parse(b"mI"), Some(Unit::Mi));
735 assert_eq!(Unit::parse(b"yd"), None);
736 assert_eq!(Unit::parse(b"meters"), None);
737 assert_eq!(Unit::parse(b""), None);
738 }
739
740 #[test]
741 fn a_box_covers_the_scores_of_everything_inside_it() {
742 let hash = encode(13.0, 38.0, 10).expect("in range");
745 let (low, high) = range(hash);
746 let inside = score(13.0, 38.0).expect("in range");
747 assert!(low <= inside && inside < high);
748 let next = range(Hash {
749 bits: hash.bits + 1,
750 step: hash.step,
751 });
752 assert_eq!(high, next.0);
753 }
754
755 #[test]
756 fn the_step_estimate_shrinks_the_box_as_the_radius_grows() {
757 assert_eq!(steps_for(0.0, 0.0), STEP_MAX);
759 assert!(steps_for(1.0, 0.0) > steps_for(1000.0, 0.0));
760 assert!(steps_for(1000.0, 0.0) > steps_for(1_000_000.0, 0.0));
761 assert_eq!(steps_for(40_000_000.0, 0.0), 1);
762 assert_eq!(steps_for(1000.0, 70.0), steps_for(1000.0, 0.0) - 1);
765 assert_eq!(steps_for(1000.0, 85.0), steps_for(1000.0, 0.0) - 2);
766 }
767
768 #[test]
769 fn the_neighbours_of_a_box_are_the_eight_boxes_around_it() {
770 let hash = encode(13.0, 38.0, 10).expect("in range");
771 let own = area(hash);
772 let near = neighbours(hash);
773 assert_eq!(area(near[0]).lat.0, own.lat.1);
776 assert_eq!(area(near[1]).lat.1, own.lat.0);
777 assert_eq!(area(near[2]).lon.0, own.lon.1);
778 assert_eq!(area(near[3]).lon.1, own.lon.0);
779 assert_eq!(area(near[4]).lat.0, own.lat.1);
781 assert_eq!(area(near[4]).lon.0, own.lon.1);
782 }
783
784 #[test]
785 fn the_boxes_around_the_date_line_wrap_rather_than_run_out() {
786 let hash = encode(179.99, 0.0, 6).expect("in range");
787 let east = neighbours(hash)[2];
788 assert!(area(east).lon.0 < area(hash).lon.0);
791 }
792
793 #[test]
794 fn a_search_keeps_the_boxes_the_area_reaches_and_drops_the_rest() {
795 let shape = Shape {
796 lon: 15.0,
797 lat: 37.0,
798 kind: Kind::Circle { radius: 200.0 },
799 unit: Unit::Km,
800 };
801 let search = areas(&shape);
802 assert_ne!(search.boxes[0].bits, 0);
805 assert!(search.boxes[1..].iter().any(|h| h.bits == 0));
806 for h in &search.boxes {
808 if h.bits == 0 && h.step == 0 {
809 continue;
810 }
811 let a = area(*h);
812 assert!(a.lon.1 >= search.bounds.lon.0 && a.lon.0 <= search.bounds.lon.1);
813 assert!(a.lat.1 >= search.bounds.lat.0 && a.lat.0 <= search.bounds.lat.1);
814 }
815 }
816
817 #[test]
818 fn a_shape_covers_what_is_inside_it_and_nothing_else() {
819 let circle = Shape {
820 lon: 15.0,
821 lat: 37.0,
822 kind: Kind::Circle { radius: 100.0 },
823 unit: Unit::Km,
824 };
825 assert!(circle.covers(15.0, 37.0).is_some());
826 assert!(circle.covers(15.0, 37.5).is_some());
827 assert!(circle.covers(15.0, 39.0).is_none());
828 let rect = Shape {
831 lon: 15.0,
832 lat: 37.0,
833 kind: Kind::Rect {
834 width: 200.0,
835 height: 200.0,
836 },
837 unit: Unit::Km,
838 };
839 let corner = (15.0 + 1.1, 37.0 + 0.85);
840 assert!(rect.covers(corner.0, corner.1).is_some());
841 assert!(circle.covers(corner.0, corner.1).is_none());
842 }
843
844 #[test]
845 fn a_score_nothing_wrote_does_not_decode() {
846 assert_eq!(decode(-1.0), None);
847 assert_eq!(decode(0.0), None);
848 assert_eq!(decode(f64::NAN), None);
849 assert_eq!(decode(f64::INFINITY), None);
850 assert_eq!(decode((1u64 << 52) as f64), None);
851 assert!(decode(1.5).is_some());
855 }
856}