sumo-types 0.1.0

Reads SUMO (Eclipse SUMO) .net.xml and .rou.xml files into well-typed Rust structs
Documentation
//! Layer 2: this crate's own domain types.
//!
//! These types are independent of SUMO/XSD (unlike the private `schema`
//! module, layer
//! 1, which is an almost literal mirror of the `.xsd` files) and are
//! produced from it by [`crate::schema_mapper`]. Consumers of this crate
//! should work on these types, not on the ones generated by xsd-parser.

use derive_more::{AsRef, Display, From};
use uom::si::f64::{Length, Velocity};
use uom::si::length::meter;

/// SUMO's default lane width (`SUMO_const_laneWidth`), used by netconvert
/// whenever a lane's `width` attribute is omitted from `.net.xml`.
///
/// This is the value SUMO itself has used across recent versions; it isn't
/// derived from anything in this repo, so double-check it against the SUMO
/// version producing your input if precision matters.
#[must_use]
pub fn default_lane_width() -> Length {
    Length::new::<meter>(3.2)
}

/// SUMO's default lane end offset: `0`, meaning the lane extends the full
/// length of its edge. Used whenever a lane's `endOffset` attribute is
/// omitted from `.net.xml`.
#[must_use]
pub fn default_lane_end_offset() -> Length {
    Length::new::<meter>(0.0)
}

/// Identifier of an [`Edge`]. A distinct type from a bare `String` so it
/// can't be passed where a [`LaneId`] is expected — a mistake that would
/// silently compile (and silently misbehave, e.g. as a wrong `HashMap`
/// lookup) if every id were just a `String`.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display, From, AsRef)]
pub struct EdgeId(#[as_ref(forward)] pub String);

/// Identifier of a [`Lane`]. See [`EdgeId`] for why this isn't a bare `String`.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display, From, AsRef)]
pub struct LaneId(#[as_ref(forward)] pub String);

/// Identifier of a [`Junction`]. See [`EdgeId`] for why this isn't a bare `String`.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display, From, AsRef)]
pub struct JunctionId(#[as_ref(forward)] pub String);

/// Identifier of a traffic light program (`tlLogic`). See [`EdgeId`] for why
/// this isn't a bare `String`.
#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Display, From, AsRef)]
pub struct TrafficLightId(#[as_ref(forward)] pub String);

/// Index of a [`Lane`] within its parent [`Edge`] (`0` is the rightmost lane
/// in SUMO's convention). A distinct type from a bare `usize` so it can't be
/// confused with unrelated counts (e.g. a traffic light link index).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display, From)]
pub struct LaneIndex(pub usize);

/// Relative right-of-way ranking of an [`Edge`] (higher wins), derived by
/// netconvert from the road classification. Can be negative (e.g. `-1` for
/// internal/connector edges). A distinct type from a bare `i32` so it can't
/// be confused with unrelated integers (e.g. a traffic light link index).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display, From)]
pub struct Priority(pub i32);

/// Index of a [`Connection`] within its traffic light's signal group
/// (`tlLogic/@state` / `phase/@state` character position). A distinct type
/// from a bare `i32` so it can't be confused with unrelated integers (e.g.
/// an edge's [`Priority`]).
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display, From)]
pub struct LinkIndex(pub i32);

/// A point in the network's coordinate system (meters), with an optional
/// `z` represented here as `0.0` by default.
///
/// [`Default`] is the origin.
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Point {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

/// Polyline that defines the geometry of an edge, lane, or junction.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Shape(pub Vec<Point>);

/// Bounding box (bottom-left corner, top-right corner).
///
/// [`Default`] is the degenerate box at the origin.
#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub struct Boundary {
    pub min: (f64, f64),
    pub max: (f64, f64),
}

/// How a network's coordinates relate to real-world geographic coordinates,
/// as declared by SUMO's `location/@projParameter`.
#[derive(Debug, Clone, Default, PartialEq)]
pub enum Projection {
    /// SUMO's `"!"` sentinel: the network isn't georeferenced, its
    /// coordinates are already local/Cartesian. Also the [`Default`]:
    /// assuming plain Cartesian coordinates is the assumption that can't
    /// silently misplace a network on the globe.
    #[default]
    None,
    /// A PROJ definition string, e.g. `"+proj=utm +zone=32 +ellps=WGS84"`.
    Proj4(String),
}

/// Geographic metadata of the network, as exported by netconvert. Unlike
/// the XSD (which marks these attributes optional), a real `netconvert`
/// output always populates all of them, so a missing one here is treated as
/// a malformed input rather than modeled with `Option`.
///
/// [`Default`] is a non-georeferenced network at the origin with an empty
/// bounding box — what an empty `.net.xml` would describe. It exists so
/// [`Network`] can keep its own [`Default`] for test fixtures
/// (`Network { edges, ..Default::default() }`) now that `location` is no
/// longer an `Option`; it is not something [`crate::read_network`] ever
/// produces.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Location {
    pub net_offset: Point,
    pub converted_boundary: Boundary,
    pub original_boundary: Boundary,
    pub projection: Projection,
}

/// The role an edge plays within the network.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeFunction {
    Normal,
    Internal,
    Connector,
    Crossing,
    Walkingarea,
}

/// How the edge's width is distributed relative to its axis.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SpreadType {
    Center,
    RoadCenter,
    Right,
}

/// A lane within an edge.
#[derive(Debug, Clone, PartialEq)]
pub struct Lane {
    pub id: LaneId,
    pub index: LaneIndex,
    pub speed: Velocity,
    pub length: Length,
    pub width: Length,
    pub end_offset: Length,
    pub shape: Shape,
    pub allow: Vec<String>,
    pub disallow: Vec<String>,
}

/// A directed edge (arc) of the network.
#[derive(Debug, Clone, PartialEq)]
pub struct Edge {
    pub id: EdgeId,
    pub function: EdgeFunction,
    pub from: Option<JunctionId>,
    pub to: Option<JunctionId>,
    pub name: Option<String>,
    pub priority: Option<Priority>,
    pub length: Option<Length>,
    pub shape: Option<Shape>,
    pub spread_type: Option<SpreadType>,
    pub lanes: Vec<Lane>,
}

/// The kind of right-of-way control a junction has (traffic light, priority,
/// yield, ...).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum JunctionKind {
    TrafficLight,
    TrafficLightUnregulated,
    TrafficLightRightOnRed,
    RailSignal,
    RailCrossing,
    Priority,
    PriorityStop,
    LeftBeforeRight,
    RightBeforeLeft,
    AllWayStop,
    Zipper,
    District,
    Unregulated,
    Internal,
    DeadEnd,
}

/// A network node where one or more edges meet.
#[derive(Debug, Clone, PartialEq)]
pub struct Junction {
    pub id: JunctionId,
    pub position: Point,
    pub kind: JunctionKind,
    pub incoming_lanes: Vec<LaneId>,
    pub internal_lanes: Vec<LaneId>,
    pub shape: Option<Shape>,
    pub name: Option<String>,
}

/// Turning direction of a connection, following SUMO's convention
/// (`SUMOXMLDefinitions::LinkDirection`).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConnectionDirection {
    Straight,
    Turn,
    TurnLeftHand,
    Left,
    Right,
    PartialLeft,
    PartialRight,
}

/// Right-of-way state of a connection, following SUMO's convention
/// (`SUMOXMLDefinitions::LinkState`) for the `state` attribute in `.net.xml`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LinkState {
    /// Has the right of way ("major").
    Major,
    /// Does not have the right of way ("minor"), must yield.
    Minor,
    /// Controlled by a traffic light that is off, with no visible signal.
    TlsOffNoSignal,
    /// Controlled by a traffic light that is off, in blinking mode.
    TlsOffBlinking,
    /// Shared right of way (everyone yields equally).
    Equal,
    /// Must come to a full stop.
    Stop,
    /// "All-way stop" style intersection.
    AllWayStop,
    /// "Zipper" merge.
    Zipper,
    /// No real connection (dead end).
    DeadEnd,
}

/// A connection between two lanes (possibly from different edges).
#[derive(Debug, Clone, PartialEq)]
pub struct Connection {
    pub from_edge: EdgeId,
    pub to_edge: EdgeId,
    pub from_lane: LaneIndex,
    pub to_lane: LaneIndex,
    pub direction: ConnectionDirection,
    pub state: LinkState,
    pub via: Option<LaneId>,
    pub traffic_light: Option<TrafficLightId>,
    pub link_index: Option<LinkIndex>,
    pub pass: bool,
    pub keep_clear: bool,
}

/// A roundabout in the network.
#[derive(Debug, Clone, PartialEq)]
pub struct Roundabout {
    pub nodes: Vec<JunctionId>,
    pub edges: Vec<EdgeId>,
}

/// A traffic light program (`tlLogic`): an ordered list of phases, each
/// contributing its `state` string. The character at index `i` of every
/// phase governs the [`Connection`] whose `link_index == i` — two links
/// that always share the same character, in every phase, are controlled by
/// the same physical signal head and should be treated as one group, even
/// if SUMO assigns them different `link_index`es.
///
/// A `tlLogic` id can appear more than once in `.net.xml` (multiple
/// programs, e.g. a normal one and a night-time one); which one is "the"
/// program for a junction is left to the caller — this type only models a
/// single program as parsed.
#[derive(Debug, Clone, PartialEq)]
pub struct TrafficLightProgram {
    pub id: TrafficLightId,
    pub phases: Vec<String>,
}

/// Complete model of a SUMO network (`.net.xml`), already converted to the
/// project's own types and decoupled from the original XSD schema.
///
/// [`Default`] is an empty network with a default [`Location`], which is
/// useful for building test fixtures but is never what
/// [`crate::read_network`] returns: `net_file.xsd` requires `<location>`
/// (`minOccurs="1"`), so a parsed `Network` always carries the real one.
#[derive(Debug, Clone, Default, PartialEq)]
pub struct Network {
    /// Always present: `net_file.xsd` declares `<location>` as
    /// `minOccurs="1"`, so a `.net.xml` without one fails to parse rather
    /// than yielding a `Network` with no location.
    pub location: Location,
    pub edges: Vec<Edge>,
    pub junctions: Vec<Junction>,
    pub connections: Vec<Connection>,
    pub roundabouts: Vec<Roundabout>,
    pub traffic_light_programs: Vec<TrafficLightProgram>,
}