1pub fn cell_hilbert(cx: u32, cy: u32, bits: u8) -> u64 {
34 let n: u64 = 1u64 << bits;
35 let (mut x, mut y) = (cx as u64, cy as u64);
36 let mut d: u64 = 0;
37 let mut s: u64 = n / 2;
38 while s > 0 {
39 let rx = if (x & s) > 0 { 1u64 } else { 0 };
40 let ry = if (y & s) > 0 { 1u64 } else { 0 };
41 d += s * s * ((3 * rx) ^ ry);
42 if ry == 0 {
44 if rx == 1 {
45 x = s - 1 - (x & (s - 1)) | (x & !(2 * s - 1));
46 y = s - 1 - (y & (s - 1)) | (y & !(2 * s - 1));
47 }
48 std::mem::swap(&mut x, &mut y);
49 }
50 s /= 2;
51 }
52 d
53}
54
55pub const LEVEL_FINE: u8 = 12;
56pub const LEVEL_COARSE: u8 = 8;
57pub const LEVEL_WORLD: u8 = 0;
63pub const MAX_CELLS: usize = 8;
65
66pub fn cell_of(lon: f64, lat: f64, bits: u8) -> (u32, u32) {
68 let n = (1u64 << bits) as f64;
69 let cx = (((lon + 180.0) / 360.0) * n).floor().clamp(0.0, n - 1.0) as u32;
70 let cy = (((lat + 90.0) / 180.0) * n).floor().clamp(0.0, n - 1.0) as u32;
71 (cx, cy)
72}
73
74#[derive(Clone, Copy, Debug, PartialEq)]
76pub struct BoxF {
77 pub xmin: f32, pub xmax: f32, pub ymin: f32, pub ymax: f32,
78}
79fn next_down(v: f64) -> f32 {
80 let f = v as f32;
81 if (f as f64) > v { f32::from_bits(if f > 0.0 { f.to_bits() - 1 } else { f.to_bits() + 1 }) } else { f }
82}
83fn next_up(v: f64) -> f32 {
84 let f = v as f32;
85 if (f as f64) < v { f32::from_bits(if f >= 0.0 { f.to_bits() + 1 } else { f.to_bits() - 1 }) } else { f }
86}
87impl BoxF {
88 pub fn from_f64(xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> BoxF {
89 BoxF { xmin: next_down(xmin), xmax: next_up(xmax),
90 ymin: next_down(ymin), ymax: next_up(ymax) }
91 }
92 pub fn intersects(&self, o: &BoxF) -> bool {
93 self.xmin <= o.xmax && self.xmax >= o.xmin
94 && self.ymin <= o.ymax && self.ymax >= o.ymin
95 }
96 pub fn as_point(&self) -> Option<(f64, f64)> {
98 if self.xmin == self.xmax && self.ymin == self.ymax {
99 Some((self.xmin as f64, self.ymin as f64))
100 } else { None }
101 }
102 pub fn encode(&self) -> [u8; 16] {
103 let mut b = [0u8; 16];
104 b[0..4].copy_from_slice(&self.xmin.to_le_bytes());
105 b[4..8].copy_from_slice(&self.xmax.to_le_bytes());
106 b[8..12].copy_from_slice(&self.ymin.to_le_bytes());
107 b[12..16].copy_from_slice(&self.ymax.to_le_bytes());
108 b
109 }
110 pub fn decode(b: &[u8]) -> Option<BoxF> {
111 if b.len() < 16 { return None; }
112 Some(BoxF {
113 xmin: f32::from_le_bytes(b[0..4].try_into().unwrap()),
114 xmax: f32::from_le_bytes(b[4..8].try_into().unwrap()),
115 ymin: f32::from_le_bytes(b[8..12].try_into().unwrap()),
116 ymax: f32::from_le_bytes(b[12..16].try_into().unwrap()),
117 })
118 }
119}
120
121pub fn cover_cells(xmin: f64, xmax: f64, ymin: f64, ymax: f64, bits: u8, max: usize)
124 -> Option<Vec<(u32, u32)>>
125{
126 let (x0, y0) = cell_of(xmin, ymin, bits);
127 let (x1, y1) = cell_of(xmax, ymax, bits);
128 let w = (x1 - x0 + 1) as usize;
129 let h = (y1 - y0 + 1) as usize;
130 if w.saturating_mul(h) > max { return None; }
131 let mut out = Vec::with_capacity(w * h);
132 for cy in y0..=y1 {
133 for cx in x0..=x1 {
134 out.push((cx, cy));
135 }
136 }
137 Some(out)
138}
139
140pub fn cover_ranges(xmin: f64, xmax: f64, ymin: f64, ymax: f64, bits: u8,
158 max_ranges: usize) -> Vec<(u64, u64)>
159{
160 use std::collections::BinaryHeap;
161 let (qx0, qy0) = cell_of(xmin, ymin, bits);
162 let (qx1, qy1) = cell_of(xmax, ymax, bits);
163 let budget = max_ranges.max(4);
164 #[derive(PartialEq, Eq, PartialOrd, Ord)]
168 struct Straddling { waste: u64, size: u32, x: u32, y: u32 }
169 let run = |x: u32, y: u32, size: u32| -> (u64, u64) {
170 let (x1, y1) = (x + size - 1, y + size - 1);
171 let corners = [
172 cell_hilbert(x, y, bits), cell_hilbert(x1, y, bits),
173 cell_hilbert(x, y1, bits), cell_hilbert(x1, y1, bits),
174 ];
175 let lo = *corners.iter().min().unwrap();
176 (lo, lo + (size as u64) * (size as u64) - 1)
177 };
178 let inside = |x: u32, y: u32, size: u32| -> Option<u64> {
181 let (x1, y1) = (x + size - 1, y + size - 1);
182 if x1 < qx0 || x > qx1 || y1 < qy0 || y > qy1 { return None; }
183 let w = (x1.min(qx1) - x.max(qx0) + 1) as u64;
184 let h = (y1.min(qy1) - y.max(qy0) + 1) as u64;
185 Some(w * h)
186 };
187 let mut out: Vec<(u64, u64)> = Vec::new();
188 let mut straddling: BinaryHeap<Straddling> = BinaryHeap::new();
189 let full = 1u32 << bits;
190 let place = |x: u32, y: u32, size: u32, out: &mut Vec<(u64, u64)>,
191 straddling: &mut BinaryHeap<Straddling>| {
192 if let Some(cells) = inside(x, y, size) {
193 let total = (size as u64) * (size as u64);
194 if cells == total {
195 out.push(run(x, y, size));
196 } else {
197 straddling.push(Straddling { waste: total - cells, size, x, y });
198 }
199 }
200 };
201 place(0, 0, full, &mut out, &mut straddling);
202 while out.len() + straddling.len() + 3 <= budget {
205 let Some(worst) = straddling.pop() else { break };
206 let half = worst.size / 2;
207 for (dx, dy) in [(0, 0), (half, 0), (0, half), (half, half)] {
208 place(worst.x + dx, worst.y + dy, half, &mut out, &mut straddling);
209 }
210 }
211 out.extend(straddling.into_iter().map(|s| run(s.x, s.y, s.size)));
212 out.sort_unstable();
214 let mut merged: Vec<(u64, u64)> = Vec::new();
215 for (lo, hi) in out {
216 match merged.last_mut() {
217 Some(last) if lo <= last.1 + 1 => last.1 = last.1.max(hi),
218 _ => merged.push((lo, hi)),
219 }
220 }
221 merged
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
232 fn hilbert_is_a_bijection_on_an_8x8_grid() {
233 let bits = 3u8;
234 let mut seen = std::collections::HashSet::new();
235 for y in 0..8u32 {
236 for x in 0..8u32 {
237 let h = cell_hilbert(x, y, bits);
238 assert!(seen.insert(h), "collision at ({x},{y})");
239 }
240 }
241 assert_eq!(seen.len(), 64);
242 let mut by_h: Vec<(u64, (u32, u32))> = (0..8u32)
244 .flat_map(|y| (0..8u32).map(move |x| (cell_hilbert(x, y, 3), (x, y))))
245 .collect();
246 by_h.sort();
247 for w in by_h.windows(2) {
248 let ((_, (x0, y0)), (_, (x1, y1))) = (w[0], w[1]);
249 let d = x0.abs_diff(x1) + y0.abs_diff(y1);
250 assert_eq!(d, 1, "curve jumps from ({x0},{y0}) to ({x1},{y1})");
251 }
252 }
253
254 #[test]
257 fn cover_ranges_cover_exactly_against_bruteforce() {
258 let bits = 6u8; let cases = [
260 (-180.0, 180.0, -90.0, 90.0),
261 (-1.0, 1.0, -1.0, 1.0),
262 (10.0, 11.5, -20.0, -19.2),
263 (100.0, 179.9, 50.0, 89.9),
264 (-0.001, 0.001, -0.001, 0.001),
265 ];
266 for (xmin, xmax, ymin, ymax) in cases {
267 let ranges = cover_ranges(xmin, xmax, ymin, ymax, bits, 32);
268 assert!(ranges.len() <= 40, "range budget blown: {}", ranges.len());
269 let (qx0, qy0) = cell_of(xmin, ymin, bits);
270 let (qx1, qy1) = cell_of(xmax, ymax, bits);
271 for cy in qy0..=qy1 {
272 for cx in qx0..=qx1 {
273 let h = cell_hilbert(cx, cy, bits);
274 assert!(ranges.iter().any(|&(lo, hi)| lo <= h && h <= hi),
275 "cell ({cx},{cy}) h={h} not covered for box {:?}",
276 (xmin, xmax, ymin, ymax));
277 }
278 }
279 }
280 }
281
282 #[test]
292 fn cover_over_fetch_is_bounded_by_the_range_budget() {
293 let bits = 16u8;
294 let (lon, lat) = (107.6f64, -6.9f64);
295 for (half_lon, half_lat, budget, ceiling) in
296 [(0.45, 0.45, 64, 1.5), (0.09, 0.09, 64, 1.5), (0.018, 0.018, 64, 1.6)]
297 {
298 let (xmin, xmax, ymin, ymax) = (lon - half_lon, lon + half_lon, lat - half_lat, lat + half_lat);
299 let ranges = cover_ranges(xmin, xmax, ymin, ymax, bits, budget);
300 assert!(ranges.len() <= budget, "{} ranges over a budget of {budget}", ranges.len());
301 let (qx0, qy0) = cell_of(xmin, ymin, bits);
302 let (qx1, qy1) = cell_of(xmax, ymax, bits);
303 let box_cells = (qx1 - qx0 + 1) as u64 * (qy1 - qy0 + 1) as u64;
304 let covered: u64 = ranges.iter().map(|(lo, hi)| hi - lo + 1).sum();
305 assert!(
306 covered as f64 <= ceiling * box_cells as f64,
307 "a {}x{} box was covered with {covered} cells ({:.2}x) by {} ranges",
308 qx1 - qx0 + 1, qy1 - qy0 + 1, covered as f64 / box_cells as f64, ranges.len()
309 );
310 }
311 }
312
313 #[test]
316 fn aligned_squares_are_contiguous_hilbert_runs() {
317 let bits = 6u8;
318 for size_log in 1..=5u32 {
319 let size = 1u32 << size_log;
320 for qy in (0..64).step_by(size as usize) {
321 for qx in (0..64).step_by(size as usize) {
322 let mut hs: Vec<u64> = (0..size).flat_map(|dy| {
323 (0..size).map(move |dx| cell_hilbert(qx + dx, qy + dy, bits))
324 }).collect();
325 hs.sort_unstable();
326 let lo = hs[0];
327 for (i, h) in hs.iter().enumerate() {
328 assert_eq!(*h, lo + i as u64,
329 "square at ({qx},{qy}) size {size} not contiguous");
330 }
331 }
332 }
333 }
334 }
335
336 #[test]
337 fn outward_rounding_always_contains_the_double_box() {
338 for &(a, b) in &[(0.1f64, 0.2f64), (-179.99999, 179.99999),
339 (37.42421356237, 37.42421356238), (-0.0, 0.0)] {
340 let bx = BoxF::from_f64(a, b, a, b);
341 assert!((bx.xmin as f64) <= a && (bx.xmax as f64) >= b);
342 assert!((bx.ymin as f64) <= a && (bx.ymax as f64) >= b);
343 }
344 }
345}
346
347#[derive(Clone, Debug, PartialEq)]
351pub enum Geom {
352 Point(f64, f64),
353 LineString(Vec<[f64; 2]>),
354 Polygon(Vec<Vec<[f64; 2]>>),
355 MultiPoint(Vec<[f64; 2]>),
356 MultiLineString(Vec<Vec<[f64; 2]>>),
357 MultiPolygon(Vec<Vec<Vec<[f64; 2]>>>),
358}
359
360impl Geom {
361 pub fn bbox(&self) -> Option<(f64, f64, f64, f64)> {
362 let mut b: Option<(f64, f64, f64, f64)> = None;
363 let mut add = |x: f64, y: f64| {
364 b = Some(match b {
365 None => (x, x, y, y),
366 Some((x0, x1, y0, y1)) => (x0.min(x), x1.max(x), y0.min(y), y1.max(y)),
367 });
368 };
369 match self {
370 Geom::Point(x, y) => add(*x, *y),
371 Geom::LineString(c) | Geom::MultiPoint(c) =>
372 c.iter().for_each(|p| add(p[0], p[1])),
373 Geom::Polygon(rs) | Geom::MultiLineString(rs) =>
374 rs.iter().flatten().for_each(|p| add(p[0], p[1])),
375 Geom::MultiPolygon(ps) =>
376 ps.iter().flatten().flatten().for_each(|p| add(p[0], p[1])),
377 }
378 b
379 }
380
381 pub fn rings_latlon(&self) -> Vec<Vec<[f64; 2]>> {
383 let flip = |r: &Vec<[f64; 2]>| r.iter().map(|p| [p[1], p[0]]).collect();
384 match self {
385 Geom::Polygon(rs) => rs.iter().take(1).map(flip).collect(),
386 Geom::MultiPolygon(ps) =>
387 ps.iter().filter_map(|rs| rs.first()).map(|r| flip(r)).collect(),
388 _ => Vec::new(),
389 }
390 }
391
392 pub fn encode(&self) -> Vec<u8> {
393 fn coords(v: &mut Vec<u8>, c: &[[f64; 2]]) {
394 v.extend_from_slice(&(c.len() as u32).to_le_bytes());
395 for p in c {
396 v.extend_from_slice(&p[0].to_le_bytes());
397 v.extend_from_slice(&p[1].to_le_bytes());
398 }
399 }
400 fn ringsets(v: &mut Vec<u8>, rs: &[Vec<[f64; 2]>]) {
401 v.extend_from_slice(&(rs.len() as u32).to_le_bytes());
402 for r in rs { coords(v, r); }
403 }
404 let mut v = Vec::new();
405 match self {
406 Geom::Point(x, y) => { v.push(1); v.extend_from_slice(&x.to_le_bytes()); v.extend_from_slice(&y.to_le_bytes()); }
407 Geom::LineString(c) => { v.push(2); coords(&mut v, c); }
408 Geom::Polygon(rs) => { v.push(3); ringsets(&mut v, rs); }
409 Geom::MultiPoint(c) => { v.push(4); coords(&mut v, c); }
410 Geom::MultiLineString(rs) => { v.push(5); ringsets(&mut v, rs); }
411 Geom::MultiPolygon(ps) => {
412 v.push(6);
413 v.extend_from_slice(&(ps.len() as u32).to_le_bytes());
414 for rs in ps { ringsets(&mut v, rs); }
415 }
416 }
417 v
418 }
419
420 pub fn decode(b: &[u8]) -> Option<Geom> {
421 fn f64_at(b: &[u8], p: &mut usize) -> Option<f64> {
422 let v = f64::from_le_bytes(b.get(*p..*p + 8)?.try_into().ok()?);
423 *p += 8; Some(v)
424 }
425 fn u32_at(b: &[u8], p: &mut usize) -> Option<u32> {
426 let v = u32::from_le_bytes(b.get(*p..*p + 4)?.try_into().ok()?);
427 *p += 4; Some(v)
428 }
429 fn coords(b: &[u8], p: &mut usize) -> Option<Vec<[f64; 2]>> {
430 let n = u32_at(b, p)? as usize;
431 if n > b.len() / 16 + 1 { return None; } let mut c = Vec::with_capacity(n);
433 for _ in 0..n { c.push([f64_at(b, p)?, f64_at(b, p)?]); }
434 Some(c)
435 }
436 fn ringsets(b: &[u8], p: &mut usize) -> Option<Vec<Vec<[f64; 2]>>> {
437 let n = u32_at(b, p)? as usize;
438 if n > b.len() / 4 + 1 { return None; }
439 let mut rs = Vec::with_capacity(n);
440 for _ in 0..n { rs.push(coords(b, p)?); }
441 Some(rs)
442 }
443 let mut p = 1usize;
444 match *b.first()? {
445 1 => Some(Geom::Point(f64_at(b, &mut p)?, f64_at(b, &mut p)?)),
446 2 => Some(Geom::LineString(coords(b, &mut p)?)),
447 3 => Some(Geom::Polygon(ringsets(b, &mut p)?)),
448 4 => Some(Geom::MultiPoint(coords(b, &mut p)?)),
449 5 => Some(Geom::MultiLineString(ringsets(b, &mut p)?)),
450 6 => {
451 let n = u32_at(b, &mut p)? as usize;
452 if n > b.len() / 4 + 1 { return None; }
453 let mut ps = Vec::with_capacity(n);
454 for _ in 0..n { ps.push(ringsets(b, &mut p)?); }
455 Some(Geom::MultiPolygon(ps))
456 }
457 _ => None,
458 }
459 }
460}