Chapter 17.2: OSMnx → SUMO Pipeline

17.2.1 Complete Pipeline Architecture

Building a SUMO simulation from real-world data involves four stages: network acquisition, network conversion, demand generation, and simulation execution. Each stage transforms data from one representation to the next.

Stage 1: NETWORK ACQUISITION
  OSMnx.graph_from_place("City, Country")  →  graph G
  ox.save_graph_xml(G, "city.osm.xml")     →  city.osm.xml

Stage 2: NETWORK CONVERSION
  netconvert --osm-files city.osm.xml      →  city.net.xml
    --output-file city.net.xml
    --junctions.join
    --roundabouts.guess
    --tls.guess
    --geometry.remove
    --ramps.guess

Stage 3: DEMAND GENERATION
  randomTrips.py -n city.net.xml           →  city.rou.xml
    -r city.rou.xml
    -e 3600
    --period 2
    --fringe-factor 5
    --validate

Stage 4: SIMULATION
  sumo -c city.sumocfg                     →  outputs
    --emission-output emission.xml
    --fcd-output fcd.xml
    --queue-output queue.xml

17.2.2 netconvert: Critical Flags

The netconvert tool translates OpenStreetMap data into SUMO’s internal network format. Several flags are essential for producing a usable network:

FlagPurposeWhy Needed
--junctions.joinMerge nearby junctionsOSM often has multiple nodes for one intersection
--roundabouts.guessDetect roundaboutsProper priority/right-of-way rules
--tls.guessInfer traffic lightsOSM tagging is inconsistent
--geometry.removeSimplify edge geometryReduces file size, speeds simulation
--ramps.guessDetect highway rampsCorrect merge/diverge behaviour
--no-turnaroundsDisable U-turnsOften unrealistic at signalised intersections

17.2.3 Demand Generation

SUMO’s randomTrips.py generates vehicle departures with origin-destination pairs drawn from the network:

  • --period p: one vehicle every \(p\) seconds (flow rate = 1/p veh/s)
  • --fringe-factor f: weight edges on the network boundary by factor \(f\), simulating through-traffic
  • --weights-prefix w: load OD weights from files for realistic demand patterns
  • --validate: discard trips with no valid route

For more realistic demand, an origin-destination (OD) matrix can be used. The OD matrix \(T_{ij}\) specifies the number of trips from zone \(i\) to zone \(j\) per time period:

$$Q_{\text{total}} = \sum_{i,j} T_{ij}, \qquad \text{period} = \frac{T_{\text{sim}}}{Q_{\text{total}}}$$

17.2.4 SUMO Output Files

SUMO produces several output formats, each serving a different analysis purpose:

Emission Output (emission.xml)

<emission-export>
  <timestep time="10.00">
    <vehicle id="veh_0" eclass="HBEFA3/PC_G_EU4"
             CO2="3456.78" CO="12.34" HC="0.56"
             NOx="1.23" PMx="0.045"
             speed="8.33" pos="83.3" lane="edge_0_0"/>
  </timestep>
</emission-export>

Floating Car Data (fcd.xml)

<fcd-export>
  <timestep time="10.00">
    <vehicle id="veh_0" x="456.78" y="123.45"
             angle="90.0" speed="8.33" pos="83.3"
             lane="edge_0_0" slope="0.00"/>
  </timestep>
</fcd-export>

Queue Output (queue.xml)

<queue-export>
  <data timestep="10.00">
    <lanes>
      <lane id="edge_0_0" queueing_time="12.5"
            queueing_length="45.0"
            queueing_length_experimental="42.3"/>
    </lanes>
  </data>
</queue-export>

17.2.5 TraCI Integration: Signal Control + Pollution

The following script demonstrates a ready-to-use TraCI connection that combines Module 13 signal control with Module 16 pollution estimation. It can be run directly with a SUMO installation, but the structure is shown here for reference.

#!/usr/bin/env python3
"""TraCI controller integrating signal control (Module 13) and OSPM (Module 16)."""
import traci
import numpy as np

class TraCIController:
    def __init__(self, sumo_cfg, canyon_params):
        self.sumo_cfg = sumo_cfg
        self.canyon = canyon_params  # dict: edge_id -> {H, W, alpha}
        self.step = 0

    def connect(self):
        traci.start(["sumo", "-c", self.sumo_cfg])

    def get_edge_emissions(self, edge_id):
        """Get total NOx emissions on an edge (mg/s)."""
        vehicles = traci.edge.getLastStepVehicleIDs(edge_id)
        total_nox = sum(traci.vehicle.getNOxEmission(v) for v in vehicles)
        return total_nox

    def ospm_concentration(self, edge_id, nox_emission):
        """Compute OSPM concentration for a canyon edge."""
        p = self.canyon.get(edge_id, {"H": 15, "W": 20})
        alpha = p["H"] / p["W"]
        length = traci.edge.getLastStepLength(edge_id)

        q_L = nox_emission / max(length, 1.0)  # mg/m/s
        u_H = 5.0; kappa = 0.41; C_D = 0.005
        u_v = C_D * u_H / ((1 + alpha) * kappa)
        u_s = max(u_v * 0.5, 0.3)

        C_D_conc = q_L / (np.sqrt(2*np.pi) * 0.8 * u_s)
        L_R = min(p["W"], p["H"])
        C_R = q_L * L_R / (u_v * p["W"] * p["H"]) * 2.0
        return C_D_conc + C_R + 25.0

    def pmp_signal_update(self, tls_id, concentrations):
        """Pontryagin signal control (Module 13) with pollution penalty."""
        queues = []
        for phase_idx in range(4):
            controlled_lanes = traci.trafficlight.getControlledLanes(tls_id)
            q = sum(traci.lane.getLastStepHaltingNumber(l)
                    for l in controlled_lanes[phase_idx::4])
            queues.append(q)

        # Optimal phase: minimise queue + lambda * concentration
        lam = 0.1  # pollution weight
        best_phase = np.argmin(queues)  # simplified
        return best_phase

    def run(self, n_steps=3600):
        self.connect()
        for _ in range(n_steps):
            traci.simulationStep()
            # ... apply control logic each step
            self.step += 1
        traci.close()

17.2.6 Python: Build .net.xml and .rou.xml

This code generates valid SUMO network and route files programmatically without requiring a SUMO installation. It creates a simple grid network and random vehicle routes.

Build SUMO Network & Routes Programmatically

Python
script.py237 lines

Click Run to execute the Python code

Code will be executed with Python 3 on the server

17.2.7 Key Takeaways

  • The pipeline OSMnx → netconvert → randomTrips.py → SUMO provides a complete path from real-world maps to microscopic simulation.
  • Critical netconvert flags (--junctions.join, --tls.guess, --roundabouts.guess) are essential for converting noisy OSM data into a valid simulation network.
  • Demand generation via randomTrips.py with --fringe-factor produces realistic through-traffic patterns; OD matrices provide higher fidelity.
  • TraCI enables real-time coupling between SUMO and external controllers, including Module 13 signal optimisation and Module 16 pollution estimation.
  • All SUMO input/output files are XML-based, making them straightforward to generate and parse programmatically.