use super::blob::{BlobReader, BlobReaderSource, BlobType, ByteOffset};
use super::block::PrimitiveBlock;
use super::elements::{Element, Way};
use crate::error::Result;
use std::collections::BTreeSet;
use std::fs::File;
use std::ops::RangeInclusive;
use std::path::Path;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum SimpleBlobType {
Header,
Primitive,
Unknown,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum ElementsAvailable {
Yes,
No,
Unknown,
}
fn range_included(range: RangeInclusive<i64>, node_ids: &BTreeSet<i64>) -> bool {
node_ids.range(range).next().is_some()
}
#[derive(Clone, Debug, Eq, PartialEq)]
enum RangeIncluded {
Yes(RangeInclusive<i64>),
No,
Unknown,
}
#[derive(Debug)]
pub struct IdRanges {
node_ids: Option<RangeInclusive<i64>>,
way_ids: Option<RangeInclusive<i64>>,
}
#[derive(Debug)]
struct BlobInfo {
offset: ByteOffset,
blob_type: SimpleBlobType,
id_ranges: Option<IdRanges>,
}
impl BlobInfo {
fn nodes_available(&self) -> ElementsAvailable {
match self.id_ranges {
Some(IdRanges {
node_ids: Some(_), ..
}) => ElementsAvailable::Yes,
Some(IdRanges { node_ids: None, .. }) => ElementsAvailable::No,
None => ElementsAvailable::Unknown,
}
}
fn ways_available(&self) -> ElementsAvailable {
match self.id_ranges {
Some(IdRanges {
way_ids: Some(_), ..
}) => ElementsAvailable::Yes,
Some(IdRanges { way_ids: None, .. }) => ElementsAvailable::No,
None => ElementsAvailable::Unknown,
}
}
fn node_range_included(&self, node_ids: &BTreeSet<i64>) -> RangeIncluded {
match self.id_ranges.as_ref() {
None => RangeIncluded::Unknown,
Some(IdRanges { node_ids: None, .. }) => RangeIncluded::No,
Some(IdRanges {
node_ids: Some(range),
..
}) => {
if range_included(range.clone(), node_ids) {
RangeIncluded::Yes(range.clone())
} else {
RangeIncluded::No
}
}
}
}
}
pub struct IndexedReader<R: BlobReaderSource + Send> {
reader: BlobReader<R>,
index: Vec<BlobInfo>,
}
impl<R: BlobReaderSource + Send> IndexedReader<R> {
pub fn new(reader: R) -> Result<Self> {
let reader = BlobReader::new_seekable(reader)?;
Ok(Self {
reader,
index: vec![],
})
}
pub fn create_index(&mut self) -> Result<()> {
if !self.index.is_empty() {
return Ok(());
}
self.reader.seek(ByteOffset(0))?;
while let Some(result) = self.reader.next_header_skip_blob() {
let (header, offset) = result?;
let offset = offset.ok_or_else(|| {
crate::error::new_error(crate::error::ErrorKind::Io(std::io::Error::other(
"IndexedReader requires a seekable BlobReader with offset tracking",
)))
})?;
let blob_type = match header.blob_type() {
BlobType::OsmHeader => SimpleBlobType::Header,
BlobType::OsmData => SimpleBlobType::Primitive,
BlobType::Unknown(_) => SimpleBlobType::Unknown,
};
self.index.push(BlobInfo {
offset,
blob_type,
id_ranges: None,
});
}
Ok(())
}
#[allow(clippy::cognitive_complexity)]
fn update_element_id_ranges(info: &mut BlobInfo, block: &PrimitiveBlock) {
if info.id_ranges.is_some() {
return;
}
let mut min_node_id: Option<i64> = None;
let mut max_node_id: Option<i64> = None;
let mut min_way_id: Option<i64> = None;
let mut max_way_id: Option<i64> = None;
for group in block.groups() {
let check_min_max = |id, min_id: &mut Option<i64>, max_id: &mut Option<i64>| {
*min_id = Some(min_id.map_or(id, |x| x.min(id)));
*max_id = Some(max_id.map_or(id, |x| x.max(id)));
};
for node in group.nodes() {
check_min_max(node.id(), &mut min_node_id, &mut max_node_id);
}
for node in group.dense_nodes() {
check_min_max(node.id(), &mut min_node_id, &mut max_node_id);
}
for way in group.ways() {
check_min_max(way.id(), &mut min_way_id, &mut max_way_id);
}
}
let to_range = |min_id, max_id| -> Option<RangeInclusive<i64>> {
if let (Some(min), Some(max)) = (min_id, max_id) {
Some(RangeInclusive::new(min, max))
} else {
None
}
};
info.id_ranges = Some(IdRanges {
node_ids: to_range(min_node_id, max_node_id),
way_ids: to_range(min_way_id, max_way_id),
});
}
#[allow(clippy::too_many_lines)]
pub fn read_ways_and_deps<F, E>(&mut self, mut filter: F, mut element_callback: E) -> Result<()>
where
F: for<'a> FnMut(&Way<'a>) -> bool,
E: for<'a> FnMut(&Element<'a>),
{
self.create_index()?;
let mut node_ids: BTreeSet<i64> = BTreeSet::new();
for info in &mut self.index {
if info.blob_type == SimpleBlobType::Primitive
&& info.ways_available() != ElementsAvailable::No
{
let block = self
.reader
.blob_from_offset(info.offset)?
.to_primitiveblock()?;
Self::update_element_id_ranges(info, &block);
for group in block.groups() {
for way in group.ways() {
if filter(&way) {
let refs = way.refs();
node_ids.extend(refs);
element_callback(&Element::Way(way));
}
}
}
}
}
for info in &mut self.index {
if let RangeIncluded::Yes(_) = info.node_range_included(&node_ids) {
let block = self
.reader
.blob_from_offset(info.offset)?
.to_primitiveblock()?;
for group in block.groups() {
for node in group.nodes() {
if node_ids.contains(&node.id()) {
element_callback(&Element::Node(node));
}
}
for node in group.dense_nodes() {
if node_ids.contains(&node.id()) {
element_callback(&Element::DenseNode(node));
}
}
}
}
}
Ok(())
}
pub fn for_each_node<F>(&mut self, mut f: F) -> Result<()>
where
F: for<'a> FnMut(Element<'a>),
{
self.create_index()?;
for info in &mut self.index {
if info.blob_type == SimpleBlobType::Primitive
&& info.nodes_available() != ElementsAvailable::No
{
let block = self
.reader
.blob_from_offset(info.offset)?
.to_primitiveblock()?;
Self::update_element_id_ranges(info, &block);
for group in block.groups() {
for node in group.nodes() {
f(Element::Node(node));
}
for dense_node in group.dense_nodes() {
f(Element::DenseNode(dense_node));
}
}
}
}
Ok(())
}
}
impl IndexedReader<File> {
pub fn from_path<P: AsRef<Path>>(path: P) -> Result<Self> {
let f = File::open(path)?;
Self::new(f)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn test_range_included_set() {
let mut set = BTreeSet::<i64>::new();
set.extend(&[1, 2, 6]);
assert!(!range_included(RangeInclusive::new(0, 0), &set));
assert!(range_included(RangeInclusive::new(1, 1), &set));
assert!(range_included(RangeInclusive::new(2, 2), &set));
assert!(!range_included(RangeInclusive::new(3, 3), &set));
assert!(!range_included(RangeInclusive::new(3, 5), &set));
assert!(range_included(RangeInclusive::new(3, 6), &set));
assert!(range_included(RangeInclusive::new(6, 6), &set));
assert!(!range_included(RangeInclusive::new(7, 7), &set));
assert!(range_included(RangeInclusive::new(0, 1), &set));
assert!(range_included(RangeInclusive::new(6, 7), &set));
assert!(range_included(RangeInclusive::new(2, 3), &set));
assert!(range_included(RangeInclusive::new(5, 6), &set));
assert!(range_included(RangeInclusive::new(5, 8), &set));
assert!(range_included(RangeInclusive::new(0, 8), &set));
assert!(range_included(RangeInclusive::new(0, 4), &set));
}
}