Initial commit
This commit is contained in:
parent
774adf7751
commit
731c5b6a92
|
|
@ -0,0 +1,21 @@
|
|||
[package]
|
||||
name = "read_osm"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
clap = { version = "4.1", features = ["derive"] }
|
||||
flatgeobuf = "3.24"
|
||||
geo = { version = "0.26", features = ["use-serde"] }
|
||||
geozero = "0.10"
|
||||
hashbrown = { version = "0.14", features = ["serde"] }
|
||||
log = "0.4"
|
||||
once_cell = "1.18"
|
||||
osmpbfreader = "0.16"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
simplelog = "0.12"
|
||||
smartstring = { version = "1.0", features = ["serde"] }
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use flatgeobuf::{ColumnType, FgbCrs, FgbWriter, FgbWriterOptions, GeometryType};
|
||||
use geo::{point, Contains, Geometry, Polygon};
|
||||
use geozero::{ColumnValue, PropertyProcessor};
|
||||
use hashbrown::{HashMap, HashSet};
|
||||
use log::{info, LevelFilter};
|
||||
use once_cell::sync::Lazy;
|
||||
use osmpbfreader::{NodeId, OsmObj, OsmPbfReader, Way};
|
||||
use serde::Deserialize;
|
||||
use simplelog::{ColorChoice, Config, TermLogger, TerminalMode};
|
||||
use smartstring::{LazyCompact, SmartString};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufReader, BufWriter, Read, Write},
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
static VALID_ACCESS_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
|
||||
let mut s = HashSet::new();
|
||||
s.insert("yes");
|
||||
s.insert("permisive");
|
||||
s.insert("destination");
|
||||
s
|
||||
});
|
||||
|
||||
/// Convert an OpenStreetMap file into a road-network file readable by Metropolis.
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about)]
|
||||
struct Args {
|
||||
/// Path to the input .osm.pbf file.
|
||||
#[arg(long)]
|
||||
osm_file: PathBuf,
|
||||
/// Path to the JSON file with the parameters.
|
||||
#[arg(long)]
|
||||
parameters: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Deserialize)]
|
||||
struct Parameters {
|
||||
/// Polygon representing the area to clip (in EPSG:4326).
|
||||
boundary: Option<Polygon>,
|
||||
/// Set of highway tags to filter.
|
||||
valid_highways: HashSet<SmartString<LazyCompact>>,
|
||||
}
|
||||
|
||||
fn is_valid_highway(way: &Way, parameters: &Parameters) -> bool {
|
||||
let has_access =
|
||||
VALID_ACCESS_TAGS.contains(way.tags.get("access").map(|s| s.as_str()).unwrap_or("yes"));
|
||||
let is_valid_highway = way
|
||||
.tags
|
||||
.get("highway")
|
||||
.map(|t| parameters.valid_highways.contains(t))
|
||||
.unwrap_or(false);
|
||||
way.nodes.len() >= 2 && has_access && is_valid_highway
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args = Args::parse();
|
||||
|
||||
TermLogger::init(
|
||||
LevelFilter::Info,
|
||||
Config::default(),
|
||||
TerminalMode::Mixed,
|
||||
ColorChoice::Auto,
|
||||
)
|
||||
.expect("Failed to initialize logging");
|
||||
|
||||
info!("Reading OSM file");
|
||||
let file = File::open(&args.osm_file)
|
||||
.unwrap_or_else(|_| panic!("Cannot open file: {:?}", args.osm_file));
|
||||
let buf = BufReader::new(file);
|
||||
let mut pbf = OsmPbfReader::new(buf);
|
||||
|
||||
info!("Reading parameters");
|
||||
let mut bytes = Vec::new();
|
||||
File::open(&args.parameters)
|
||||
.unwrap_or_else(|_| panic!("Cannot open file: {:?}", args.parameters))
|
||||
.read_to_end(&mut bytes)
|
||||
.unwrap_or_else(|_| panic!("Cannot read file: {:?}", args.parameters));
|
||||
let parameters: Parameters =
|
||||
serde_json::from_slice(&bytes).expect("Unable to parse parameters");
|
||||
|
||||
// Find all the nodes and ways that we need to keep.
|
||||
//
|
||||
// A node is valid if all the following conditions are met:
|
||||
// - The node is part of at least two valid ways OR it is the first or last node of a valid
|
||||
// way.
|
||||
// - The node is inside the boundary to clip (if any).
|
||||
let mut valid_ways = HashSet::new();
|
||||
let mut source_target_nodes: HashSet<NodeId> = HashSet::new();
|
||||
let mut all_nodes_count: HashMap<NodeId, usize> = HashMap::new();
|
||||
for obj in pbf.par_iter().filter_map(|r| r.ok()) {
|
||||
if let OsmObj::Way(way) = obj {
|
||||
if is_valid_highway(&way, ¶meters) {
|
||||
valid_ways.insert(way.id);
|
||||
source_target_nodes.insert(way.nodes[0]);
|
||||
source_target_nodes.insert(way.nodes[way.nodes.len() - 1]);
|
||||
for &node_id in way.nodes.iter() {
|
||||
*all_nodes_count.entry(node_id).or_default() += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let nodes_to_keep: HashSet<NodeId> = source_target_nodes
|
||||
.into_iter()
|
||||
.chain(
|
||||
all_nodes_count
|
||||
.into_iter()
|
||||
.filter_map(|(k, v)| if v >= 2 { Some(k) } else { None }),
|
||||
)
|
||||
.collect();
|
||||
|
||||
// Find the nodes inside the boundary.
|
||||
let mut valid_nodes: HashSet<NodeId> = HashSet::new();
|
||||
pbf.rewind().unwrap();
|
||||
for obj in pbf.par_iter().filter_map(|r| r.ok()) {
|
||||
if let OsmObj::Node(node) = obj {
|
||||
if nodes_to_keep.contains(&node.id) {
|
||||
let point = point!(x: node.lon(), y: node.lat());
|
||||
if parameters
|
||||
.boundary
|
||||
.as_ref()
|
||||
.map(|b| b.contains(&point))
|
||||
.unwrap_or(true)
|
||||
{
|
||||
valid_nodes.insert(node.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
println!("Number of nodes to keep: {}", valid_nodes.len());
|
||||
|
||||
// Store all the nodes that we keep in a FlatGeoBuf with their coordinates.
|
||||
let options = FgbWriterOptions {
|
||||
write_index: true,
|
||||
crs: FgbCrs {
|
||||
code: 4326,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let mut fgb = FgbWriter::create_with_options("nodes", GeometryType::Point, options).unwrap();
|
||||
fgb.add_column("fid", ColumnType::Long, |_fbb, col| {
|
||||
col.nullable = false;
|
||||
col.unique = true;
|
||||
});
|
||||
|
||||
pbf.rewind().unwrap();
|
||||
for obj in pbf.par_iter().filter_map(|r| r.ok()) {
|
||||
if let OsmObj::Node(node) = obj {
|
||||
if nodes_to_keep.contains(&node.id) {
|
||||
fgb.add_feature_geom(
|
||||
Geometry::Point(point!(x: node.lon(), y: node.lat())),
|
||||
|feat| {
|
||||
feat.property(0, "fid", &ColumnValue::Long(node.id.0))
|
||||
.unwrap();
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let output_path = "../../../output/road_network/france-nodes.fgb";
|
||||
let mut fout = BufWriter::new(File::create(output_path).expect("Cannot create output file"));
|
||||
fgb.write(&mut fout).unwrap();
|
||||
|
||||
pbf.rewind().unwrap();
|
||||
let mut tags: HashMap<SmartString<LazyCompact>, usize> = HashMap::new();
|
||||
for obj in pbf.par_iter().filter_map(|r| r.ok()) {
|
||||
if let OsmObj::Way(way) = obj {
|
||||
if valid_ways.contains(&way.id) {
|
||||
for (key, _value) in way.tags.iter() {
|
||||
*tags.entry(key.clone()).or_default() += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let buf = serde_json::to_vec(&tags)?;
|
||||
let mut file = BufWriter::new(File::open("tmp.json")?);
|
||||
file.write_all(&buf)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"valid_highway": [
|
||||
"motorway",
|
||||
"trunk",
|
||||
"primary",
|
||||
"secondary",
|
||||
"motorway_link",
|
||||
"trunk_link",
|
||||
"primary_link",
|
||||
"secondary_link",
|
||||
"tertiary",
|
||||
"tertiary_link",
|
||||
"residential",
|
||||
"living_street",
|
||||
"unclassified",
|
||||
"road"
|
||||
]
|
||||
}
|
||||
Loading…
Reference in New Issue