mirror of
https://github.com/a-b-street/abstreet.git
synced 2024-11-24 09:24:26 +03:00
Address SUMO PR comments and also rebase. Mainly:
- use an enum for vehicle types - distinguish lane and internal lane IDs
This commit is contained in:
parent
8436b5358e
commit
6f5f001406
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3763,7 +3763,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
|
||||
name = "sumo"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"abstio",
|
||||
"abstutil",
|
||||
"anyhow",
|
||||
"geom",
|
||||
"map_model",
|
||||
"quick-xml",
|
||||
|
@ -3,7 +3,8 @@
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
|
||||
|
||||
use abstutil::{MapName, Parallelism, Tags, Timer};
|
||||
use abstio::MapName;
|
||||
use abstutil::{Parallelism, Tags, Timer};
|
||||
use geom::{Bounds, Distance, FindClosest, GPSBounds, HashablePt2D, Speed, EPSILON_DIST};
|
||||
|
||||
use crate::pathfind::Pathfinder;
|
||||
|
@ -5,7 +5,9 @@ authors = ["Dustin Carlino <dabreegster@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
abstio = { path = "../abstio" }
|
||||
abstutil = { path = "../abstutil" }
|
||||
anyhow = "1.0.37"
|
||||
geom = { path = "../geom" }
|
||||
map_model = { path = "../map_model" }
|
||||
quick-xml = { version = "0.20.0", features=["serialize"] }
|
||||
|
@ -15,7 +15,7 @@ and [SUMO](https://www.eclipse.org/sumo/). Some of the ideas:
|
||||
|
||||
A quick SUMO primer. To convert an OSM file into a SUMO network:
|
||||
|
||||
`netconvert --osm-files data/input/seattle/osm/montlake.osm --output.street-names -o montlake.net.xml`
|
||||
`netconvert --osm-files data/input/seattle/osm/montlake.osm --output.street-names --keep-edges.components 1 -o montlake.net.xml`
|
||||
|
||||
To convert the network into an ABST map:
|
||||
|
||||
|
@ -1,10 +1,13 @@
|
||||
//! This crate provides a Rust interface to different parts of the [SUMO](https://www.eclipse.org/sumo/) traffic simulator.
|
||||
|
||||
#[macro_use]
|
||||
extern crate anyhow;
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use geom::{Distance, PolyLine, Polygon, Pt2D, Speed};
|
||||
|
||||
pub use self::raw::{Connection, Direction, EdgeID, LaneID, NodeID};
|
||||
pub use self::raw::{Connection, Direction, EdgeID, InternalLaneID, LaneID, NodeID};
|
||||
|
||||
mod normalize;
|
||||
mod raw;
|
||||
@ -44,21 +47,22 @@ pub struct Lane {
|
||||
pub length: Distance,
|
||||
pub width: Distance,
|
||||
pub center_line: PolyLine,
|
||||
pub allow: Vec<String>,
|
||||
pub allow: Vec<VehicleClass>,
|
||||
}
|
||||
|
||||
/// See https://sumo.dlr.de/docs/Networks/SUMO_Road_Networks.html#internal_edges
|
||||
pub struct InternalEdge {
|
||||
pub id: EdgeID,
|
||||
pub lanes: Vec<InternalLane>,
|
||||
}
|
||||
|
||||
pub struct InternalLane {
|
||||
pub id: LaneID,
|
||||
pub id: InternalLaneID,
|
||||
pub index: usize,
|
||||
pub speed: Speed,
|
||||
pub length: Distance,
|
||||
pub center_line: Option<PolyLine>,
|
||||
pub allow: Vec<String>,
|
||||
pub allow: Vec<VehicleClass>,
|
||||
}
|
||||
|
||||
pub struct Junction {
|
||||
@ -66,6 +70,16 @@ pub struct Junction {
|
||||
pub junction_type: String,
|
||||
pub pt: Pt2D,
|
||||
pub incoming_lanes: Vec<LaneID>,
|
||||
pub internal_lanes: Vec<LaneID>,
|
||||
pub internal_lanes: Vec<InternalLaneID>,
|
||||
pub shape: Polygon,
|
||||
}
|
||||
|
||||
#[derive(PartialEq)]
|
||||
pub enum VehicleClass {
|
||||
Pedestrian,
|
||||
Bicycle,
|
||||
RailUrban,
|
||||
// TODO Use all values from
|
||||
// https://sumo.dlr.de/docs/Definition_of_Vehicles,_Vehicle_Types,_and_Routes.html#abstract_vehicle_class
|
||||
Other(String),
|
||||
}
|
||||
|
@ -2,27 +2,31 @@
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
use abstutil::{CmdArgs, MapName, Tags, Timer};
|
||||
use anyhow::{bail, Result};
|
||||
|
||||
use abstio::MapName;
|
||||
use abstutil::{CmdArgs, Tags, Timer};
|
||||
use geom::{Distance, PolyLine};
|
||||
use map_model::{
|
||||
osm, raw, AccessRestrictions, Intersection, IntersectionID, IntersectionType, Lane, LaneID,
|
||||
LaneType, Map, Road, RoadID, Turn, TurnID, TurnType,
|
||||
};
|
||||
|
||||
use sumo::{Direction, Network, NodeID};
|
||||
use sumo::{Direction, InternalLaneID, Network, NodeID, VehicleClass};
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<()> {
|
||||
let mut timer = Timer::new("convert SUMO network");
|
||||
let mut args = CmdArgs::new();
|
||||
let input = args.required_free();
|
||||
args.done();
|
||||
|
||||
let network = Network::load(&input, &mut timer).unwrap();
|
||||
let map = convert(&input, network);
|
||||
let map = convert(&input, network)?;
|
||||
map.save();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn convert(orig_path: &str, network: Network) -> Map {
|
||||
fn convert(orig_path: &str, network: Network) -> Result<Map> {
|
||||
let mut intersections = Vec::new();
|
||||
let mut ids_intersections: BTreeMap<NodeID, IntersectionID> = BTreeMap::new();
|
||||
for (_, junction) in network.junctions {
|
||||
@ -33,10 +37,12 @@ fn convert(orig_path: &str, network: Network) -> Map {
|
||||
turns: BTreeSet::new(),
|
||||
elevation: Distance::ZERO,
|
||||
intersection_type: IntersectionType::StopSign,
|
||||
// TODO Temporary ID. We could consider squeezing SUMO IDs into this scheme.
|
||||
orig_id: osm::NodeID(123),
|
||||
incoming_lanes: Vec::new(),
|
||||
outgoing_lanes: Vec::new(),
|
||||
roads: BTreeSet::new(),
|
||||
merged: false,
|
||||
});
|
||||
ids_intersections.insert(junction.id, id);
|
||||
}
|
||||
@ -59,11 +65,11 @@ fn convert(orig_path: &str, network: Network) -> Map {
|
||||
for lane in &edge.lanes {
|
||||
let lane_id = LaneID(lanes.len());
|
||||
ids_lanes.insert(lane.id.clone(), lane_id);
|
||||
let lane_type = if lane.allow == vec!["pedestrian".to_string()] {
|
||||
let lane_type = if lane.allow == vec![VehicleClass::Pedestrian] {
|
||||
LaneType::Sidewalk
|
||||
} else if lane.allow == vec!["bicycle".to_string()] {
|
||||
} else if lane.allow == vec![VehicleClass::Bicycle] {
|
||||
LaneType::Biking
|
||||
} else if lane.allow == vec!["rail_urban".to_string()] {
|
||||
} else if lane.allow == vec![VehicleClass::RailUrban] {
|
||||
LaneType::LightRail
|
||||
} else {
|
||||
LaneType::Driving
|
||||
@ -100,9 +106,10 @@ fn convert(orig_path: &str, network: Network) -> Map {
|
||||
}
|
||||
let parts: Vec<&str> = edge.edge_type.split(".").collect();
|
||||
// "highway.footway"
|
||||
if parts.len() == 2 {
|
||||
tags.insert(parts[0].to_string(), parts[1].to_string());
|
||||
if parts.len() != 2 {
|
||||
bail!("Unknown edge_type {}", edge.edge_type);
|
||||
}
|
||||
tags.insert(parts[0].to_string(), parts[1].to_string());
|
||||
let mut lanes_ltr = lanes_rtl;
|
||||
lanes_ltr.reverse();
|
||||
|
||||
@ -111,6 +118,7 @@ fn convert(orig_path: &str, network: Network) -> Map {
|
||||
osm_tags: Tags::new(tags),
|
||||
turn_restrictions: Vec::new(),
|
||||
complicated_turn_restrictions: Vec::new(),
|
||||
// TODO Temporary ID. We could consider squeezing SUMO IDs into this scheme.
|
||||
orig_id: raw::OriginalRoad::new(123, (456, 789)),
|
||||
speed_limit,
|
||||
access_restrictions: AccessRestrictions::new(),
|
||||
@ -124,14 +132,17 @@ fn convert(orig_path: &str, network: Network) -> Map {
|
||||
dst_i,
|
||||
});
|
||||
} else {
|
||||
// TODO Should we check that the attributes are the same for both directions?
|
||||
// There's an existing road with the forward direction lanes. We're currently
|
||||
// processing the reverse direction of that road, so lanes_rtl oriented in the forwards
|
||||
// direction is already left-to-right.
|
||||
let mut lanes_ltr = lanes_rtl;
|
||||
lanes_ltr.extend(roads[road_id.0].lanes_ltr.clone());
|
||||
// TODO Should we check that the attributes are the same for both directions?
|
||||
roads[road_id.0].lanes_ltr = lanes_ltr;
|
||||
}
|
||||
}
|
||||
|
||||
let mut internal_lane_geometry: BTreeMap<sumo::LaneID, PolyLine> = BTreeMap::new();
|
||||
let mut internal_lane_geometry: BTreeMap<InternalLaneID, PolyLine> = BTreeMap::new();
|
||||
for (_, edge) in network.internal_edges {
|
||||
for lane in edge.lanes {
|
||||
if let Some(pl) = lane.center_line {
|
||||
@ -180,7 +191,7 @@ fn convert(orig_path: &str, network: Network) -> Map {
|
||||
}
|
||||
}
|
||||
|
||||
Map::import_minimal(
|
||||
Ok(Map::import_minimal(
|
||||
// Double basename because "foo.net.xml" just becomes "foo.net"
|
||||
MapName::new("sumo", &abstutil::basename(abstutil::basename(orig_path))),
|
||||
network.location.converted_boundary,
|
||||
@ -189,5 +200,5 @@ fn convert(orig_path: &str, network: Network) -> Map {
|
||||
roads,
|
||||
lanes,
|
||||
turns,
|
||||
)
|
||||
))
|
||||
}
|
||||
|
@ -1,16 +1,19 @@
|
||||
//! Transforms a `raw::Network` into a `Network` that's easier to reason about.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::error::Error;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use abstutil::Timer;
|
||||
use geom::{Distance, PolyLine, Pt2D, Ring};
|
||||
|
||||
use crate::{raw, Edge, InternalEdge, InternalLane, Junction, Lane, Network};
|
||||
use crate::{
|
||||
raw, Edge, InternalEdge, InternalLane, InternalLaneID, Junction, Lane, LaneID, Network,
|
||||
};
|
||||
|
||||
impl Network {
|
||||
/// Reads a .net.xml file and return the normalized SUMO network.
|
||||
pub fn load(path: &str, timer: &mut Timer) -> Result<Network, Box<dyn Error>> {
|
||||
pub fn load(path: &str, timer: &mut Timer) -> Result<Network> {
|
||||
let raw = raw::Network::parse(path, timer)?;
|
||||
timer.start("normalize");
|
||||
let network = Network::from_raw(raw);
|
||||
@ -52,7 +55,7 @@ impl Network {
|
||||
let mut lanes = Vec::new();
|
||||
for lane in edge.lanes {
|
||||
lanes.push(InternalLane {
|
||||
id: lane.id,
|
||||
id: InternalLaneID(lane.id),
|
||||
index: lane.index,
|
||||
speed: lane.speed,
|
||||
length: lane.length,
|
||||
@ -87,7 +90,7 @@ impl Network {
|
||||
let mut lanes = Vec::new();
|
||||
for lane in edge.lanes {
|
||||
lanes.push(Lane {
|
||||
id: lane.id,
|
||||
id: LaneID(lane.id),
|
||||
index: lane.index,
|
||||
speed: lane.speed,
|
||||
length: lane.length,
|
||||
@ -117,6 +120,7 @@ impl Network {
|
||||
network
|
||||
}
|
||||
|
||||
/// Normalize coordinates to map-space, with Y increasing down.
|
||||
fn fix_coordinates(&mut self) {
|
||||
// I tried netconvert's --flip-y-axis option, but it makes all of the y coordinates
|
||||
// extremely negative.
|
||||
|
@ -2,13 +2,13 @@
|
||||
//! subset of the structures and fields defined at
|
||||
//! <https://sumo.dlr.de/docs/Networks/PlainXML.html> are produced.
|
||||
|
||||
use std::error::Error;
|
||||
|
||||
use abstutil::Timer;
|
||||
use serde::Deserialize;
|
||||
|
||||
use geom::{Bounds, Distance, GPSBounds, PolyLine, Polygon, Pt2D, Ring, Speed};
|
||||
|
||||
use crate::VehicleClass;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Network {
|
||||
pub location: Location,
|
||||
@ -31,9 +31,9 @@ pub struct Location {
|
||||
}
|
||||
|
||||
impl Network {
|
||||
pub fn parse(path: &str, timer: &mut Timer) -> Result<Network, Box<dyn Error>> {
|
||||
pub fn parse(path: &str, timer: &mut Timer) -> anyhow::Result<Network> {
|
||||
timer.start(format!("read {}", path));
|
||||
let bytes = abstutil::slurp_file(path)?;
|
||||
let bytes = abstio::slurp_file(path)?;
|
||||
let raw_string = std::str::from_utf8(&bytes)?;
|
||||
let network = quick_xml::de::from_str(raw_string)?;
|
||||
timer.stop(format!("read {}", path));
|
||||
@ -46,7 +46,9 @@ pub struct EdgeID(pub String);
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
|
||||
pub struct NodeID(String);
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
|
||||
pub struct LaneID(String);
|
||||
pub struct LaneID(pub String);
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
|
||||
pub struct InternalLaneID(pub String);
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Type {
|
||||
@ -54,10 +56,10 @@ pub struct Type {
|
||||
pub priority: usize,
|
||||
pub speed: Speed,
|
||||
pub width: Option<Distance>,
|
||||
#[serde(deserialize_with = "parse_list", default)]
|
||||
pub allow: Vec<String>,
|
||||
#[serde(deserialize_with = "parse_list", default)]
|
||||
pub disallow: Vec<String>,
|
||||
#[serde(deserialize_with = "parse_list_vehicles", default)]
|
||||
pub allow: Vec<VehicleClass>,
|
||||
#[serde(deserialize_with = "parse_list_vehicles", default)]
|
||||
pub disallow: Vec<VehicleClass>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -109,17 +111,18 @@ impl std::default::Default for SpreadType {
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Lane {
|
||||
pub id: LaneID,
|
||||
/// This could be a LaneID or an InternalLaneID. It'll be distinguished during normalization.
|
||||
pub id: String,
|
||||
pub index: usize,
|
||||
pub speed: Speed,
|
||||
pub length: Distance,
|
||||
pub width: Option<Distance>,
|
||||
#[serde(deserialize_with = "parse_pl")]
|
||||
pub shape: Result<PolyLine, String>,
|
||||
#[serde(deserialize_with = "parse_list", default)]
|
||||
pub allow: Vec<String>,
|
||||
#[serde(deserialize_with = "parse_list", default)]
|
||||
pub disallow: Vec<String>,
|
||||
pub shape: anyhow::Result<PolyLine>,
|
||||
#[serde(deserialize_with = "parse_list_vehicles", default)]
|
||||
pub allow: Vec<VehicleClass>,
|
||||
#[serde(deserialize_with = "parse_list_vehicles", default)]
|
||||
pub disallow: Vec<VehicleClass>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
@ -131,8 +134,12 @@ pub struct Junction {
|
||||
pub y: f64,
|
||||
#[serde(rename = "incLanes", deserialize_with = "parse_list_lanes", default)]
|
||||
pub incoming_lanes: Vec<LaneID>,
|
||||
#[serde(rename = "intLanes", deserialize_with = "parse_list_lanes", default)]
|
||||
pub internal_lanes: Vec<LaneID>,
|
||||
#[serde(
|
||||
rename = "intLanes",
|
||||
deserialize_with = "parse_list_internal_lanes",
|
||||
default
|
||||
)]
|
||||
pub internal_lanes: Vec<InternalLaneID>,
|
||||
#[serde(deserialize_with = "parse_polygon", default)]
|
||||
pub shape: Option<Polygon>,
|
||||
}
|
||||
@ -150,7 +157,7 @@ pub struct Connection {
|
||||
pub to: EdgeID,
|
||||
#[serde(rename = "toLane")]
|
||||
pub to_lane: usize,
|
||||
pub via: Option<LaneID>,
|
||||
pub via: Option<InternalLaneID>,
|
||||
pub dir: Direction,
|
||||
}
|
||||
impl Connection {
|
||||
@ -217,10 +224,20 @@ fn parse_gps_bounds<'de, D: serde::Deserializer<'de>>(d: D) -> Result<GPSBounds,
|
||||
})
|
||||
}
|
||||
|
||||
fn parse_list<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<String>, D::Error> {
|
||||
fn parse_list_vehicles<'de, D: serde::Deserializer<'de>>(
|
||||
d: D,
|
||||
) -> Result<Vec<VehicleClass>, D::Error> {
|
||||
let raw = <String>::deserialize(d)?;
|
||||
let parts: Vec<String> = raw.split(" ").map(|x| x.to_string()).collect();
|
||||
Ok(parts)
|
||||
let mut vehicles = Vec::new();
|
||||
for x in raw.split(" ") {
|
||||
vehicles.push(match x {
|
||||
"pedestrian" => VehicleClass::Pedestrian,
|
||||
"bicycle" => VehicleClass::Bicycle,
|
||||
"rail_urban" => VehicleClass::RailUrban,
|
||||
other => VehicleClass::Other(other.to_string()),
|
||||
});
|
||||
}
|
||||
Ok(vehicles)
|
||||
}
|
||||
|
||||
fn parse_list_lanes<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<LaneID>, D::Error> {
|
||||
@ -229,6 +246,17 @@ fn parse_list_lanes<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<LaneID
|
||||
Ok(parts)
|
||||
}
|
||||
|
||||
fn parse_list_internal_lanes<'de, D: serde::Deserializer<'de>>(
|
||||
d: D,
|
||||
) -> Result<Vec<InternalLaneID>, D::Error> {
|
||||
let raw = <String>::deserialize(d)?;
|
||||
let parts: Vec<InternalLaneID> = raw
|
||||
.split(" ")
|
||||
.map(|x| InternalLaneID(x.to_string()))
|
||||
.collect();
|
||||
Ok(parts)
|
||||
}
|
||||
|
||||
fn parse_pts<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<Pt2D>, D::Error> {
|
||||
let raw = <String>::deserialize(d)?;
|
||||
let mut pts = Vec::new();
|
||||
@ -238,7 +266,7 @@ fn parse_pts<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Vec<Pt2D>, D::Err
|
||||
Ok(pts)
|
||||
}
|
||||
|
||||
fn parse_pl<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Result<PolyLine, String>, D::Error> {
|
||||
fn parse_pl<'de, D: serde::Deserializer<'de>>(d: D) -> Result<anyhow::Result<PolyLine>, D::Error> {
|
||||
let pts = parse_pts(d)?;
|
||||
Ok(PolyLine::new(pts))
|
||||
}
|
||||
@ -259,14 +287,14 @@ fn parse_polygon<'de, D: serde::Deserializer<'de>>(d: D) -> Result<Option<Polygo
|
||||
))
|
||||
}
|
||||
|
||||
fn parse_pt(pt: &str) -> Result<Pt2D, Box<dyn Error>> {
|
||||
fn parse_pt(pt: &str) -> anyhow::Result<Pt2D> {
|
||||
let mut parts = Vec::new();
|
||||
for x in pt.split(",") {
|
||||
parts.push(x.parse::<f64>()?);
|
||||
}
|
||||
// Ignore the Z coordinate if it's there
|
||||
if parts.len() != 2 && parts.len() != 3 {
|
||||
return Err("not 2 or 3 parts".into());
|
||||
bail!("not 2 or 3 parts");
|
||||
}
|
||||
Ok(Pt2D::new(parts[0], parts[1]))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user