use anyhow::{Context, Result};
use osmpbf::{Element, ElementReader};
use std::collections::HashMap;
use std::fs::File;
use std::path::Path;
use super::{BBox, OsmSegment};
pub struct OsmExtractor {
pbf_path: String,
}
impl OsmExtractor {
pub fn new(pbf_path: String) -> Result<Self> {
if !Path::new(&pbf_path).exists() {
anyhow::bail!("PBF file not found: {}", pbf_path);
}
Ok(Self { pbf_path })
}
pub fn extract_bbox(&self, bbox: &BBox, road_classes: &[String]) -> Result<Vec<OsmSegment>> {
let mut nodes: HashMap<i64, (f64, f64)> = HashMap::new();
let mut segments: Vec<OsmSegment> = Vec::new();
tracing::info!("Reading PBF file: {}", self.pbf_path);
let file = File::open(&self.pbf_path)
.context(format!("Failed to open PBF file: {}", self.pbf_path))?;
let reader = ElementReader::new(file);
reader.for_each(|element| match element {
Element::Node(node) => {
let lon = node.lon();
let lat = node.lat();
if bbox.contains(lon, lat) {
nodes.insert(node.id(), (lon, lat));
}
}
Element::DenseNode(node) => {
let lon = node.lon();
let lat = node.lat();
if bbox.contains(lon, lat) {
nodes.insert(node.id(), (lon, lat));
}
}
_ => {}
})?;
tracing::info!("Collected {} nodes within bbox", nodes.len());
let file = File::open(&self.pbf_path)?;
let reader = ElementReader::new(file);
reader.for_each(|element| {
if let Element::Way(way) = element {
let mut highway_val: Option<&str> = None;
let mut name_val: Option<&str> = None;
let mut oneway_val: Option<&str> = None;
let mut surface_val: Option<&str> = None;
for (key, value) in way.tags() {
match key {
"highway" => highway_val = Some(value),
"name" => name_val = Some(value),
"oneway" => oneway_val = Some(value),
"surface" => surface_val = Some(value),
_ => {}
}
}
if let Some(hw) = highway_val {
if !road_classes.is_empty() && !road_classes.iter().any(|rc| rc == hw) {
return;
}
let mut geometry: Vec<(f64, f64)> = Vec::new();
for node_id in way.refs() {
if let Some(&coords) = nodes.get(&node_id) {
geometry.push(coords);
}
}
if geometry.len() >= 2 {
segments.push(OsmSegment {
id: way.id(),
name: name_val.map(|s| s.to_string()),
highway: hw.to_string(),
oneway: oneway_val.map(|s| s.to_string()),
surface: surface_val.map(|s| s.to_string()),
geometry,
});
}
}
}
})?;
tracing::info!("Extracted {} road segments", segments.len());
Ok(segments)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_osm_extractor_new_not_found() {
let path = "non_existent_file.pbf".to_string();
let result = OsmExtractor::new(path.clone());
assert!(result.is_err());
if let Err(err) = result {
assert_eq!(err.to_string(), format!("PBF file not found: {}", path));
}
}
}