1use core::{hash, fmt, ptr};
4use super::{Bbox, Coordinate, MAX_LAT, MAX_LON};
5
6const CHUNK_BITS_SIZE: usize = 5;
7const MAX_LEN: usize = 12;
8const BASE32_CHARS: [char; 32] = [
9 '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
10 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'j', 'k', 'm',
11 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
12];
13
14const fn build_reverse_table(chars: [char; 32]) -> [u8; 256] {
15 let mut table = [u8::MAX; 256];
16
17 let mut idx = 0;
18 while idx < chars.len() {
19 table[chars[idx] as usize] = idx as u8;
20 idx += 1
21 }
22
23 table
24}
25
26const REVERSE_BASE32: [u8; 256] = build_reverse_table(BASE32_CHARS);
27
28#[derive(Copy, Clone, Debug)]
29pub struct DecodeError {
31 position: u8,
32 character: u8,
33}
34
35impl fmt::Display for DecodeError {
36 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
37 let Self { position, character } = self;
38 fmt.write_fmt(format_args!("Encountered invalid character byte='{character}' at position={position}"))
39 }
40}
41
42const fn encode(coord: Coordinate, len: usize) -> GeoHash {
43 #[inline]
45 const fn interleave(x: u32, y: u32) -> u64 {
46 #[inline]
48 const fn spread(x: u32) -> u64 {
49 let mut new_x = x as u64;
50 new_x = (new_x | (new_x << 16)) & 0x0000ffff0000ffff;
51 new_x = (new_x | (new_x << 8)) & 0x00ff00ff00ff00ff;
52 new_x = (new_x | (new_x << 4)) & 0x0f0f0f0f0f0f0f0f;
53 new_x = (new_x | (new_x << 2)) & 0x3333333333333333;
54 new_x = (new_x | (new_x << 1)) & 0x5555555555555555;
55
56 new_x
57 }
58
59 spread(x) | (spread(y) << 1)
60 }
61
62 let lat32 = (coord.latitude / (MAX_LAT * 2.0)) + 1.5;
63 let lon32 = (coord.longitude / (MAX_LON * 2.0)) + 1.5;
64
65 let lat_bits = lat32.to_bits() >> 20;
66 let lon_bits = lon32.to_bits() >> 20;
67
68 let mut idx = 0;
69 let mut buffer = [0u8; MAX_LEN];
70 let mut interleaved_bits = interleave(lat_bits as u32, lon_bits as u32);
71 while idx < len {
72 let code = ((interleaved_bits >> 59) as u32) & 0x1f;
74 buffer[idx] = BASE32_CHARS[code as usize] as u8;
75
76 interleaved_bits <<= CHUNK_BITS_SIZE as u32;
77 idx += 1;
78 }
79
80 GeoHash {
81 buffer,
82 len: len as _
83 }
84}
85
86pub struct Codec<const LEN: usize>;
91
92impl<const LEN: usize> Codec<LEN> {
93 const VALIDATE: () = {
94 if LEN < 1 {
95 panic!("Geohash len cannot less than 1")
96 } else if LEN > MAX_LEN {
97 panic!("Geohash len cannot greater than 12")
98 }
99 };
100
101 #[inline(always)]
102 pub const fn encode(coord: Coordinate) -> GeoHash {
104 let _ = Self::VALIDATE;
105 encode(coord, LEN)
106 }
107}
108
109#[derive(Copy, Clone)]
110pub struct GeoHash {
112 buffer: [u8; MAX_LEN],
113 len: u8,
114}
115
116impl GeoHash {
117 pub const MAX_LEN: usize = MAX_LEN;
119
120 #[inline(always)]
121 pub const fn encode(coord: Coordinate, len: usize) -> Option<Self> {
127 if len >= 1 && len <= Self::MAX_LEN {
128 Some(encode(coord, len))
129 } else {
130 None
131 }
132 }
133
134 #[inline(always)]
135 pub const fn from_str(text: &str) -> Self {
137 match Self::try_from_str(text) {
138 Some(result) => result,
139 None => panic!("'text' is not valid geohash")
140 }
141 }
142
143 pub const fn try_from_str(text: &str) -> Option<Self> {
145 if text.len() >= 1 && text.len() <= 12 {
146 let mut buffer = [0u8; MAX_LEN];
147 unsafe {
148 ptr::copy_nonoverlapping(text.as_ptr(), buffer.as_mut_ptr(), text.len());
149 }
150 Some(Self {
151 buffer,
152 len: text.len() as _
153 })
154 } else {
155 None
156 }
157 }
158
159 #[inline(always)]
160 pub const fn len(&self) -> usize {
162 self.len as _
163 }
164
165 #[inline(always)]
166 pub const fn as_str(&self) -> &str {
168 unsafe {
169 core::str::from_utf8_unchecked(
170 core::slice::from_raw_parts(self.buffer.as_ptr(), self.len as _)
171 )
172 }
173 }
174
175 pub const fn decode_bbox(&self) -> Result<Bbox, DecodeError> {
177 let bits_len = self.len * CHUNK_BITS_SIZE as u8;
178
179 let mut position = 0;
180 let mut hash: u64 = 0;
181 let bytes = self.as_str().as_bytes();
182 while position < bytes.len() {
183 let character = bytes[position];
184 let ch_decoded = REVERSE_BASE32[character as usize];
185 if ch_decoded == u8::MAX {
186 return Err(DecodeError {
187 position: position as _,
188 character,
189 })
190 }
191
192 hash <<= 5;
193 hash |= ch_decoded as u64;
194
195 position += 1;
196 }
197
198 #[inline]
200 const fn squash(x: u64) -> u32 {
201 let mut new_x = x & 0x5555555555555555;
202 new_x = (new_x | (new_x >> 1)) & 0x3333333333333333;
203 new_x = (new_x | (new_x >> 2)) & 0x0f0f0f0f0f0f0f0f;
204 new_x = (new_x | (new_x >> 4)) & 0x00ff00ff00ff00ff;
205 new_x = (new_x | (new_x >> 8)) & 0x0000ffff0000ffff;
206 new_x = (new_x | (new_x >> 16)) & 0x00000000ffffffff;
207 new_x as u32
208 }
209
210 #[inline]
212 const fn deinterleave(x: u64) -> (u32, u32) {
213 (squash(x), squash(x >> 1))
214 }
215
216 let full_hash = hash << (64 - bits_len);
217 let (lat32, lon32) = deinterleave(full_hash);
218
219 const fn decode32(x: u32, r: f64) -> f64 {
221 let p = f64::from_bits(((x as u64) << 20) | (1023 << 52));
222 2.0 * r * (p - 1.0) - r
223 }
224 let min = Coordinate {
225 latitude: decode32(lat32, MAX_LAT),
226 longitude: decode32(lon32, MAX_LON),
227 };
228
229 const fn calc_geohash_diff(bits: u32) -> Coordinate {
230 let lat_bits = bits / 2;
231 let lon_bits = bits - lat_bits;
232
233 let latitude = crate::math::ldexp(MAX_LAT * 2.0, -(lat_bits as i32));
234 let longitude = crate::math::ldexp(MAX_LON * 2.0, -(lon_bits as i32));
235 Coordinate {
236 latitude,
237 longitude,
238 }
239 }
240
241 let max_diff = calc_geohash_diff(bits_len as u32);
242 let max = Coordinate {
243 latitude: min.latitude + max_diff.latitude,
244 longitude: min.longitude + max_diff.longitude,
245 };
246
247 Ok(Bbox {
248 min,
249 max
250 })
251 }
252}
253
254impl fmt::Debug for GeoHash {
255 #[inline(always)]
256 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
257 fmt::Debug::fmt(self.as_str(), fmt)
258 }
259}
260
261impl fmt::Display for GeoHash {
262 #[inline(always)]
263 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
264 fmt::Display::fmt(self.as_str(), fmt)
265 }
266}
267
268impl hash::Hash for GeoHash {
269 #[inline(always)]
270 fn hash<H: hash::Hasher>(&self, state: &mut H) {
271 state.write(self.as_str().as_bytes())
272 }
273}
274
275impl PartialEq for GeoHash {
276 #[inline(always)]
277 fn eq(&self, other: &Self) -> bool {
278 self.as_str() == other.as_str()
279 }
280}
281
282impl PartialEq<str> for GeoHash {
283 #[inline(always)]
284 fn eq(&self, other: &str) -> bool {
285 self.as_str() == other
286 }
287}
288
289impl PartialEq<&str> for GeoHash {
290 #[inline(always)]
291 fn eq(&self, other: &&str) -> bool {
292 self.as_str() == *other
293 }
294}