use std::collections::HashMap;
use std::path::Path;
use osmpbf::{Element, ElementReader};
use crate::{FeaturesVecLayer, Result};
use super::OsmFeature;
use super::stitching;
#[derive(Clone)]
struct WayGeometry
{
geometry: geo::LineString,
}
pub fn read_pbf(path: impl AsRef<Path>) -> Result<FeaturesVecLayer<OsmFeature>>
{
let path = path.as_ref();
let reader = ElementReader::from_path(path)?;
let node_coords = reader.par_map_reduce(
|element| match element
{
Element::Node(node) =>
{
let coord = geo::coord! {
x: node.lon(),
y: node.lat(),
};
vec![(node.id(), coord)]
}
Element::DenseNode(node) =>
{
let coord = geo::coord! {
x: node.lon(),
y: node.lat(),
};
vec![(node.id(), coord)]
}
_ => vec![],
},
Vec::new,
|mut a, b| {
a.extend(b);
a
},
)?;
let node_coords: HashMap<i64, geo::Coord> = node_coords.into_iter().collect();
let reader = ElementReader::from_path(path)?;
let mut features = Vec::new();
let mut ways_by_id: HashMap<i64, WayGeometry> = HashMap::new();
reader.for_each(|element| match element
{
Element::Node(node) =>
{
let tags = node
.tags()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect();
let geometry = geo::Geometry::Point(geo::Point::new(node.lon(), node.lat()));
features.push(OsmFeature {
id: node.id(),
geometry,
tags,
});
}
Element::Way(way) =>
{
let tags: Vec<(String, String)> = way
.tags()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect();
let node_refs: Vec<i64> = way.refs().collect();
if node_refs.len() < 2
{
return;
}
let coords: Vec<geo::Coord> = node_refs
.iter()
.filter_map(|&node_id| node_coords.get(&node_id).copied())
.collect();
if coords.len() != node_refs.len()
{
return;
}
let linestring = geo::LineString::new(coords);
ways_by_id.insert(
way.id(),
WayGeometry {
geometry: linestring.clone(),
},
);
let geometry = if node_refs.first() == node_refs.last() && linestring.0.len() >= 3
{
geo::Geometry::Polygon(geo::Polygon::new(linestring, vec![]))
}
else if let Some(area_tag) = tags.iter().find(|(k, _)| k == "area")
{
if area_tag.1 == "yes" && linestring.0.len() >= 3
{
geo::Geometry::Polygon(geo::Polygon::new(linestring, vec![]))
}
else
{
geo::Geometry::LineString(linestring)
}
}
else
{
geo::Geometry::LineString(linestring)
};
features.push(OsmFeature {
id: way.id(),
geometry,
tags,
});
}
_ =>
{}
})?;
let reader = ElementReader::from_path(path)?;
reader.for_each(|element| {
if let Element::Relation(relation) = element
{
let is_multipolygon = relation
.tags()
.any(|(k, v)| k == "type" && v == "multipolygon");
if !is_multipolygon
{
return;
}
let tags: Vec<(String, String)> = relation
.tags()
.map(|(k, v)| (k.to_owned(), v.to_owned()))
.collect();
let mut outer_ways = Vec::new();
let mut inner_ways = Vec::new();
for member in relation.members()
{
let member_id = member.member_id;
let role = member.role().unwrap_or("");
if member.member_type != osmpbf::RelMemberType::Way
{
continue;
}
if let Some(way_geom) = ways_by_id.get(&member_id)
{
if role == "outer"
{
outer_ways.push(way_geom.geometry.clone());
}
else if role == "inner"
{
inner_ways.push(way_geom.geometry.clone());
}
}
}
if outer_ways.is_empty()
{
return;
}
let outer_rings = match stitching::stitch_rings(outer_ways)
{
Ok(rings) => rings,
Err(_) => return,
};
let inner_rings = if !inner_ways.is_empty()
{
stitching::stitch_rings(inner_ways).unwrap_or_default()
}
else
{
vec![]
};
let geometry = if outer_rings.len() == 1
{
let exterior = outer_rings[0].clone();
let interiors = inner_rings.clone();
let polygon = geo::Polygon::new(exterior, interiors);
geo::Geometry::Polygon(polygon)
}
else
{
let polygons: Vec<geo::Polygon> = outer_rings
.iter()
.map(|outer| {
let exterior = outer.clone();
let interiors = inner_rings.clone();
geo::Polygon::new(exterior, interiors)
})
.collect();
geo::Geometry::MultiPolygon(geo::MultiPolygon::new(polygons))
};
features.push(OsmFeature {
id: relation.id(),
geometry,
tags,
});
}
})?;
Ok(FeaturesVecLayer::from(features))
}