use std::{cell::OnceCell, collections::BTreeMap};
use osmpbf::{Element, PrimitiveBlock, RelMemberType};
use crate::{
builder::report::CandidateIssue,
record::{LocationPrecision, OsmObjectType},
};
use super::{
address::{
AddressCandidate, collect_addr_tags_from_map, collect_clean_tags, emit_address,
validate_address_tags,
},
boundary::{
BoundaryRelationMember, BoundaryRelationStub, boundary_tags, has_admin_boundary_tags,
relation_member_role,
},
emitted::Emitted,
interpolation::has_interpolation_tag,
place::{emit_place_node, has_place_tag},
spill::{WayFeature, WayKind},
street::{has_highway_tag, missing_street_name_issue, street_name, street_tags},
};
#[derive(Debug, Default)]
pub(crate) struct ScanOutput {
pub emitted: Emitted,
pub ways: Vec<(WayFeature, Vec<i64>)>,
pub relations: Vec<BoundaryRelationStub>,
pub has_nodes: bool,
pub has_ways: bool,
}
pub(crate) fn scan_block(block: &PrimitiveBlock) -> ScanOutput {
let mut output = ScanOutput::default();
for element in block.elements() {
match element {
Element::DenseNode(node) => {
output.has_nodes = true;
output.emitted.report.scanned.dense_nodes += 1;
scan_node(
&mut output,
node.id(),
node.lat(),
node.lon(),
collect_clean_tags(node.tags()),
);
}
Element::Node(node) => {
output.has_nodes = true;
output.emitted.report.scanned.nodes += 1;
scan_node(
&mut output,
node.id(),
node.lat(),
node.lon(),
collect_clean_tags(node.tags()),
);
}
Element::Way(way) => {
output.has_ways = true;
output.emitted.report.scanned.ways += 1;
let tags = collect_clean_tags(way.tags());
if !tags.is_empty() {
let refs = OnceCell::new();
scan_way(&mut output, way.id(), tags, || {
refs.get_or_init(|| way.refs().collect::<Vec<_>>())
.as_slice()
});
}
}
Element::Relation(relation) => {
output.emitted.report.scanned.relations += 1;
let members = relation
.members()
.filter_map(|member| {
if member.member_type != RelMemberType::Way {
return None;
}
let role = relation_member_role(member.role().unwrap_or_default())?;
Some(BoundaryRelationMember {
way_id: member.member_id,
role,
})
})
.collect();
scan_relation(
&mut output,
relation.id(),
collect_clean_tags(relation.tags()),
members,
);
}
}
}
output
}
fn scan_node(
output: &mut ScanOutput,
object_id: i64,
lat: f64,
lon: f64,
all_tags: BTreeMap<String, String>,
) {
if all_tags.is_empty() {
return;
}
let out = &mut output.emitted;
if has_place_tag(&all_tags) {
emit_place_node(object_id, lat, lon, &all_tags, out);
}
let tags = collect_addr_tags_from_map(&all_tags);
if tags.is_empty() {
return;
}
if has_interpolation_tag(&tags) {
out.reject(
CandidateIssue::InterpolationUnsupportedObject,
OsmObjectType::Node,
object_id,
&all_tags,
Some(&tags),
Some("interpolation"),
);
return;
}
if let Err(issue) = validate_address_tags(&tags) {
out.reject(
issue,
OsmObjectType::Node,
object_id,
&all_tags,
Some(&tags),
Some("address"),
);
return;
}
emit_address(
AddressCandidate {
object_type: OsmObjectType::Node,
object_id,
lat,
lon,
location_precision: LocationPrecision::Point,
tags,
},
out,
);
}
fn scan_way<'a>(
output: &mut ScanOutput,
object_id: i64,
all_tags: BTreeMap<String, String>,
refs: impl Fn() -> &'a [i64],
) {
let feature = |kind, tags| {
let refs = refs();
(
WayFeature {
kind,
way_id: object_id,
node_count: refs.len() as u32,
tags,
},
refs.to_vec(),
)
};
if has_admin_boundary_tags(&all_tags) && !refs().is_empty() {
output
.ways
.push(feature(WayKind::Boundary, boundary_tags(&all_tags)));
}
if has_highway_tag(&all_tags) {
if street_name(&all_tags).is_some() {
if refs().is_empty() {
output.emitted.reject(
CandidateIssue::StreetUnresolvedGeometry,
OsmObjectType::Way,
object_id,
&all_tags,
Some(&BTreeMap::new()),
Some("street"),
);
} else {
output
.ways
.push(feature(WayKind::Street, street_tags(&all_tags)));
}
} else {
output.emitted.reject_in_report(
missing_street_name_issue(&all_tags),
OsmObjectType::Way,
object_id,
&all_tags,
None,
None,
);
}
}
let tags = collect_addr_tags_from_map(&all_tags);
if tags.is_empty() {
return;
}
if has_interpolation_tag(&tags) {
if refs().is_empty() {
output.emitted.reject(
CandidateIssue::InterpolationWayWithoutNodes,
OsmObjectType::Way,
object_id,
&all_tags,
Some(&tags),
Some("interpolation"),
);
} else {
output.ways.push(feature(WayKind::Interpolation, tags));
}
return;
}
if let Err(issue) = validate_address_tags(&tags) {
output.emitted.reject(
issue,
OsmObjectType::Way,
object_id,
&all_tags,
Some(&tags),
Some("address"),
);
return;
}
if refs().is_empty() {
output.emitted.reject(
CandidateIssue::WayWithoutResolvedNodes,
OsmObjectType::Way,
object_id,
&all_tags,
None,
Some("address"),
);
return;
}
output.ways.push(feature(WayKind::Address, tags));
}
fn scan_relation(
output: &mut ScanOutput,
object_id: i64,
all_tags: BTreeMap<String, String>,
members: Vec<BoundaryRelationMember>,
) {
if has_admin_boundary_tags(&all_tags) {
if !members.is_empty() {
output.relations.push(BoundaryRelationStub {
object_id,
members,
tags: boundary_tags(&all_tags),
});
}
return;
}
let tags = collect_addr_tags_from_map(&all_tags);
if tags.is_empty() {
return;
}
let (issue, layer_hint) = if has_interpolation_tag(&tags) {
(
CandidateIssue::InterpolationUnsupportedObject,
"interpolation",
)
} else {
(CandidateIssue::UnsupportedRelation, "address")
};
output.emitted.reject(
issue,
OsmObjectType::Relation,
object_id,
&all_tags,
Some(&tags),
Some(layer_hint),
);
}