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:
Dustin Carlino 2021-01-05 12:42:40 -08:00
parent 8436b5358e
commit 6f5f001406
8 changed files with 112 additions and 50 deletions

2
Cargo.lock generated
View File

@ -3763,7 +3763,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
name = "sumo"
version = "0.1.0"
dependencies = [
"abstio",
"abstutil",
"anyhow",
"geom",
"map_model",
"quick-xml",

View File

@ -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;

View File

@ -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"] }

View File

@ -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:

View File

@ -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),
}

View File

@ -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,
)
))
}

View File

@ -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.

View File

@ -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]))
}