use crate::blob_meta::{BlobIndex, ElemKind};
use crate::PrimitiveBlock;
use rustc_hash::FxHashSet;
use std::io;
pub(crate) type OwnedBlock = (Vec<u8>, BlobIndex, Option<Vec<u8>>);
use protohoggr::{
encode_bytes_field, encode_packed_bool, encode_packed_int32, encode_packed_sint32,
encode_packed_sint64,
};
use super::encode::{
collect_packed_varint_keys, encode_relation, encode_relation_raw_bytes, encode_way,
encode_way_raw_bytes, encode_way_raw_bytes_with_locations, encode_way_with_locations,
};
use super::string_table::StringTable;
pub use crate::write::header_builder::HeaderBuilder;
const MAX_ENTITIES_PER_BLOCK: usize = 8000;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum BlockType {
DenseNodes,
Ways,
Relations,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Metadata<'a> {
pub version: i32,
pub timestamp: i64,
pub changeset: i64,
pub uid: i32,
pub user: &'a str,
pub visible: bool,
}
use crate::elements::{MemberId, MemberType};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct MemberData<'a> {
pub id: MemberId,
pub role: &'a str,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct RawMetadata {
pub version: i32,
pub timestamp: i64,
pub changeset: i64,
pub uid: i32,
pub user_sid: i32,
pub visible: bool,
}
pub(super) fn member_type_value(mt: MemberType) -> i32 {
match mt {
MemberType::Node => 0,
MemberType::Way => 1,
MemberType::Relation => 2,
MemberType::Unknown(_) => 0,
}
}
pub struct BlockBuilder {
string_table: StringTable,
block_type: Option<BlockType>,
count: usize,
cap: usize,
min_id: i64,
max_id: i64,
min_lat: i32,
max_lat: i32,
min_lon: i32,
max_lon: i32,
tag_key_indices: FxHashSet<u32>,
tag_key_scratch: Vec<u32>,
last_tagdata: Option<Vec<u8>>,
dense_ids: Vec<i64>,
dense_lats: Vec<i64>,
dense_lons: Vec<i64>,
dense_keys_vals: Vec<i32>,
dense_versions: Vec<i32>,
dense_timestamps: Vec<i64>,
dense_changesets: Vec<i64>,
dense_uids: Vec<i32>,
dense_user_sids: Vec<i32>,
dense_visibles: Vec<bool>,
has_dense_metadata: bool,
last_dense_id: i64,
last_dense_lat: i64,
last_dense_lon: i64,
last_dense_timestamp: i64,
last_dense_changeset: i64,
last_dense_uid: i32,
last_dense_user_sid: i32,
group_buf: Vec<u8>, elem_scratch: Vec<u8>, packed_scratch: Vec<u8>, packed_vals_scratch: Vec<u8>, packed_lat_scratch: Vec<u8>, packed_lon_scratch: Vec<u8>, info_scratch: Vec<u8>,
encode_buf: Vec<u8>,
pre_seeded: bool,
}
impl Default for BlockBuilder {
fn default() -> Self {
Self::new()
}
}
impl BlockBuilder {
pub fn new() -> Self {
Self::with_element_cap(MAX_ENTITIES_PER_BLOCK)
}
pub fn with_element_cap(cap: usize) -> Self {
assert!(
cap > 0,
"BlockBuilder::with_element_cap: --elements-per-blob must be > 0"
);
let kv_cap = cap.saturating_mul(2);
BlockBuilder {
string_table: StringTable::new(),
block_type: None,
count: 0,
cap,
min_id: i64::MAX,
max_id: i64::MIN,
min_lat: i32::MAX,
max_lat: i32::MIN,
min_lon: i32::MAX,
max_lon: i32::MIN,
tag_key_indices: FxHashSet::default(),
tag_key_scratch: Vec::new(),
last_tagdata: None,
dense_ids: Vec::with_capacity(cap),
dense_lats: Vec::with_capacity(cap),
dense_lons: Vec::with_capacity(cap),
dense_keys_vals: Vec::with_capacity(kv_cap),
dense_versions: Vec::with_capacity(cap),
dense_timestamps: Vec::with_capacity(cap),
dense_changesets: Vec::with_capacity(cap),
dense_uids: Vec::with_capacity(cap),
dense_user_sids: Vec::with_capacity(cap),
dense_visibles: Vec::with_capacity(cap),
has_dense_metadata: false,
last_dense_id: 0,
last_dense_lat: 0,
last_dense_lon: 0,
last_dense_timestamp: 0,
last_dense_changeset: 0,
last_dense_uid: 0,
last_dense_user_sid: 0,
group_buf: Vec::new(),
elem_scratch: Vec::new(),
packed_scratch: Vec::new(),
packed_vals_scratch: Vec::new(),
packed_lat_scratch: Vec::new(),
packed_lon_scratch: Vec::new(),
info_scratch: Vec::new(),
encode_buf: Vec::new(),
pre_seeded: false,
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.count == 0
}
#[inline]
fn track_id(&mut self, id: i64) {
if id < self.min_id {
self.min_id = id;
}
if id > self.max_id {
self.max_id = id;
}
}
#[inline]
fn track_coords(&mut self, decimicro_lat: i32, decimicro_lon: i32) {
if decimicro_lat < self.min_lat { self.min_lat = decimicro_lat; }
if decimicro_lat > self.max_lat { self.max_lat = decimicro_lat; }
if decimicro_lon < self.min_lon { self.min_lon = decimicro_lon; }
if decimicro_lon > self.max_lon { self.max_lon = decimicro_lon; }
}
#[inline]
pub(crate) fn is_pre_seeded(&self) -> bool {
self.pre_seeded
}
#[inline]
pub fn should_flush(&self) -> bool {
self.count >= self.cap
}
pub fn can_add_node(&self) -> bool {
match self.block_type {
None => true,
Some(BlockType::DenseNodes) => !self.should_flush(),
Some(_) => false,
}
}
pub fn can_add_way(&self) -> bool {
match self.block_type {
None => true,
Some(BlockType::Ways) => !self.should_flush(),
Some(_) => false,
}
}
pub fn can_add_relation(&self) -> bool {
match self.block_type {
None => true,
Some(BlockType::Relations) => !self.should_flush(),
Some(_) => false,
}
}
#[allow(clippy::cast_possible_wrap)]
pub fn add_node<'t>(
&mut self,
id: i64,
decimicro_lat: i32,
decimicro_lon: i32,
tags: impl IntoIterator<Item = (&'t str, &'t str)>,
metadata: Option<&Metadata<'_>>,
) {
assert!(
self.can_add_node(),
"cannot add node: block full or wrong type"
);
self.block_type = Some(BlockType::DenseNodes);
let lat = i64::from(decimicro_lat);
let lon = i64::from(decimicro_lon);
self.dense_ids.push(id - self.last_dense_id);
self.dense_lats.push(lat - self.last_dense_lat);
self.dense_lons.push(lon - self.last_dense_lon);
self.last_dense_id = id;
self.last_dense_lat = lat;
self.last_dense_lon = lon;
self.track_coords(decimicro_lat, decimicro_lon);
for (key, val) in tags {
let key_idx = self.string_table.add(key);
self.tag_key_indices.insert(key_idx);
self.dense_keys_vals.push(key_idx as i32);
self.dense_keys_vals
.push(self.string_table.add(val) as i32);
}
self.dense_keys_vals.push(0);
if let Some(meta) = metadata {
if !self.has_dense_metadata && self.count > 0 {
self.backfill_default_dense_metadata();
}
self.add_dense_metadata(meta);
} else if self.has_dense_metadata {
self.push_default_dense_metadata();
}
self.track_id(id);
self.count += 1;
}
#[allow(clippy::cast_possible_wrap)]
fn add_dense_metadata(&mut self, meta: &Metadata<'_>) {
self.has_dense_metadata = true;
self.dense_versions.push(meta.version);
self.dense_timestamps
.push(meta.timestamp - self.last_dense_timestamp);
self.last_dense_timestamp = meta.timestamp;
self.dense_changesets
.push(meta.changeset - self.last_dense_changeset);
self.last_dense_changeset = meta.changeset;
self.dense_uids.push(meta.uid - self.last_dense_uid);
self.last_dense_uid = meta.uid;
let user_sid = self.string_table.add(meta.user) as i32;
self.dense_user_sids
.push(user_sid - self.last_dense_user_sid);
self.last_dense_user_sid = user_sid;
self.dense_visibles.push(meta.visible);
}
fn backfill_default_dense_metadata(&mut self) {
self.has_dense_metadata = true;
for _ in 0..self.count {
self.dense_versions.push(0);
self.dense_timestamps.push(0);
self.dense_changesets.push(0);
self.dense_uids.push(0);
self.dense_user_sids.push(0);
self.dense_visibles.push(true);
}
}
fn push_default_dense_metadata(&mut self) {
self.dense_versions.push(0);
self.dense_timestamps.push(-self.last_dense_timestamp);
self.last_dense_timestamp = 0;
self.dense_changesets.push(-self.last_dense_changeset);
self.last_dense_changeset = 0;
self.dense_uids.push(-self.last_dense_uid);
self.last_dense_uid = 0;
self.dense_user_sids.push(-self.last_dense_user_sid);
self.last_dense_user_sid = 0;
self.dense_visibles.push(true);
}
pub fn add_way<'t>(
&mut self,
id: i64,
tags: impl IntoIterator<Item = (&'t str, &'t str)>,
refs: &[i64],
metadata: Option<&Metadata<'_>>,
) {
assert!(
self.can_add_way(),
"cannot add way: block full or wrong type"
);
self.block_type = Some(BlockType::Ways);
encode_way(
&mut self.string_table,
&mut self.group_buf,
&mut self.elem_scratch,
&mut self.packed_scratch,
&mut self.packed_vals_scratch,
&mut self.info_scratch,
&mut self.tag_key_indices,
id,
tags,
refs,
metadata,
);
self.track_id(id);
self.count += 1;
}
#[allow(clippy::too_many_arguments)]
pub fn add_way_with_locations<'t>(
&mut self,
id: i64,
tags: impl IntoIterator<Item = (&'t str, &'t str)>,
refs: &[i64],
locations: &[(i32, i32)],
metadata: Option<&Metadata<'_>>,
) {
debug_assert_eq!(refs.len(), locations.len(), "refs and locations must match");
assert!(
self.can_add_way(),
"cannot add way: block full or wrong type"
);
self.block_type = Some(BlockType::Ways);
encode_way_with_locations(
&mut self.string_table,
&mut self.group_buf,
&mut self.elem_scratch,
&mut self.packed_scratch,
&mut self.packed_vals_scratch,
&mut self.packed_lat_scratch,
&mut self.packed_lon_scratch,
&mut self.info_scratch,
&mut self.tag_key_indices,
id,
tags,
refs,
locations,
metadata,
);
self.track_id(id);
self.count += 1;
}
pub fn add_relation<'t>(
&mut self,
id: i64,
tags: impl IntoIterator<Item = (&'t str, &'t str)>,
members: &[MemberData<'_>],
metadata: Option<&Metadata<'_>>,
) {
assert!(
self.can_add_relation(),
"cannot add relation: block full or wrong type"
);
self.block_type = Some(BlockType::Relations);
encode_relation(
&mut self.string_table,
&mut self.group_buf,
&mut self.elem_scratch,
&mut self.packed_scratch,
&mut self.packed_vals_scratch,
&mut self.info_scratch,
&mut self.tag_key_indices,
id,
tags,
members,
metadata,
);
self.track_id(id);
self.count += 1;
}
pub(crate) fn pre_seed_string_table(&mut self, block: &PrimitiveBlock) {
debug_assert!(self.is_empty(), "pre_seed must be called on empty builder");
self.string_table.pre_seed(block);
self.pre_seeded = true;
}
#[allow(clippy::cast_possible_wrap)]
pub(crate) fn add_node_raw(
&mut self,
id: i64,
decimicro_lat: i32,
decimicro_lon: i32,
raw_tags: impl Iterator<Item = (i32, i32)>,
metadata: Option<&RawMetadata>,
) {
assert!(
self.can_add_node(),
"cannot add node: block full or wrong type"
);
self.block_type = Some(BlockType::DenseNodes);
let lat = i64::from(decimicro_lat);
let lon = i64::from(decimicro_lon);
self.dense_ids.push(id - self.last_dense_id);
self.dense_lats.push(lat - self.last_dense_lat);
self.dense_lons.push(lon - self.last_dense_lon);
self.last_dense_id = id;
self.last_dense_lat = lat;
self.last_dense_lon = lon;
self.track_coords(decimicro_lat, decimicro_lon);
for (key_sid, val_sid) in raw_tags {
#[allow(clippy::cast_sign_loss)]
self.tag_key_indices.insert(key_sid as u32);
self.dense_keys_vals.push(key_sid);
self.dense_keys_vals.push(val_sid);
}
self.dense_keys_vals.push(0);
if let Some(meta) = metadata {
if !self.has_dense_metadata && self.count > 0 {
self.backfill_default_dense_metadata();
}
self.add_dense_metadata_raw(meta);
} else if self.has_dense_metadata {
self.push_default_dense_metadata();
}
self.track_id(id);
self.count += 1;
}
pub(crate) fn add_way_raw_bytes(
&mut self,
id: i64,
keys_data: &[u8],
vals_data: &[u8],
refs_data: &[u8],
info_data: Option<&[u8]>,
) {
assert!(
self.can_add_way(),
"cannot add way: block full or wrong type"
);
self.block_type = Some(BlockType::Ways);
collect_packed_varint_keys(keys_data, &mut self.tag_key_indices);
encode_way_raw_bytes(
&mut self.group_buf,
&mut self.elem_scratch,
id,
keys_data,
vals_data,
refs_data,
info_data,
);
self.track_id(id);
self.count += 1;
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn add_way_raw_bytes_with_locations(
&mut self,
id: i64,
keys_data: &[u8],
vals_data: &[u8],
refs_data: &[u8],
lat_data: &[u8],
lon_data: &[u8],
info_data: Option<&[u8]>,
) {
assert!(
self.can_add_way(),
"cannot add way: block full or wrong type"
);
self.block_type = Some(BlockType::Ways);
collect_packed_varint_keys(keys_data, &mut self.tag_key_indices);
encode_way_raw_bytes_with_locations(
&mut self.group_buf,
&mut self.elem_scratch,
id,
keys_data,
vals_data,
refs_data,
lat_data,
lon_data,
info_data,
);
self.track_id(id);
self.count += 1;
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn add_relation_raw_bytes(
&mut self,
id: i64,
keys_data: &[u8],
vals_data: &[u8],
roles_sid_data: &[u8],
memids_data: &[u8],
types_data: &[u8],
info_data: Option<&[u8]>,
) {
assert!(
self.can_add_relation(),
"cannot add relation: block full or wrong type"
);
self.block_type = Some(BlockType::Relations);
collect_packed_varint_keys(keys_data, &mut self.tag_key_indices);
encode_relation_raw_bytes(
&mut self.group_buf,
&mut self.elem_scratch,
id,
keys_data,
vals_data,
roles_sid_data,
memids_data,
types_data,
info_data,
);
self.track_id(id);
self.count += 1;
}
#[allow(clippy::cast_possible_wrap)]
fn add_dense_metadata_raw(&mut self, meta: &RawMetadata) {
self.has_dense_metadata = true;
self.dense_versions.push(meta.version);
self.dense_timestamps
.push(meta.timestamp - self.last_dense_timestamp);
self.last_dense_timestamp = meta.timestamp;
self.dense_changesets
.push(meta.changeset - self.last_dense_changeset);
self.last_dense_changeset = meta.changeset;
self.dense_uids.push(meta.uid - self.last_dense_uid);
self.last_dense_uid = meta.uid;
self.dense_user_sids
.push(meta.user_sid - self.last_dense_user_sid);
self.last_dense_user_sid = meta.user_sid;
self.dense_visibles.push(meta.visible);
}
fn encode_block(&mut self) -> io::Result<Option<BlobIndex>> {
let block_type = match self.block_type {
Some(t) => t,
None => return Ok(None),
};
let kind = match block_type {
BlockType::DenseNodes => ElemKind::Node,
BlockType::Ways => ElemKind::Way,
BlockType::Relations => ElemKind::Relation,
};
let bbox = if kind == ElemKind::Node && self.min_lat <= self.max_lat {
Some(crate::blob_meta::BlobBbox::new(
self.min_lat,
self.max_lat,
self.min_lon,
self.max_lon,
))
} else {
None
};
let index = BlobIndex {
kind,
min_id: self.min_id,
max_id: self.max_id,
count: self.count as u64,
bbox,
};
self.encode_buf.clear();
self.string_table.encode_to(&mut self.encode_buf, &mut self.elem_scratch);
match block_type {
BlockType::DenseNodes => {
self.encode_dense_nodes_group();
}
BlockType::Ways | BlockType::Relations => {
encode_bytes_field(&mut self.encode_buf, 2, &self.group_buf);
}
}
self.last_tagdata = if self.tag_key_indices.is_empty() {
None
} else {
self.tag_key_scratch.clear();
self.tag_key_scratch.extend(
self.tag_key_indices.iter()
.copied()
.filter(|&idx| !self.string_table.strings[idx as usize].is_empty()),
);
self.tag_key_scratch.sort_by(|&a, &b| {
self.string_table.strings[a as usize]
.as_bytes()
.cmp(self.string_table.strings[b as usize].as_bytes())
});
let total: usize = 3 + self.tag_key_scratch.iter()
.map(|&idx| 2 + self.string_table.strings[idx as usize].len())
.sum::<usize>();
let mut buf = Vec::with_capacity(total);
buf.push(crate::blob_meta::TAG_INDEX_VERSION);
#[allow(clippy::cast_possible_truncation)]
let count = self.tag_key_scratch.len() as u16;
buf.extend_from_slice(&count.to_le_bytes());
for &idx in &self.tag_key_scratch {
let key = self.string_table.strings[idx as usize].as_bytes();
#[allow(clippy::cast_possible_truncation)]
let key_len = key.len() as u16;
buf.extend_from_slice(&key_len.to_le_bytes());
buf.extend_from_slice(key);
}
Some(buf)
};
self.reset();
Ok(Some(index))
}
#[hotpath::measure]
pub fn take(&mut self) -> io::Result<Option<&[u8]>> {
if self.encode_block()?.is_some() {
Ok(Some(&self.encode_buf))
} else {
Ok(None)
}
}
#[hotpath::measure]
pub(crate) fn take_owned(&mut self) -> io::Result<Option<OwnedBlock>> {
if let Some(index) = self.encode_block()? {
let tagdata = self.last_tagdata.take();
Ok(Some((std::mem::take(&mut self.encode_buf), index, tagdata)))
} else {
Ok(None)
}
}
fn encode_dense_nodes_group(&mut self) {
self.group_buf.clear();
encode_packed_sint64(&mut self.group_buf, &mut self.elem_scratch, 1, &self.dense_ids);
if self.has_dense_metadata {
self.elem_scratch.clear();
self.packed_scratch.clear();
encode_packed_int32(&mut self.elem_scratch, &mut self.packed_scratch, 1, &self.dense_versions);
encode_packed_sint64(&mut self.elem_scratch, &mut self.packed_scratch, 2, &self.dense_timestamps);
encode_packed_sint64(&mut self.elem_scratch, &mut self.packed_scratch, 3, &self.dense_changesets);
encode_packed_sint32(&mut self.elem_scratch, &mut self.packed_scratch, 4, &self.dense_uids);
encode_packed_sint32(&mut self.elem_scratch, &mut self.packed_scratch, 5, &self.dense_user_sids);
encode_packed_bool(&mut self.elem_scratch, 6, &self.dense_visibles);
encode_bytes_field(&mut self.group_buf, 5, &self.elem_scratch);
}
encode_packed_sint64(&mut self.group_buf, &mut self.elem_scratch, 8, &self.dense_lats);
encode_packed_sint64(&mut self.group_buf, &mut self.elem_scratch, 9, &self.dense_lons);
encode_packed_int32(&mut self.group_buf, &mut self.elem_scratch, 10, &self.dense_keys_vals);
self.elem_scratch.clear();
encode_bytes_field(&mut self.elem_scratch, 2, &self.group_buf);
encode_bytes_field(&mut self.encode_buf, 2, &self.elem_scratch);
}
fn reset(&mut self) {
self.block_type = None;
self.count = 0;
self.min_id = i64::MAX;
self.max_id = i64::MIN;
self.min_lat = i32::MAX;
self.max_lat = i32::MIN;
self.min_lon = i32::MAX;
self.max_lon = i32::MIN;
self.tag_key_indices.clear();
self.has_dense_metadata = false;
self.pre_seeded = false;
self.last_dense_id = 0;
self.last_dense_lat = 0;
self.last_dense_lon = 0;
self.last_dense_timestamp = 0;
self.last_dense_changeset = 0;
self.last_dense_uid = 0;
self.last_dense_user_sid = 0;
self.group_buf.clear();
self.dense_ids.clear();
self.dense_lats.clear();
self.dense_lons.clear();
self.dense_keys_vals.clear();
self.dense_versions.clear();
self.dense_timestamps.clear();
self.dense_changesets.clear();
self.dense_uids.clear();
self.dense_user_sids.clear();
self.dense_visibles.clear();
self.string_table.clear();
}
}