use protohoggr::{encode_bytes_field, encode_int64_field, encode_tag, encode_varint, zigzag_encode_64, Cursor, PackedSint64Iter, WIRE_LEN, WIRE_VARINT};
use super::NodeIndex;
#[derive(Default)]
pub(super) struct WayReframeScratch {
pub group_ranges: Vec<(usize, usize)>,
pub scalar_fields: Vec<u8>,
pub group_out: Vec<u8>,
pub reframed_way: Vec<u8>,
pub packed_lats: Vec<u8>,
pub packed_lons: Vec<u8>,
pub refs: Vec<i64>,
}
#[derive(Default)]
pub(super) struct WayReframeStats {
pub way_count: u64,
pub min_way_id: i64,
pub max_way_id: i64,
pub missing_locations: u64,
}
#[allow(clippy::too_many_lines)]
pub(super) fn reframe_way_blob_with_locations(
decompressed: &[u8],
node_index: &NodeIndex,
output: &mut Vec<u8>,
scratch: &mut WayReframeScratch,
) -> std::result::Result<WayReframeStats, String> {
let (st_offset, st_len) = parse_block_top(decompressed, scratch)?;
let stringtable_bytes = &decompressed[st_offset..st_offset + st_len];
output.clear();
encode_bytes_field(output, 1, stringtable_bytes);
let mut stats = WayReframeStats {
min_way_id: i64::MAX,
max_way_id: i64::MIN,
..WayReframeStats::default()
};
for i in 0..scratch.group_ranges.len() {
let (gr_offset, gr_len) = scratch.group_ranges[i];
let group_bytes = &decompressed[gr_offset..gr_offset + gr_len];
process_group(group_bytes, node_index, output, scratch, &mut stats)?;
}
output.extend_from_slice(&scratch.scalar_fields);
if stats.way_count == 0 {
stats.min_way_id = 0;
stats.max_way_id = 0;
}
Ok(stats)
}
fn parse_block_top(
decompressed: &[u8],
scratch: &mut WayReframeScratch,
) -> std::result::Result<(usize, usize), String> {
scratch.group_ranges.clear();
scratch.scalar_fields.clear();
let mut stringtable_range: Option<(usize, usize)> = None;
let mut cursor = Cursor::new(decompressed);
while let Some((field, wire_type)) = cursor.read_tag().map_err(|e| format!("reframe block tag: {e}"))? {
match (field, wire_type) {
(1, WIRE_LEN) => {
let data = cursor.read_len_delimited().map_err(|e| format!("reframe st: {e}"))?;
let offset = data.as_ptr() as usize - decompressed.as_ptr() as usize;
stringtable_range = Some((offset, data.len()));
}
(2, WIRE_LEN) => {
let data = cursor.read_len_delimited().map_err(|e| format!("reframe group: {e}"))?;
let offset = data.as_ptr() as usize - decompressed.as_ptr() as usize;
scratch.group_ranges.push((offset, data.len()));
}
(17..=20, WIRE_VARINT) => {
let raw = cursor.read_raw_field(wire_type).map_err(|e| format!("reframe scalar: {e}"))?;
encode_tag(&mut scratch.scalar_fields, field, wire_type);
scratch.scalar_fields.extend_from_slice(raw);
}
_ => cursor.skip_field(wire_type).map_err(|e| format!("reframe skip: {e}"))?,
}
}
stringtable_range.ok_or_else(|| "reframe: no StringTable in PrimitiveBlock".to_string())
}
fn process_group(
group_bytes: &[u8],
node_index: &NodeIndex,
output: &mut Vec<u8>,
scratch: &mut WayReframeScratch,
stats: &mut WayReframeStats,
) -> std::result::Result<(), String> {
scratch.group_out.clear();
let mut gr_cursor = Cursor::new(group_bytes);
while let Some((field, wire_type)) = gr_cursor.read_tag().map_err(|e| format!("reframe gtag: {e}"))? {
if field == 3 && wire_type == WIRE_LEN {
let way_bytes = gr_cursor.read_len_delimited().map_err(|e| format!("reframe way: {e}"))?;
splice_way_locations(way_bytes, node_index, scratch, stats)?;
encode_bytes_field(&mut scratch.group_out, 3, &scratch.reframed_way);
} else {
let raw = gr_cursor.read_raw_field(wire_type).map_err(|e| format!("reframe gskip: {e}"))?;
encode_tag(&mut scratch.group_out, field, wire_type);
scratch.group_out.extend_from_slice(raw);
}
}
encode_bytes_field(output, 2, &scratch.group_out);
Ok(())
}
fn splice_way_locations(
way_bytes: &[u8],
node_index: &NodeIndex,
scratch: &mut WayReframeScratch,
stats: &mut WayReframeStats,
) -> std::result::Result<(), String> {
scratch.reframed_way.clear();
scratch.refs.clear();
scratch.packed_lats.clear();
scratch.packed_lons.clear();
let mut way_id: i64 = 0;
let mut have_id = false;
let mut way_cursor = Cursor::new(way_bytes);
while let Some((wf, wt)) = way_cursor.read_tag().map_err(|e| format!("reframe wtag: {e}"))? {
match (wf, wt) {
(1, WIRE_VARINT) => {
way_id = way_cursor.read_varint_i64().map_err(|e| format!("reframe wid: {e}"))?;
have_id = true;
encode_int64_field(&mut scratch.reframed_way, 1, way_id);
}
(8, WIRE_LEN) => {
let refs_data = way_cursor.read_len_delimited().map_err(|e| format!("reframe wrefs: {e}"))?;
let mut cum: i64 = 0;
for delta in PackedSint64Iter::new(refs_data) {
cum += delta;
scratch.refs.push(cum);
}
encode_tag(&mut scratch.reframed_way, 8, WIRE_LEN);
encode_varint(&mut scratch.reframed_way, refs_data.len() as u64);
scratch.reframed_way.extend_from_slice(refs_data);
}
(9 | 10, WIRE_LEN) => {
let _ = way_cursor.read_raw_field(wt).map_err(|e| format!("reframe wstrip: {e}"))?;
}
_ => {
let raw = way_cursor.read_raw_field(wt).map_err(|e| format!("reframe wskip: {e}"))?;
encode_tag(&mut scratch.reframed_way, wf, wt);
scratch.reframed_way.extend_from_slice(raw);
}
}
}
if !have_id {
return Err("reframe: way without field 1 (id)".to_string());
}
if way_id < stats.min_way_id { stats.min_way_id = way_id; }
if way_id > stats.max_way_id { stats.max_way_id = way_id; }
stats.way_count += 1;
let mut last_lat: i64 = 0;
let mut last_lon: i64 = 0;
for &node_id in &scratch.refs {
let (lat, lon) = match node_index.get(node_id) {
Some(loc) => (i64::from(loc.0), i64::from(loc.1)),
None => {
stats.missing_locations += 1;
(0, 0)
}
};
encode_varint(&mut scratch.packed_lats, zigzag_encode_64(lat - last_lat));
encode_varint(&mut scratch.packed_lons, zigzag_encode_64(lon - last_lon));
last_lat = lat;
last_lon = lon;
}
if !scratch.refs.is_empty() {
encode_bytes_field(&mut scratch.reframed_way, 9, &scratch.packed_lats);
encode_bytes_field(&mut scratch.reframed_way, 10, &scratch.packed_lons);
}
Ok(())
}