use super::reader::Reader;
use super::*;
#[derive(Debug, Clone, Copy)]
pub(crate) struct FuzzyInfo {
pub idx_zoom: u8,
pub agg_zoom: u8,
pub tile_count: u32,
pub multi_group_count: u32,
pub multi_value_count: u32,
pub keys_off: u64,
pub values_off: u64,
pub multi_dir_off: u64,
pub multi_values_off: u64,
pub max_group_len: u32,
pub zoom_ranges: [(u32, u32); 29],
}
impl Reader<'_> {
pub(super) fn validate_fuzzy(&mut self) -> Result<(), Error> {
let s = self.section(SECTION_FUZZY);
if !s.off.is_multiple_of(8) {
return malformed("FUZZY section alignment");
}
if u64::from(s.len) < FUZZY_HEADER_LEN {
return malformed("FUZZY length");
}
let raw = &self.raw()[s.off as usize..s.off as usize + FUZZY_HEADER_LEN as usize];
let mut f = FuzzyInfo {
idx_zoom: raw[0],
agg_zoom: raw[1],
tile_count: u32_le(raw, 4),
multi_group_count: u32_le(raw, 8),
multi_value_count: u32_le(raw, 12),
keys_off: 0,
values_off: 0,
multi_dir_off: 0,
multi_values_off: 0,
max_group_len: 1,
zoom_ranges: [(0, 0); 29],
};
if raw[2] != 0
|| raw[3] != 0
|| f.agg_zoom > f.idx_zoom
|| f.idx_zoom > 28
|| f.tile_count == 0
{
return malformed("FUZZY header");
}
let size = FUZZY_HEADER_LEN
+ 8 * u64::from(f.tile_count)
+ 2 * u64::from(f.tile_count)
+ 4 * u64::from(f.multi_group_count)
+ 2 * u64::from(f.multi_value_count);
if align4(size) != u64::from(s.len) {
return malformed("FUZZY section size");
}
f.keys_off = u64::from(s.off) + FUZZY_HEADER_LEN;
f.values_off = f.keys_off + 8 * u64::from(f.tile_count);
f.multi_dir_off = f.values_off + 2 * u64::from(f.tile_count);
f.multi_values_off = f.multi_dir_off + 4 * u64::from(f.multi_group_count);
let data = self.raw();
for pad in size..u64::from(s.len) {
if data[(u64::from(s.off) + pad) as usize] != 0 {
return malformed("FUZZY padding");
}
}
let mut prev = 0u64;
for i in 0..f.tile_count {
let key = u64_le(data, (f.keys_off + 8 * u64::from(i)) as usize);
if i > 0 && key <= prev {
return malformed("FUZZY keys not strictly ascending");
}
prev = key;
let z = (key >> 56) as u8;
if z < f.agg_zoom || z > f.idx_zoom {
return malformed("FUZZY key zoom");
}
let range = &mut f.zoom_ranges[z as usize];
if range.1 == 0 {
*range = (i, i + 1);
} else {
range.1 = i + 1;
}
let value = u16_le(data, (f.values_off + 2 * u64::from(i)) as usize);
if value & FUZZY_MULTI == 0 {
if u32::from(value) >= self.timezone_count() {
return malformed("FUZZY value index");
}
} else if u32::from(value & !FUZZY_MULTI) >= f.multi_group_count {
return malformed("FUZZY multi group index");
}
}
for g in 0..f.multi_group_count {
let off = (f.multi_dir_off + 4 * u64::from(g)) as usize;
let first = u16_le(data, off);
let count = u16_le(data, off + 2);
if count == 0 || u32::from(first) + u32::from(count) > f.multi_value_count {
return malformed("FUZZY multi group range");
}
f.max_group_len = f.max_group_len.max(u32::from(count));
}
for i in 0..f.multi_value_count {
let v = u16_le(data, (f.multi_values_off + 2 * u64::from(i)) as usize);
if u32::from(v) >= self.timezone_count() {
return malformed("FUZZY multi value index");
}
}
self.fuzzy = Some(f);
Ok(())
}
fn fuzzy_key_at(&self, f: &FuzzyInfo, i: u32) -> u64 {
u64_le(self.raw(), (f.keys_off + 8 * u64::from(i)) as usize)
}
fn fuzzy_group_at(&self, f: &FuzzyInfo, g: u32) -> (u16, u16) {
let off = (f.multi_dir_off + 4 * u64::from(g)) as usize;
(u16_le(self.raw(), off), u16_le(self.raw(), off + 2))
}
fn fuzzy_search(&self, f: &FuzzyInfo, target: u64) -> Option<u32> {
let (mut lo, mut hi) = f.zoom_ranges[(target >> 56) as usize];
if lo == hi {
return None;
}
while lo < hi {
let mid = lo + (hi - lo) / 2;
if self.fuzzy_key_at(f, mid) < target {
lo = mid + 1;
} else {
hi = mid;
}
}
if lo < f.tile_count && self.fuzzy_key_at(f, lo) == target {
Some(lo)
} else {
None
}
}
fn fuzzy_probe(&self, lng: f64, lat: f64) -> Result<Option<u16>, Error> {
let Some(f) = &self.fuzzy else {
return Err(Error::NoFuzzy);
};
if !lng.is_finite()
|| !lat.is_finite()
|| !(-180.0..=180.0).contains(&lng)
|| !(-90.0..=90.0).contains(&lat)
{
return Ok(None);
}
let tile = TileId::new(lng, lat, u32::from(f.idx_zoom));
for z in f.agg_zoom..=f.idx_zoom {
let key = tile.shift(f.idx_zoom - z).0;
if let Some(pos) = self.fuzzy_search(f, key) {
let value = u16_le(self.raw(), (f.values_off + 2 * u64::from(pos)) as usize);
return Ok(Some(value));
}
}
Ok(None)
}
pub(crate) fn fuzzy_lookup(&self, lng: f64, lat: f64) -> Result<Option<u32>, Error> {
let Some(value) = self.fuzzy_probe(lng, lat)? else {
return Ok(None);
};
if value & FUZZY_MULTI == 0 {
return Ok(Some(u32::from(value)));
}
let f = self.fuzzy.as_ref().ok_or(Error::NoFuzzy)?;
let (first, _) = self.fuzzy_group_at(f, u32::from(value & !FUZZY_MULTI));
let v = u16_le(
self.raw(),
(f.multi_values_off + 2 * u64::from(first)) as usize,
);
Ok(Some(u32::from(v)))
}
pub(crate) fn fuzzy_entries(&self) -> Result<Vec<(u64, Vec<u16>)>, Error> {
let Some(f) = &self.fuzzy else {
return Err(Error::NoFuzzy);
};
let data = self.raw();
let mut out = Vec::with_capacity(f.tile_count as usize);
for i in 0..f.tile_count {
let key = self.fuzzy_key_at(f, i);
let value = u16_le(data, (f.values_off + 2 * u64::from(i)) as usize);
if value & FUZZY_MULTI == 0 {
out.push((key, vec![value]));
continue;
}
let (first, count) = self.fuzzy_group_at(f, u32::from(value & !FUZZY_MULTI));
let group = (0..u32::from(count))
.map(|j| {
u16_le(
data,
(f.multi_values_off + 2 * (u64::from(first) + u64::from(j))) as usize,
)
})
.collect();
out.push((key, group));
}
Ok(out)
}
pub(crate) fn fuzzy_zooms(&self) -> Option<(u8, u8)> {
self.fuzzy.as_ref().map(|f| (f.idx_zoom, f.agg_zoom))
}
}