use crate::Result;
pub(crate) struct WayGeocodeFlags {
pub(crate) is_street: bool,
pub(crate) is_building_addr: bool,
pub(crate) is_interp: bool,
}
pub(crate) struct GeocodeTagLiterals<'a> {
pub(crate) k_highway: &'a [u8],
pub(crate) k_name: &'a [u8],
pub(crate) k_addr_housenumber: &'a [u8],
pub(crate) k_addr_street: &'a [u8],
pub(crate) k_building: &'a [u8],
pub(crate) k_addr_interpolation: &'a [u8],
pub(crate) excluded_highway_values: &'a [&'a [u8]],
}
impl GeocodeTagLiterals<'_> {
pub(crate) const fn standard() -> GeocodeTagLiterals<'static> {
GeocodeTagLiterals {
k_highway: b"highway",
k_name: b"name",
k_addr_housenumber: b"addr:housenumber",
k_addr_street: b"addr:street",
k_building: b"building",
k_addr_interpolation: b"addr:interpolation",
excluded_highway_values: &[
b"footway", b"path", b"track", b"steps", b"cycleway",
b"service", b"pedestrian", b"bridleway", b"construction",
],
}
}
}
#[derive(Default)]
struct ResolvedTagIndices {
k_highway: Option<u32>,
k_name: Option<u32>,
k_addr_housenumber: Option<u32>,
k_addr_street: Option<u32>,
k_building: Option<u32>,
k_addr_interpolation: Option<u32>,
excluded_highway_indices: [Option<u32>; 9],
}
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
pub(crate) fn scan_way_refs(
decompressed: &[u8],
refs_buf: &mut Vec<i64>,
group_starts: &mut Vec<(usize, usize)>,
mut callback: impl FnMut(i64, &[i64]),
) -> Result<()> {
use crate::read::wire::{Cursor, WIRE_LEN};
let buffer = decompressed;
let mut cursor = Cursor::new(buffer);
group_starts.clear();
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(2, WIRE_LEN) => {
let data = cursor.read_len_delimited()?;
let offset = data.as_ptr() as usize - buffer.as_ptr() as usize;
group_starts.push((offset, data.len()));
}
_ => { cursor.skip_field(wire_type)?; }
}
}
for &(off, len) in group_starts.iter() {
let group_data = &buffer[off..off + len];
let mut gcursor = Cursor::new(group_data);
while let Some((field, wire_type)) = gcursor.read_tag()? {
if field == 3 && wire_type == WIRE_LEN {
let way_data = gcursor.read_len_delimited()?;
parse_way_refs(way_data, refs_buf, &mut callback)?;
} else {
gcursor.skip_field(wire_type)?;
}
}
}
Ok(())
}
#[allow(clippy::cast_possible_wrap)]
fn parse_way_refs(
way_data: &[u8],
refs_buf: &mut Vec<i64>,
callback: &mut impl FnMut(i64, &[i64]),
) -> Result<()> {
use crate::read::wire::{Cursor, PackedSint64Iter, WIRE_LEN, WIRE_VARINT};
let mut cursor = Cursor::new(way_data);
let mut way_id: i64 = 0;
let mut refs_data: Option<&[u8]> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_VARINT) => { way_id = cursor.read_varint_i64()?; }
(8, WIRE_LEN) => { refs_data = Some(cursor.read_len_delimited()?); }
_ => { cursor.skip_field(wire_type)?; }
}
}
if let Some(rd) = refs_data {
refs_buf.clear();
let mut cum: i64 = 0;
for delta in PackedSint64Iter::new(rd) {
cum += delta;
refs_buf.push(cum);
}
callback(way_id, refs_buf);
}
Ok(())
}
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap, clippy::cognitive_complexity, clippy::too_many_lines)]
pub(crate) fn scan_way_geocode_tagged_refs(
decompressed: &[u8],
literals: &GeocodeTagLiterals<'_>,
refs_buf: &mut Vec<i64>,
group_starts: &mut Vec<(usize, usize)>,
mut callback: impl FnMut(i64, WayGeocodeFlags, &[i64]),
) -> Result<()> {
use crate::read::wire::{Cursor, WIRE_LEN};
let buffer = decompressed;
let mut cursor = Cursor::new(buffer);
group_starts.clear();
let mut stringtable_data: Option<&[u8]> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_LEN) => { stringtable_data = Some(cursor.read_len_delimited()?); }
(2, WIRE_LEN) => {
let data = cursor.read_len_delimited()?;
let offset = data.as_ptr() as usize - buffer.as_ptr() as usize;
group_starts.push((offset, data.len()));
}
_ => { cursor.skip_field(wire_type)?; }
}
}
let resolved = if let Some(st_data) = stringtable_data {
resolve_tag_indices(st_data, literals)?
} else {
ResolvedTagIndices::default()
};
let any_key_present = resolved.k_highway.is_some()
|| resolved.k_name.is_some()
|| resolved.k_addr_housenumber.is_some()
|| resolved.k_addr_street.is_some()
|| resolved.k_building.is_some()
|| resolved.k_addr_interpolation.is_some();
for &(off, len) in group_starts.iter() {
let group_data = &buffer[off..off + len];
let mut gcursor = Cursor::new(group_data);
while let Some((field, wire_type)) = gcursor.read_tag()? {
if field == 3 && wire_type == WIRE_LEN {
let way_data = gcursor.read_len_delimited()?;
parse_way_tagged_refs(
way_data, &resolved, any_key_present, refs_buf, &mut callback,
)?;
} else {
gcursor.skip_field(wire_type)?;
}
}
}
Ok(())
}
fn resolve_tag_indices(
stringtable_data: &[u8],
literals: &GeocodeTagLiterals<'_>,
) -> Result<ResolvedTagIndices> {
use crate::read::wire::{Cursor, WIRE_LEN};
let mut resolved = ResolvedTagIndices::default();
let mut cursor = Cursor::new(stringtable_data);
let mut idx: u32 = 0;
while let Some((field, wire_type)) = cursor.read_tag()? {
if field == 1 && wire_type == WIRE_LEN {
let s = cursor.read_len_delimited()?;
if s == literals.k_highway && resolved.k_highway.is_none() {
resolved.k_highway = Some(idx);
}
if s == literals.k_name && resolved.k_name.is_none() {
resolved.k_name = Some(idx);
}
if s == literals.k_addr_housenumber && resolved.k_addr_housenumber.is_none() {
resolved.k_addr_housenumber = Some(idx);
}
if s == literals.k_addr_street && resolved.k_addr_street.is_none() {
resolved.k_addr_street = Some(idx);
}
if s == literals.k_building && resolved.k_building.is_none() {
resolved.k_building = Some(idx);
}
if s == literals.k_addr_interpolation && resolved.k_addr_interpolation.is_none() {
resolved.k_addr_interpolation = Some(idx);
}
for (i, &excl) in literals.excluded_highway_values.iter().enumerate() {
if i < resolved.excluded_highway_indices.len()
&& s == excl
&& resolved.excluded_highway_indices[i].is_none()
{
resolved.excluded_highway_indices[i] = Some(idx);
}
}
idx += 1;
} else {
cursor.skip_field(wire_type)?;
}
}
Ok(resolved)
}
#[allow(clippy::cast_possible_wrap)]
fn parse_way_tagged_refs(
way_data: &[u8],
resolved: &ResolvedTagIndices,
any_key_present: bool,
refs_buf: &mut Vec<i64>,
callback: &mut impl FnMut(i64, WayGeocodeFlags, &[i64]),
) -> Result<()> {
use crate::read::wire::{Cursor, PackedSint64Iter, PackedUint32Iter, WIRE_LEN, WIRE_VARINT};
let mut cursor = Cursor::new(way_data);
let mut way_id: i64 = 0;
let mut keys_data: Option<&[u8]> = None;
let mut vals_data: Option<&[u8]> = None;
let mut refs_data: Option<&[u8]> = None;
while let Some((field, wire_type)) = cursor.read_tag()? {
match (field, wire_type) {
(1, WIRE_VARINT) => { way_id = cursor.read_varint_i64()?; }
(2, WIRE_LEN) => { keys_data = Some(cursor.read_len_delimited()?); }
(3, WIRE_LEN) => { vals_data = Some(cursor.read_len_delimited()?); }
(8, WIRE_LEN) => { refs_data = Some(cursor.read_len_delimited()?); }
_ => { cursor.skip_field(wire_type)?; }
}
}
let mut has_highway = false;
let mut highway_val_idx: Option<u32> = None;
let mut has_name = false;
let mut has_addr_hn = false;
let mut has_addr_st = false;
let mut has_building = false;
let mut has_addr_interp = false;
if any_key_present {
if let (Some(kd), Some(vd)) = (keys_data, vals_data) {
let keys = PackedUint32Iter::new(kd);
let mut vals = PackedUint32Iter::new(vd);
for key_idx in keys {
let val_idx = vals.next();
if Some(key_idx) == resolved.k_highway {
has_highway = true;
highway_val_idx = val_idx;
} else if Some(key_idx) == resolved.k_name {
has_name = true;
} else if Some(key_idx) == resolved.k_addr_housenumber {
has_addr_hn = true;
} else if Some(key_idx) == resolved.k_addr_street {
has_addr_st = true;
} else if Some(key_idx) == resolved.k_building {
has_building = true;
} else if Some(key_idx) == resolved.k_addr_interpolation {
has_addr_interp = true;
}
}
}
}
let is_street = has_highway && has_name && {
!resolved.excluded_highway_indices.iter().any(|&maybe_idx| {
match (maybe_idx, highway_val_idx) {
(Some(excl), Some(val)) => excl == val,
_ => false,
}
})
};
let is_building_addr = has_building && has_addr_hn && has_addr_st;
let is_interp = has_addr_interp && has_addr_st;
if let Some(rd) = refs_data {
refs_buf.clear();
let mut cum: i64 = 0;
for delta in PackedSint64Iter::new(rd) {
cum += delta;
refs_buf.push(cum);
}
} else {
refs_buf.clear();
}
callback(
way_id,
WayGeocodeFlags { is_street, is_building_addr, is_interp },
refs_buf,
);
Ok(())
}