This documentation is in the process of turning into a proper website.

This homepage should definitely have:

  • our 3 principles
  • lots of videos
  • contact info
  • contributing


Our main software is A/B Street, but along the way, we've split out a few related side projects. A/B Street has a huge scope, so over time, we'd like to carve out more smaller pieces from it.

The main projects:

  • A/B Street for running a traffic simulation, editing roads and intersections, and measuring the impact of your changes. It has game-like elements, but leans more on the side of being a real prototyping tool.
  • 15-minute neighborhood explorer for seeing where people live in relation to commercial and public amenities.
  • 15-minute Santa, a light-hearted arcade game to demonstrate the concept of a 15-minute neighborhood.

Some dedicated tools for the OpenStreetMap community:

Some pieces of the code-base are eventually destined to be stand-alone and useful for other projects:

  • widgetry Rust UI library for native/web
  • a common library for transforming OpenStreetMap key/values to a clear schema of lanes

And some planned projects:

A/B Street


A/B Street is "a traffic simulation game exploring how small changes to roads affect cyclists, transit users, pedestrians, and drivers." In other words, you can transform that street parking into a bus lane or fix that pesky left turn at a traffic signal, measure the effects, then propose actually making the change.

A/B Street uses game-like elements to gradually introduce all of the features of the simulation, with a tutorial and a few challenge modes. (Or at least, that's the goal.) But just becaused it's called a "game" doesn't mean it's not trying to model the real world as accurately as possible from open data.

Keep in mind it's impossible to simulate all complexity in the real world of people moving around a city. Every traffic model makes lots of assumptions and trade-offs, including A/B Street.


A/B Street gives you a 2D representation of roads, with as much detail about bus/bike/turn/parking lanes, transit stops, traffic signals, and parking lots as possible, all from OpenStreetMap.

(map model pictures)

Depending on elevation data availability, some areas let you visualize steep streets -- because your bike network should be planned accordingly.

(two elevation pictures)

Using external travel demand models, you can explore patterns of where people live, work, and shop.

(commuter layer picture)


Using some external data about what trips people take on a typical day, you can simulate drivers, bicyclists, and pedestrians moving around. (Public transit and scooter/bikeshare micromobility planned.)

(video of one intersection)

You can follow individual people and watch them wrestle with problems.

(follow video)

Or get a bird's-eye view of how everything is moving.

(unzoomed video)

Every driving trip begins and ends with parking, which might be easy in suburban areas, but hard in cities.


Some details:

  • People will take the best route available, not accounting for current congestion. Drivers prefer the fastest route (shorter distances, higher speed limits). Pedestrians and cyclists also factor in elevation changes.
  • Vehicles instantly accelerate and stop. This is a useful simplification to speed up the simulation, but of course, you shouldn't use A/B Street to simulate jam waves on freeways.
  • Vehicles stay in the same lane once they pick it, and sometimes they choose their lane really poorly. We're working on improving this.
  • We don't simulate accidents -- but we do measure "risk exposure" where a collision may be likely.


A/B Street lets you change how road space is divided up. You can create regular vehicle lanes, bus-only lanes, bike lanes, street parking, and new sidewalks. You can either transform existing lanes, or slightly widen/shrink the road. You can reverse the direction of lanes, or close them down to simulate construction.


You can also change speed limits and restrict access to an area. Only trips that start or end in a private area can use the roads within, modelling gated communities. Or you can allow through-traffic for people walking and biking, to create low-traffic neighborhoods.


You can also change how traffic signals work. You can modify the timing if you notice left turns need more time, or set up actuated timing, so that the green light lasts longer when many vehicles are waiting. You can also change which movements are allowed each stage, so you could try out a dedicated left turn stage or a pedestrian all-walk/scramble cycle.



A/B Street lets you tell data-driven stories about both infrastructure and people

stories you can tell

individ/aggregate absolute/relative

  • go through all of the layers
  • and dashboards

Not to crush your dreams, but...

gridlock cant do A/B tests if you cant finish trips

mode shift!

15-minute neighborhood explorer


  • Web version
  • To run locally, get the latest release for Windows, Mac, or Linux. After unzipping, run fifteen_min.exe or fifteen_min.

In a 15-minute city, most residents can reach a variety of stores, restaurants, parks, and jobs within about a 15 minute walk or bike ride. Part of advocating for a bike-friendly city is evaluating the zoning laws and understanding where people live in relation to where they work, shop, and meet friends. This tool is a prototype letting you see what's 15 minutes away from a starting point. You can explore the shops and estimate how much street parking and how many people live within a walkshed.


The tool is quite simple right now. If you have an idea how this could be used for advocacy, please get in touch.


This section describes some of the technical implementation details.

Calculating time to buildings

When you click on a starting building, what happens? Follow along with the code: https://github.com/a-b-street/abstreet/blob/master/fifteen_min/src/isochrone.rs

First, we calculate the time to reach all buildings on the map from the start, based on the current options for walking or biking. For walking, this calls all_walking_costs_from. This method simply performs Dijkstra's maneuever, floodfilling a graph from the start node and tracking the cost to reach other nodes. It terminates early when the cost exceeds 15 minutes.

One relevant detail is that the graph is based off of WalkingNodes, which represent both endpoints of a sidewalk. A building exists some distance along a sidewalk. To calculate the final cost to a building, we also add up the cost between the nearest end of the sidewalk and the building's exact position along the sidewalk. This calculation doesn't use the length of the building's driveway/front path, but it could.

The other question is how we calculate the cost to cross a sidewalk. The units are durations, and most of the time, we simply calculate distance / speed. The base speed for walking can be configured in the 15m tool UI, with a few pre-set values for jogging, using a wheelchair, or a general average for adults. We also adjust speed based on the road's incline, using a variation of Tobler's hiking function. The elevation data baked into the map model likely has bugs, which then affect walking speed.

In the future, we'll keep expanding this definition of "cost" to capture other factors that make it safe and pleasant to walk, so that the 15m tool ultimately reflects how awful it'd be to access essential services by walking along a busy arterial road.

Drawing the isochrone

Now that we have the cost to reach a bunch of buildings, we want to draw the three-colored isochrone, showing the area reachable within 5, 10, and 15 minutes. We use the contour crate to do this, which uses the marching squares algorithm to find the "contour" where the cost changes from less than 5 to over 5 minutes.

In my mind, an isochrone contour algorithm could just take the list of (point, cost) pairs as input, but marching squares requires a grid, so the code first creates that grid, using a fixed 100 meter cell size. If two buildings happen to map to the same square, the cost of one of them is used arbitrarily. It shouldn't matter much; it takes around 30 seconds to walk 100 meters, so the contour might be a little off by about that much.


You can see some gaps in the middle of the park. Because there are no buildings in the middle, the grid cell has 0 cost in there. There's probably some better approaches to calculating isochrones.

Finding the perfect home

The tool also has a feature that lets you mark what categories of amenities you care about, then it finds houses within a 15 min walk of all of those. Its implementation is laughably brute-force. For each of the categories, it finds all stores matching that category, then does the Dijkstra's floodfill from each of those buildings. An obvious speedup here would be to perform Dijkstra's just once for each category and insert all of the stores into the priority queue as starting nodes.

15-minute Santa

Created by Dustin Carlino, Yuwen Li, & Michael Kirk

15-minute Santa is a game where you deliver presents across Seattle. You earn more points delivering to high-density housing, and you need to refuel from shops, so you'll have to understand where people live in relation to where they work and shop.

Contact dabreegster@gmail.com with any feedback or file an issue on Github.

Play it

Unzip, then run santa.exe or santa. No mobile/tablet support, sorry -- you need a keyboard.


Why did y'all make this?

We normally work on A/B Street, a traffic simulation that lets the general public explore a future prioritizing more sustainable modes of transportation. All of the recent talk about 15-minute cities prompted us to explore how Seattle's zoning causes many people to live far from where they get groceries. After experimenting with a more serious tool to understand walk-sheds, we decided to spend a few weeks on something a bit more light-hearted.


The map of Seattle and location of shops comes from OpenStreetMap. We only consider shops if they sell food or drinks -- let us know if the map seems to be missing your favorite restaurant. The number of housing units is based on Seattle GIS data. Mixed-use buildings with both commercial and residential units aren't represented. The game lets you upzone any house to place a new store; obviously this is a vast simplification of how complex a real conversation about changing zoning codes should be.

We rigorously evaluated the speed and carrying capacity of different cargo bikes and sleighs on the market to tune the vehicles in the game.

Modding the game

Native versions only -- sorry, not easy to do on the web.

You can adjust the difficulty of the levels or give yourself all the upzoning power you want by editing data/player/santa.json. You first have to set "enable_modding": true. The format should mostly be self-explanatory; also see here as a reference. If you break something, just delete the file to start over. If you come up with a better gameplay progression, please share -- tuning a game is hard!

Adding new maps

Missing your slice of Seattle, or want to run somewhere else? If you have a bit of technical experience, follow this guide and then the above instructions for modding the game. Otherwise, draw the map boundaries in http://geojson.io and send it to us along with a time limit, goal, and starting point on the map. If you have a public data source for the number of housing units per building, please include it!

OpenStreetMap viewer

  • Web version
  • To run locally, get the latest release for Windows, Mac, or Linux. After unzipping, run osm_viewer.exe or osm_viewer.

A separate tool that visualizes OpenStreetMap data, with details on lanes, turn restrictions, parking lot capacity, and road width. It's also very convenient to explore the raw attributes on roads. The OSM Connect 2020 talk talks about possible directions for this viewer.

Mapping on-street parking in OpenStreetMap


This guide assumes you've edited OSM before. Contact dabreegster@gmail.com if you have any trouble. Also give me a heads up when you make some edits, so I can regenerate the maps!

  1. Install A/B Street
  2. Choose Contribute parking data on the main screen
  3. Change the map if you'd like to focus somewhere in particular
  4. Click a road with unknown parking
  5. Select what kind of on-street parking the road has
  6. Repeat
  7. Click Generate OsmChange file
  8. Upload the diff.osc file by adding a layer in JOSM (or send it to me)

Like all edits to OSM, to figure out ground-truth, you can survey in-person or use Bing Streetside. Do not use data from Google Maps to edit OSM.



I'm trying to build a realistic traffic simulation of Seattle using OSM data, then use it to strengthen proposals for pedestrianized streets, improving the bike network, and mitigating the West Seattle bridge closure. A/B Street is only as good as its data, and parking is one of the biggest gaps. Missing data means unrealistic traffic as vehicles contend for few parking spots, and roads that look much wider than they are in reality.

Why put this data in OSM?

Why can't I just grab parking data from SDOT's map, using the blockface dataset? Well, I'm trying -- when you see a parking lane in the tool, it's coming from blockface, unless that road in OSM is tagged. But the blockface dataset is comically wrong in many places -- for example, the Montlake bridge apparently has unrestricted parking?! King County GIS has confirmed the dataset isn't meant to be used for this level of detail.

Plus, if the data is in OSM, anybody else can make use of it.

How does the tool work?

A/B Street attempts to render individual lanes and intersections from OSM data. This makes it useful to audit the lane tags in OSM, including parking:lane. The tool tracks your edits and when you generate the OsmChange file, it grabs modified ways from the OSM API to generate a diff. You can inspect the diff, load it in JOSM, and upload.

Your changes won't immediately be reflected in A/B Street. Let me know when you've done some amount of mapping, and I'll regenerate the maps from fresh data.

Why use this tool?

You don't have to; this tool or ID or JOSM all work. But the UI is clunky for this specific purpose. (Also, if you find this tool clunky in any way, let me know and I'll fix it.) There's also a proposed StreetComplete quest.

What about parking restrictions?

There are many parking:lane tags to indicate restricted parking zones, time restrictions, etc. Feel free to map that in ID or JOSM, but I'm just looking to make a first pass over a wide area.

What about off-street parking?

Ideally I'd also like to know how many private parking spots are available to residents of each building. But I don't know of an OSM schema for mapping this, or a practical way to collect this data. Let me know if you have ideas.

What about long roads where parking appears and disappears?

The tool won't help. Use your favorite editor to split the way when the lane configuration changes. Also feel free to just skip these areas.

How to coordinate with other mappers?

If somebody wants to set up HOT tasking, that'd be great, but I don't expect so many people to jump on this.

I noticed weird roads in the tool

Welcome to my world. ;) If the number of lanes seems wrong, select the road and check the OSM tags. I'm inferring lanes from that. Feel free to make manual OSM edits to fix any problems you see. (I'd like to extend this tool to make that easier; let me know if you have ideas how to do this.)

I want to map an area, but there's no option for it

To keep the release size small, I'm not including all maps yet. Let me know what you'd like to see included.

Or if you have a .osm file, try the quick start guide.

User guide

This is an alpha-quality demo. Please email dabreegster@gmail.com or file a Github issue if you hit problems.

Installing the game

Grab a pre-built binary release -- updated every Sunday, announced at r/abstreet:

  • Windows
    • Unzip the folder, then run play_abstreet.bat. If you get a warning about compressed files, choose to extract -- you can't run from the .zip directly.
    • If you get a "Windows protected you" security warning, click "more info", then "run anyway." We don't sign the release yet, so A/B Street shows up as an unknown publisher.
  • Mac
    • Unzip the directory, then run play_abstreet.sh.
    • If you get an error about the developer unverified, follow this. Help needed to start signing the release!
    • If that just opens a text file instead of running the game, then instead open terminal, cd to the directory you just unzipped. Then do: cd game; RUST_BACKTRACE=1 ./game 1> ../output.txt 2>&1
    • Help needed to package this as a Mac .app, to make this process simpler
  • Linux
    • Unzip the directory, then run play_abstreet.sh.
  • FreeBSD, thanks to Yuri

Or you can play directly in your web browser -- some things don't work as well, but no install required.

Or you can compile from source.

Playing the game

  • Use the tutorial to learn the controls.
  • Play the challenges for directed gameplay.
  • Try out any ideas in the sandbox.

Common issues

If the size of text and panels seems very strange, you can try editing play_abstreet.sh or play_abstreet.bat and passing --scale_factor=1 on the command line. This value is detected from your monitor settings, so if you have a Retina or other HiDPI display, things may be too big or small.

Data source licensing

A/B Street binary releases contain pre-built maps that combine data from:

Other binary data bundled in:

Importing a new city into A/B Street

If you get stuck, please email dabreegster@gmail.com or file a Github issue. I promise quick turnaround time. All you need to do is send the boundary of the area you want, drawn with geojson.io.

The easy method

This only works in the downloaded version, not on web.

  1. Click the map name in sandbox mode to change your location.
  2. Scroll down and click "import a new city"
  3. Follow the instructions. That's it!

This may take a few minutes, depending on download speed.

Advanced: Using the command-line

The process above using the UI just calls a tool to do all of the work. If you want, you can just call that tool from the command line. First save a GeoJSON file with your boundary. Then...

Using a .zip release: ./tools/one_step_import boundary.geojson

Building from source: cargo build --release --bin importer --bin one_step_import --bin geojson_to_osmosis --bin pick_geofabrik --bin clip_osm && ./target/release/one_step_import boundary.geojson

The new map is located in the country with code "zz" (this isn't a real country), in the "oneshot" city. This is stored in data/system/zz/oneshot/maps.

Advanced: Adding the city to A/B street permanently

The easiest method is to just ask Dustin to do this. The full process:

  1. Make sure you can run import.sh -- see the instructions. You'll need Rust, osmconvert, gdal, etc.

  2. Create a new directory: mkdir importer/config/xy/your_city, where xy is a lowercase two letter country code from https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2

  3. Use geojson.io or geoman.io to draw a boundary around the region you want to simulate and save the geojson locally.

  4. Use cargo run --bin geojson_to_osmosis < boundary.geojson to convert that geojson to the Osmosis format required by osmconvert. This tool writes one file per feature in the input, so you'd then mv boundary0.poly importer/config/xy/your_city/region_name.poly, repeating if you drew multiple polygons.

  5. Copy importer/config/il/tel_aviv/cfg.json to importer/config/xy/your_city/cfg.json and edit this file. See here for details on the different fields. The defaults are a reasonable start; the only thing you need to change is osm_url. You can use pick_geofabrik to figure out this URL.

  6. Run the import: ./import.sh --city=xy/your_city --raw --map

  7. Update .gitignore, following tel_aviv as an example. Keep sorted!

  8. Fill out nice_map_name in map_gui/src/tools/mod.rs.

Send a PR with your changes! I'll generate everything and make it work with updater, so most people don't have to build everything from scratch.

Also, you can divide the city into multiple regions, repeating step 4 and declaring more polygon boundaries. The boundaries may overlap each other, and they don't have to cover all of the space. Picking good boundaries may take trial-and-error; the goal is to keep the resulting map file size small, so that it loads quickly, while capturing all of the area needed to simulate something interesting. This is easiest when you have some local knowledge of the area, and at least a vague goal in mind for what you want to study.

Advanced: Importing a .osm file directly

This section assumes you're comfortable working on a command line. If you have a .osm XML file, you can import it directly by running ./import.sh --oneshot=/path/to/extract.osm. If you're running from a .zip release and not building from source, replace the first part with ./tools/importer.

Assuming this succeeds, it'll create a file called data/system/zz/oneshot/maps/extract.bin. Currently the UI won't list this file, so you have to launch the game pointed at this file directly; see here for how to do that.

If you save a .osm file from JOSM, you might get an error importing related to convert_osm/src/clip.rs. If so, delete the <bounds> element from the top of the .osm file and try again.

If you follow this process, the resulting map won't have any border intersections, which will break parts of the simulation:


You can fix this by creating the Osmosis .poly file and passing --oneshot_clip=/path/to/clip.poly to the import command.

One use case for following this section is to temporarily work around broken intersection geometry. The process is:

  1. Edit the problematic area in JOSM, recreating a complicated intersection in a simpler way.
  2. Save the .osm file locally
  3. Run the importer
  4. Try in A/B Street

You probably don't want to upload the changeset to OSM, unless it's actually mis-tagged. Usually the problem is how A/B Street tries to interpret what's in OSM. Ideally we could also follow this process using the ID editor, but it can't currently manage changeset files fully.

Next steps

OpenStreetMap isn't the only data source we need. If you look at the import pipeline for Seattle, you'll see many more sources for parking, GTFS bus schedules, person/trip demand data for scenarios, etc. Most of these aren't standard between cities. If you want to make your city more realistic, we'll have to import more data. Get in touch.

You may notice issues with OSM data while using A/B Street. Some of these are bugs in A/B Street itself, but others are incorrectly tagged lanes. Some resources for fixing OSM:

Instructions for ASU collaborators

These instructions are tailored to the ASU Transportation AI Lab.

The most important tip: ask questions! File an issue, email dabreegster@gmail.com, or ask for a Slack invite.


A new version is released every Sunday, but you probably don't need to update every week.

  1. Go to https://github.com/a-b-street/abstreet/releases and download the latest .zip file for Windows, Mac, or Linux.


  1. Unzip the folder and run play_abstreet.sh or play_abstreet.bat. If you get security warnings, see here.


  1. On the main title screen, click Sandbox. This starts in Seattle by default, so change the map at the top.


  1. Choose USA, then Phoenix.


  1. You'll be prompted to download some files. It should be quick. After it's done, click Phoenix again.

You've now opened up the Tempe map!

A shortcut and improving the simulation in Tempe

On Windows, edit run_abstreet.bat and change the last line to:

game.exe --dev data/system/us/phoenix/maps/tempe.bin --infinite_parking 1> ..\\output.txt 2>&1

On Mac, edit run_abstreet.sh and change the last line to:

RUST_BACKTRACE=1 ./game --dev data/system/us/phoenix/maps/tempe.bin --infinite_parking 1> ../output.txt 2>&1

--dev data/system/us/phoenix/maps/tempe.bin will skip the title screen and start on the Tempe map by default; this will save you lots of time.

--infinite_parking disables the parking simulation. By default, there's an unrealistic amount of people walking around Tempe just to reach the spot where their car is parked. We don't have good data yet about on- and off-street parking, so it's best to just make driving trips begin and end at buildings or off the map, without a step to search for parking.

There are a bunch of other startup parameters you can pass here too.

Importing a Grid2Demand scenario

When you run https://github.com/asu-trans-ai-lab/grid2demand, you get an input_agents.csv file. You can import this into A/B Street as a scenario.

  1. Change the traffic from none


  1. Click import Grid2Demand data


  1. Choose your input_agents.csv file

  2. A new scenario will be imported. Later you can launch this from the same menu; the scenario will be called grid2demand


Grid2Demand needs a .osm file as input. The extract of Tempe that A/B Street uses is at https://abstreet.s3.us-east-2.amazonaws.com/dev/data/input/us/phoenix/osm/tempe.osm.gz. Note the file is compressed.

Modifying a scenario

You can transform a scenario before simulating it. This example will cancel all walking and biking trips from the scenario, only leaving driving and public transit.

  1. After loading a scenario, click 0 modifications to traffic patterns


  1. Click Change trip mode

  2. Select the types of trips that you want to transform, and change them to cancel. Click Apply.


Importing Vol2Timing data

https://github.com/asu-trans-ai-lab/Vol2Timing/ produces timing.csv files that you can import into A/B Street.

  1. Open the traffic signal editor for an intersection in A/B Street.

  2. Click Edit entire signal

  3. Choose import from a new GMNS timing.csv, then pick your file.

The import process isn't finished yet; some movements aren't matched properly, some movements are incorrectly marked as protected, and no crosswalks are imported yet. When you import, some error messages may be displayed, and others might wind up printed to STDOUT (captured in output.txt on Windows).

If you want to import timing for more intersections in the same map, after Edit entire signal, you should also have n option like import from GMNS C:\path\to\timing.csv.

Debugging timing.csv

Along with QGIS, you can also visualize timing.csv in A/B Street directly.

  1. From the title screen, choose Internal dev tools.

  2. Change the map if necessary.

  3. Click view KML.

  4. Click load KML file, then choose your timing.csv.

  5. Each movement is drawn in red. You can hover over a line-string to see its attributes, and click to open all details.


  1. Using the key=value filter on the left, you can type in no=3 to match stage_no=3 and easily check what movements belong to each stage.



The whole point of A/B Street is for people to suggest real fixes to their city, so here's the list. In most cases, we're not yet able to use A/B Street itself to illustrate the problem or solution -- but that shouldn't stop this list from starting.

If you want to add anything here, contact us!


Disclaimer: currently this is just one person's (Dustin) list, so it reflects my experiences and biases. South Seattle is conspicuously missing -- I need to go explore more.

Safety problems biking:

  • Airport Way southbound from ID to Georgetown. The right lane has a huge shoulder; at least paint a bike lane
  • Broadway bike lanes missing between Highland and John
  • Partitioning off a lane of Aurora at Green Lake west, proposed by Seattle Greenways
  • Bike lanes vanishing halfway up Harvard to Roanoke
  • The massive intersection where the Burke Gilman meets Corliss
  • No passing room on Fuhrman/Boyer

Antagonistic traffic signal timing:

(Why are these important: they're hopefully a cheap change, and problems here add up, making driving the most convenient option.)

  • Northbound on 11th after the University Bridge
  • Turning left from 11th Ave NE to Ravenna
  • The double crosswalk at Montlake / Husky Stadium
  • Cherry and 12th; long cycle time for people crossing near Seattle University
  • Yesler and Broadway slow beg buttons
  • Unmarked beg buttons around Beacon Hill, and no bike actuators
  • Beg buttons along Interurban near Bitter Lake

Larger changes:

  • Cafe/bus street along University Ave
  • Eastlake cycle lanes


There's some idea related to a cycle-path on the A5


Over time, each idea will link to a full write-up. That page can have any format, but I imagine this basic structure is useful:

  • Describe the problem
    • A clear map showing the scope of the issue
    • Include pictures and videos from a physical survey, or maybe annotated satellite imagery
    • Use A/B Street to demonstrate the problem for individual people or in aggregate
  • Describe the proposed solution
    • Ideally A/B Street is useful for visualizing the change, and measuring quantitative results.
    • Explain any limits with the modelling
  • Give context on the problem
    • Is this being discussed anywhere -- often on Twitter?
    • Are there other proposals or write-ups?
  • Give arguments against the proposed solution
    • Unintended consequences, prohibitive costs, alternate routes available

Lake Washington Blvd Stay Healthy Street

Draft, updated May 7, 2020 by Dustin Carlino (dabreegster@gmail.com)

In April 2020, Seattle Department of Transportation started rolling out Stay Healthy Streets, restricting roads to through-traffic to give people walking and biking more space for social distancing. Seattle Neighborhood Greenways soon proposed extending this to a 130-mile network.

Selecting the streets requires some planning:

These streets were selected to amplify outdoor exercise opportunities for areas with limited open space options, low car ownership and routes connecting people to essential services and food take out. We also ensured street closures did not impact newly opened food pick up loading zones, parking around hospitals for service for health care professionals, and bus routes.

I've spent the last two years building A/B Street, software to explore the impacts of changes like this on different modes of transportation. So, let's try implementing part of the proposed network and see what happens!

Lake Washington Blvd

Let's start with one part of the proposal, closing Lake Washington Blvd to cars through the Arboretum. There's already a multi-use trail alongside this stretch, but its width makes it difficult to maintain 6 feet from people. There are some parking lots that become inaccessible with this proposal, but they're currently closed anyway.


First attempt

Let's get started! If you want to follow along, install A/B Street, open sandbox mode, and switch the map to Lake Washington corridor. Zoom in on the southern tip of the Arboretum and hop into edit mode. We can see Lake Washington Blvd just has one travel lane in each direction here. Click each lane, convert it to a bike lane, and repeat north until Foster Island Road.

When we leave edit mode, the traffic simulation resets to midnight. Nothing really interesting happens until 5 or 6am, so we'll speed up time. Watching the section of road we edited, we'll only see pedestrians and bikes use this stretch of road. If we want, we can click an individual person and follow along their journey.

Something's weird though. There's lots of traffic cutting northbound through the neighborhood, along 29th, Ward, and 28th. We can open up the throughput layer to find which roads have the most traffic. More usefully, we can select "compare before edits" to see what roads are getting more or less traffic because of the road we modified. As expected, there's much less traffic along Lake Wash Blvd, but it's also clear that lots of cars are now cutting through 26th Ave E.

Traffic calming

Let's say you want to nudge traffic to use 23rd Ave, the nearest north/south arterial, instead. (A/B Street is an unopinionated tool; if you have a different goal in mind, try using it for that instead.) In this simulation, drivers pick the fastest route, so we could try lowering speed limits or make some of the residential streets connecting to Madison one-way, discouraging through-traffic. In reality, the speed limit changes could be implemented through traffic calming or cheap, temporary alternatives.

Next steps

I'm working to model "local access only" roads in A/B Street, and I'll describe how to measure the impact on travel times. Stay tuned to see more of the proposed network simulated, and get in touch if you'd like to help out!

Technical details

This section of the site is intended for anybody who wants to understand or work on A/B Street's internals.

Developer guide

Getting started

You will first need:

One-time setup:

  1. Download the repository: git clone https://github.com/a-b-street/abstreet.git

  2. Grab the minimal amount of data to get started: cargo run --bin updater -- --minimal

  3. Run the game: RUST_BACKTRACE=1 cargo run --bin game --release. On Windows, set environment variables like this: set RUST_BACKTRACE=1 && cargo run --bin game --release

Development tips

  • Generated API documentation
  • Compile faster by just doing cargo run. The executable will have debug stack traces and run more slowly. You can do cargo run --release to build in optimized release mode; compilation will be slower, but the executable much faster.
  • Some in-game features are turned off by default or don't have a normal menu to access them. The list:
    • To toggle developer mode: press Control+S in game, or cargo run -- --dev
    • To warp to an object by numeric ID: press Control+j
    • To enter debug mode with all sorts of goodies: press Control+D
  • You can start the game in different modes using flags:
    • cargo run --bin game -- --dev data/system/us/seattle/maps/downtown.bin starts on a particular map
    • cargo run --bin game -- data/system/us/seattle/scenarios/downtown/weekday.bin starts with a scenario (which is tied to a certain map)
    • cargo run --bin game -- --challenge=trafficsig/tut2 starts on a particular challenge. See the list of aliases by passing in a bad value here.
    • cargo run --bin game -- data/player/saves/us/seattle/montlake/no_edits_unnamed/00h00m20.3s.bin restores an exact simulation state. Savestates are found in debug mode (Control+D) -- they're probably confusing for the normal player experience, so they're hidden for now.
    • cargo run --bin game -- --tutorial=12 starts somewhere in the tutorial
    • Adding --edits='name of edits' starts with edits applied to the map.

Downloading more cities

Most easily, you can download new cities using the UI directly.

As data formats change over time, things in the data/ directory not under version control will get out of date. At any time, you can run cargo run --bin updater from the main repository directory to update only the files that have changed.

You can also opt into downloading updates for more cities by editing data/player/data.json. If you want to opt into absolutely everything: cargo run --bin updater -- --opt-into-all > data/player/data.json

Building map data

You can skip this section if you're just touching code in game, widgetry, and sim.

To run all pieces of the importer, you'll need some extra dependencies:

The first stage of the importer, --raw, will download input files from OSM, King County GIS, and so on. If the mirrors are slow or the files vanish, you could fill out data/config and use the updater described above to grab the latest input.

Building contraction hierarchies for pathfinding occurs in the --map stage. It can take a few minutes for larger maps. To view occasional progress updates, you can run the importer with

RUST_LOG="fast_paths=debug/contracted node [0-9]+0000 "

You can rerun specific stages of the importer:

  • If you're modifying the initial OSM data -> RawMap conversion in convert_osm, you need ./import.sh --raw --map.
  • If you're modifying map_model but not the OSM -> RawMap conversion, then you just need ./import.sh --map.
  • If you're modifying the demand model for Seattle, you can add --scenario to regenerate.
  • By default, all maps are regenerated. You can also specify a single map: ./import.sh --map downtown.
  • By default, Seattle is assumed as the city. You have to specify otherwise: ./import.sh --city=us/detroit --map downtown.

You can also make the importer import a new city.

Understanding stuff

The docs listed at https://github.com/a-b-street/abstreet#documentation explain things like map importing and how the traffic simulation works.

Code organization

If you're going to dig into the code, it helps to know what all the crates are. The most interesting crates are map_model, sim, and game.

Constructing the map:

  • convert_osm: extract useful data from OpenStreetMap and other data sources, emit intermediate map format
  • kml: extract shapes from KML and CSV shapefiles
  • map_model: the final representation of the map, also conversion from the intermediate map format into the final format
  • map_editor: GUI for modifying geometry of maps and creating maps from scratch. pretty abandoned as of June 2020
  • importer: tool to run the entire import pipeline
  • updater: tool to download/upload large files used in the import pipeline

Traffic simulation:

  • sim: all of the agent-based simulation logic
  • headless: tool to run a simulation without any visualization


  • game: the GUI and main gameplay
  • map_gui: common code to interact with map_model maps
  • widgetry: a GUI and 2D OpenGL rendering library, using glium + winit + glutin

Common utilities:

  • abstutil: a grab-bag timing and logging utilities
  • abstio: Reading/writing files on native/web
  • geom: types for GPS and map-space points, lines, angles, polylines, polygons, circles, durations, speeds


  • collisions: an experimental data format for real-world collision data
  • traffic_seitan: a bug-finding tool that randomly generates live map edits
  • tests: integration tests
  • santa: 15-minute Santa, an arcade game about delivering and zoning
  • parking_mapper: a standalone tool to help map street parking in OSM
  • osm_viewer: a standalone tool to render OSM in detail
  • fifteen_min: a standalone tool to explore 15-minute neighborhoods
  • popdat: use census data to produce traffic simulation input
  • traffic_signal_data: manual timing overrides for some traffic signals
  • sumo: interoperability with SUMO

Code conventions

All code is automatically formatted using https://github.com/rust-lang/rustfmt; please run cargo fmt before sending a PR.

cargo fmt can't yet organize imports, but we follow a convention to minimize conflict with what some IDEs do. Follow existing code to group imports: std, external crates, other crates in the project, the current crate, then finally any module declarations.

We also use https://github.com/rust-lang/rust-clippy to adhere to more Rust best practices. No need to run it for every commit, and sometimes we'll disable a warning. If in doubt, just ask on a PR.

See the testing strategy page.

Error handling

The error handling is unfortunately inconsistent. The goal is to gracefully degrade instead of crashing the game. If a crash does happen, make sure the logs will have enough context to reproduce and debug. For example, giving up when some geometry problem happens isn't ideal, but at least make sure to print the road / agent IDs or whatever will help find the problem. It's fine to crash during map importing, since the player won't deal with this, and loudly stopping problems is useful. It's also fine to crash when initially constructing all of the renderable map objects, because this crash will consistently happen at startup-time and be noticed by somebody developing before a player gets to it.

Since almost none of the code ever needs to distinguish error cases, use anyhow. Most of the errors generated within A/B Street are just strings anyway; the bail! macro is a convenient way to return them.


Prefer using info!, warn!, error!, etc from the log crate rather than println.

Adjust the log level without recompiling via the RUST_LOG env variable.

RUST_LOG=debug cargo run --bin game

This can be done on a per lib basis:

RUST_LOG=my_lib=debug cargo run --bin game

Or a module-by-module basis:

RUST_LOG=my_lib::module=debug cargo run --bin game

You can mix and match:

# error logging by default, except the foo:bar module at debug level
# and the entire baz crate at info level
RUST_LOG=error,foo::bar=debug,baz=info cargo run --bin game

For some special cases, you might want to use regex matching by specifying a pattern with the "/":

# only log once every 10k
RUST_LOG="fast_paths=debug/contracted node [0-9]+0000 " mike import_la

See the env_logger documentation for more usage examples.


Use https://github.com/flamegraph-rs/flamegraph, just running it on the binaries you build normally.

Development notes

Find packages to upgrade: cargo outdated -R

Deal with compile tile: cargo bloat --time

Find why two binary crates aren't sharing dependencies: https://old.reddit.com/r/rust/comments/cqceu4/common_crates_in_cargo_workspace_recompiled/

Where's a dependency coming from? cargo tree -i -p syn

Diff screencaps: http://www.imagemagick.org/Usage/compare/#methods

Debug OpenGL calls:

apitrace trace --api gl ../target/debug/game
qapitrace game.trace
apitrace dump game.trace

Understand XML: just use firefox

Building releases

Cross-compilation notes: https://github.com/rust-embedded/cross Or use https://github.com/japaric/trust

Initially have to:

cargo install cross
sudo apt-get install docker.io
sudo usermod -aG docker ${USER}


sudo systemctl start docker
cross build --release --target x86_64-pc-windows-gnu --bin game
wine target/x86_64-pc-windows-gnu/release/game.exe data/system/seattle/maps/montlake.bin


For formatting:

sudo apt-get install npm
cd ~; mkdir npm; cd npm
npm init --yes
npm install prettier --save-dev --save-exact


# Fullscreen
ffmpeg -f x11grab -r 25 -s 1920x960 -i :0.0+0,55 -vcodec huffyuv raw.avi

ffmpeg -ss 10.0 -t 5.0 -i raw.avi -f gif -filter_complex "[0:v] fps=12,scale=1024:-1,split [a][b];[a] palettegen [p];[b][p] paletteuse" screencast.gif

Faster linking

sudo apt-get install lld

Stick this in ~/.cargo/config:

rustflags = [
    "-C", "link-arg=-fuse-ld=lld",


Keep a fork up to date:

# Once
git remote add upstream https://github.com/rust-windowing/glutin/

git fetch upstream
git merge upstream/master
git diff upstream/master


perl -pi -e 's/WrappedComposite::text_button\(ctx, (.+?), (.+?)\)/Btn::text_fg(\1).build_def\(ctx, \2\)/' `find|grep rs|xargs`

Stack overflow

rust-gdb --args ../target/release/game --dev

Drawing diagrams



xodo on Android for annotating maps in the field

OSM tools

osmcha.org for recent changes

To upload diffs:

java -jar ~/Downloads/josm-tested.jar ~/abstreet/map_editor/diff.osc

JOSM: Press (and release T), then click to pan. Download a relevant layer, select the .osc, merge, then upload.



Release checklist

What things are sensitive to changes in map data and simulation rules?

  • tutorial
  • optimize commute challenges

What things do I always forget to test?

  • DPI issues, use --scale_factor


Suppose you're tired of manually fiddling with traffic signals, and you want to use machine learning to do it. You can run A/B Street without graphics and automatically control it through an API.


This Python example has everything you need to get started.

See all example code -- there are different experiments in Go and Python that automate running a simulation, measuring some metric, and making a change to improve the metric.

Control flow

The headless API server that you run contains a single map and simulation at a time. Even though you can theoretically have multiple clients make requests to it simultaneously, the server will only execute one at a time. If you're trying to do something other than use one script to make API calls in sequence, please get in touch, so we can figure out something better suited to your use case.

When you start the headless server, it always loads the montlake map with the weekday scenario. The only way you can change this is by calling /sim/load. For example:

curl http://localhost:1234/sim/load -d '{ "scenario": "data/system/us/seattle/scenarios/downtown/monday.bin", "modifiers": [], "edits": null }' -X POST`

You can also pass flags like --infinite_parking to the server to control SimOptions. These settings will apply for the entire lifetime of the server; you can't change them later.

API details

Under construction: The API will keep changing. There are no backwards compatibility guarantees yet. Please make sure I know about your project, so I don't break your client code.

For now, the API is JSON over HTTP. The exact format is unspecified, error codes are missing, etc. A summary of the commands available so far:

  • /sim
    • GET /sim/reset: Reset all temporary map edits and the simulation state. The trips that will run don't change; they're determined by the scenario specified by the last call to /sim/load. If you made live map edits using things like /traffic-signals/set, they'll be reset to the edits from /sim/load.
    • POST /sim/load: Switch the scenario being simulated, and also optionally sets the map edits.
    • GET /sim/get-time: Returns the current simulation time.
    • GET /sim/goto-time?t=06:30:00: Simulate until 6:30 AM. If the time you specify is before the current time, you have to call /sim/reset first.
    • POST /sim/new-person: The POST body must be an ExternalPerson in JSON format.
  • /traffic-signals
    • GET /traffic-signals/get?id=42: Returns the traffic signal of intersection #42 in JSON.
    • POST /traffic-signals/set: The POST body must be a ControlTrafficSignal in JSON format.
    • GET /traffic-signals/get-delays?id=42&t1=03:00:00&t2=03:30:00: Returns the delay experienced by every agent passing through intersection #42 from 3am to 3:30, grouped by direction of travel.
    • GET /traffic-signals/get-cumulative-thruput?id=42: Returns the number of agents passing through intersection #42 since midnight, grouped by direction of travel.
    • GET /traffic-signals/get-all-current-state: Returns the current state of all traffic signals, including the stage timing, waiting, and accepted agents.
  • /data
    • GET /data/get-finished-trips: Returns a JSON list of all finished trips. Each tuple is (time the trip finished in seconds after midnight, trip ID, mode, duration of trip in seconds). The mode is a string like "Walk" or "Drive". If the trip was cancelled for any reason, duration will be null.
    • GET /data/get-agent-positions: Returns a JSON list of all active agents. Vehicle type (or pedestrian), person ID, and position is included.
    • GET /data/get-road-thruput: Returns a JSON list of (road, agent type, hour since midnight, throughput for that one hour period).
    • GET /data/get-blocked-by-graph: Returns a mapping from agent IDs to how long they've been waiting and why they're blocked.
    • GET /data/trip-time-lower-bound?id=123: Returns a reasonable lower bound for the total duration of trip 123, in seconds. The time is calculated assuming no delay at intersections, travelling full speed along every road, and using the primary mode for the entire trip (so just driving).
    • GET /data/all-trip-time-lower-bounds: The faster equivalent of calling /data/trip-time-lower-bound for every trip in the simulation.
  • /map
    • GET /map/get-edits: Returns the current map edits in JSON. You can save this to a file in data/player/edits/city_name/map_name/ and later use it in-game normally. You can also later run the headless server with --edits=name_of_edits.
    • GET /map/get-edit-road-command?id=123: Returns an object that can be modified and then added to map edits.
    • GET /map/get-intersection-geometry?id=123: Returns a GeoJSON object with one feature for the intersection and a feature for all connecting roads. The polygon coordinates are measured in meters, with the origin centered at the intersection's center.
    • GET /map/get-all-geometry: Returns a huge GeoJSON object with one feature per road and intersection in the map. The coordinate space is WGS84.

Working with the map model

If you need to deeply inspect the map, you can dump it to JSON:

cargo run --bin dump_map data/system/us/seattle/maps/montlake.bin > montlake.json

See some example code that reads this JSON and finds buildings.

You could also edit the map JSON, convert it back to binary, and use it in the simulation. This isn't recommended generally, but one possible use case could be tuning the amount of offstreet parking per building. The map JSON has a list called buildings, and each object there has a field parking. You coud set this object to { "Private": [100, false] } to indicate 100 parking spots, for a building not explicitly designated in OpenStreetMap as a garage. After editing the JSON, you have to convert it back to the binary format:

cargo run --bin json_to_binary_map -- --input=montlake.json out=data/system/us/seattle/maps/montlake_modified.bin`

The format of the map isn't well-documented yet. See the generated API docs and the map model docs in the meantime.

Working with individual trips

You can use the /sim/new-person API in the middle of a simulation, if needed. If possible, it's simpler to create a Scenario as input.

Working with Scenarios

You can import trips from your own data.

You can also generate different variations of one of the demand models by specifying an RNG seed:

cargo run --bin random_scenario -- --rng=123 --map=data/system/us/seattle/maps/montlake.bin --scenario_name=home_to_work

You can also dump Scenarios (the file that defines all of the people and trips) to JSON:

cargo run --bin dump_scenario data/system/us/seattle/scenarios/montlake/weekday.bin > montlake_weekday.json

You can modify the JSON, then put the file back in the appropriate directory and use it in-game:

cargo run --bin game data/system/us/seattle/scenarios/montlake/modified_scenario.json

The Scenario format is documented here.

Testing strategy

Unit tests

As you've probably noticed, there aren't many. Lots of the interesting behavior in A/B Street - UI interactions, details of the simulation, map importing -- would take lots of infrastructure to specify a setup and expected outcomes. If you have ideas for new tests, contributions always welcome! In the meantime, one useful test covers how OSM tags translate into individual lanes.

Screenshot diffs

Downloading fresh OSM data or modifying any part of the map importing pipeline could easily break things. Expressing invariants about the map output is hard, because importing is far from perfect, and OSM data is often quite buggy. So the approach to preventing regressions here is to look for visual changes to the final rendered map.

  1. When a new map is opted into this type of test, somebody manually squints carefully at it and sanity checks that it works to some degree.
  2. They use the screen capture tool in debug mode to tile the map into 1920x960 chunks and screengrab everything.
  3. Later, somebody regenerates the map with some possible changes.
  4. They grab screenshots again, then use compare_screenshots.sh to quickly look at the visual diff. Changes to intersection geometry, number of lanes, rendering, etc are all easy to spot.
  5. If this manual inspection of the diff is good, they commit the new screenshots as the new goldenfiles.


This tool regenerates all maps and scenarios from scratch. cargo run --bin updater -- --dry then reveals what files have changed.

Additionally, this script does a few more tests:

  • --prebake runs the full weekday scenario on two maps that've previously been coerced into being gridlock-free

Integration tests

The tests crate contains some integration tests.

One part runs the full importer against really simple .osm files. To iterate rapidly on interpreting turn restrictions, it produces goldenfiles describing all turns in the tiny map.

The "smoke-test" section simulates one hour on all maps, flushing out bugs with bus spawning, agents hitting odd parts of the map, etc

The "check proposals" section makes sure the edits shipped with the game still load properly.

Old tests

Once upon a time, I made a little test harness that would run the simulation headlessly (without graphics), set up certain situations forcing a car to park in a certain spot, and asserted that different sim/src/events.rs were produced in the right order. The map_editor tool was used to manually draw really simple maps for these situations. I deleted everything, because the effort to specify the input and expected output were too tedious to maintain, and this never really helped catch bugs. There was a way to label roads and buildings in the synthetic maps, so the test code could assert person 2 made it to the "house" building, but even with all of this, it was pretty hard.

This approach is maybe worth reviving, though.

Mass importing many maps

For https://github.com/a-b-street/abstreet/issues/326, I'm starting to figure out how to import hundreds of maps into A/B Street. There are many issues with scaling up the number of supported maps. This document just focuses on importing.

The current approach

https://download.bbbike.org/ conveniently has 200 OSM extracts for major cities world-wide. The data/bbike.sh script downloads these. Then data/mass_import.sh attempts to import them into A/B Street.

The bbike extracts, however, cover huge areas surrounding major cities. Importing such large areas is slow, and the result is too large to work well in A/B Street or the OSM viewer. Ideally, we want just the area concentrated around the "core" of each city.

https://github.com/a-b-street/abstreet/blob/master/convert_osm/src/bin/extract_cities.rs transforms a huge .osm file into smaller pieces, each focusing on one city core. This tool looks for administrative boundary relations tagged as cities, produces a clipping polygon covering the city, and uses osmconvert to produce a smaller .osm file. The tool has two strategies for generating clipping polygons. One is to locate the admin_centre or label node for the region, then generate a circle of fixed radius around that point. Usually this node is located in the city core, so it works reasonably, except for "narrow" cities along a coast. The other strategy glues together the relation's multipolygon boundary, then simplifies the shape (usually with thousands of points) using a convex hull. This strategy tends to produce results that're too large, because city limits are often really huge.


  • Outside the US, administrative boundaries don't always have a "city" defined. In Tokyo in particular, this name isn't used. I'm not sure which boundary level to use yet.
  • The tool assumes driving on the right everywhere. OSM has https://wiki.openstreetmap.org/wiki/Key:driving_side, but this is usually tagged at the country level, which isn't included in the bbike extracts.
  • The resulting maps are all "flattened" in A/B Street's list, so you can't see any hierarchy of areas. Two cities with the same name from different areas will arbitrarily collide.

Data organization

A/B Street includes lots of large binary files to represent converted maps, scenarios, and prebaked simulation results. The files are too large to store in git, but the files are still logically tied to a version of the code, since the format sometimes changes. Additionally, all of the files are too large to include in the .zip release that most people use, but it should still be possible for players to download the optional content. Also, there are different versions of the game floating around, on native and web, that have to be associated with the proper version of these files.

It's all slightly confusing, so this page describes how it all works.

The data itself

If you peek into the data/ directory, it's mainly split into 3 subdirectories. system/ is used when running the game and is the subject of this page. input/ is used to store input and intermediate files for importing maps, and only developers running the importer should care about it. player/ contains local settings, map edits, and other data created in-game.

data/MANIFEST.json is a listing of all files in data/system/, along with their size and md5sum. Different tools compare this manifest to the local filesystem to figure out what to do.

There are also some other scripts and files in data/, but they should probably be moved.

Where the data is stored

data/system/ and data/input/ are stored in Amazon S3, at http://abstreet.s3-website.us-east-2.amazonaws.com. This S3 bucket is organized into versions: dev, 0.2.17, 0.2.18, etc. dev represents the latest version of all data files. The numbered versions correspond to releases and only contain data/system/, not data/input/. Depending how large these directories grow over time, I'll commit to keeping around at least 3 of the previous numbered versions, but I might delete older ones after that.

Native, running from source

For people building the game from source, the process to keep data files fresh is to cargo run --bin updater. This tool calculates md5sums of all local files, then compares it with the checked-in data/MANIFEST.json. Any difference results in a local file being deleted or a new file from S3 being downloaded. By editing data/player/data.json manually or using the UI in the game (found by loading a map, then choosing to download more maps), somebody can opt into downloading "extra/optional" cities.

Native, running from a release .zip

When the weekly .zip binary release for Mac, Linux, and Windows is produced, the game crate is built with --features release_s3. When the downloader UI is opened in-game, this causes downloads to occur from a versioned S3 directory, like 0.2.17, depending on the version string compiled into the game at that time. So somebody can run off the weekly release, opt into more cities, and get the correct version of the files, even if the format has changed in /dev/ since then.

Web, running locally

The strategy for managing files gets more interested when the game is compiled to WebAssembly, since browsers can't read from the local filesystem. game/src/load.rs contains some crazy tricks to instead make asynchronous HTTP requests through the browser. When using game/run_web.sh, the files are served through a local HTTP server and symlinked to the local copy of data/system/.

Not all files are loaded through HTTP; some are actually statically compiled into the .wasm file itself! abstutil/src/io_web.rs does this magic using the include_dir crate. Only a few critical large files, needed at startup, are included. There's an IO layer for listing and reading files that, on web, merges results from the bundled-in files and the remote files that're declared to exist in the bundled-in copy of data/MANIFEST.json.

Web, from S3

Everything's the same, except that since the URL isn't localhost, the game makes HTTP requests to the S3 bucket (or wherever the game is hosted). Additionally, the files are expected to be gzipped. The web version always pins to /dev, never a release version of the data, since the web client is always updated along with the data, for now.

Release process

This happens every Sunday.

  1. Push a commit containing [rebuild] [release] in the commit message.
  2. Wait for the build to complete at https://github.com/a-b-street/abstreet/actions
  3. Manually download the 3 .zip files
  4. With your current directory set to where the .zips are downloaded, run ~/abstreet/release/finalize.sh v0_2_38, changing the version number. (That path assumes the abstreet git repo is in your home directory.)
  5. If you want, sanity check that the final generated binaries work. Sometimes I test things like the map importer or that maps not included by default can be downloaded.
  6. Create a new release at https://github.com/a-b-street/abstreet/releases/new. Make sure the version matches, with a different format -- v0.2.38. The description should match what later goes in the changelog, and the name really ought to be gastronomically offensive.
  7. Upload the 3 transformed .zips created by finalize.sh -- they're named something like abstreet_mac_v0_2_38.zip.
  8. Publish release!
  9. From the root directory of the abstreet repo, run ./release/update_docs.sh 37 38. When the major version number changes, update that script first. This updates the latest release from 0.2.37 to 0.2.38. It assumes the https://github.com/a-b-street/docs repo is in your home directory named ~/docs.
  10. Go fill out ~/docs/book/src/project/CHANGELOG.md; the notes should match what's in the Github release.
  11. Follow the steps that the update_docs.sh script tells you. Don't forget to push the commit on the main abstreet repo as well.

One of the update_docs.sh steps is an S3 copy. This "freezes" the current "dev" data and web deployment as a named version. The URL would be something like http://abstreet.s3-website.us-east-2.amazonaws.com/0.2.38/abstreet.html.

How it works

The process is kind of convoluted; help is always welcome to smooth it out.

Github Actions is used to build for all 3 platforms. .github/workflows/main.yml is configured to only build when [rebuild] is in the commit message. [release] enables a cargo feature flag that tells map_gui/src/tools/updater.rs where to look to download new system data. Because the binary map format changes, an older release has to be pinned to a particular versioned copy of all the system data. The workflow calls release/build.sh, which assembles the directory structure with all of the executables, system data (obtained by running the updater with the default Seattle-only opt-in), and instructions.

There's some funkiness with producing a .zip, because uploading Github artifacts always double-zips, and also on the Windows runner, there doesn't seem to be a way to create a .zip. That's why the extra step of downloading the build artifacts and running release/finalize.sh locally is necessary.

Data formats

This section describes data formats that A/B Street uses for input and output. Like the API, nothing is guaranteed to be backwards-compatible yet. The best way to make sure schema changes won't mess up your workflow is to keep in touch and make sure I know how you're using these files.

The formats are currently JSON, without a machine-readable schema. In the future, I'm highly likely to use protocol buffers, flatbuffers, Thrift, etc instead, so that languages with static type systems can benefit from auto-generated libraries and so that larger files can be encoded in binary.

Scenario data format

If you want to import your own travel demand model, you can use this JSON file.


Until we switch to using protobufs or similar, there's no formal spec. The JSON deserialization happens from Rust code. It's easiest to follow the example below.


This defines a scenario containing a single person, who takes one trip. They begin at midnight at the building nearest to the origin, then at 3AM (10800 is seconds after midnight), they drive to the building nearest to the destination. The mode field could also be Walk, Bike, or Transit. The purpose field is mostly unused; you could pick other values that show up in the UI. You could add more trips to this person, with increasing departure time. And of course, you can add as many people as you like.

  "scenario_name": "monday",
  "people": [
      "trips": [
          "departure": 10800.0,
          "origin": {
            "Position": {
              "longitude": -122.303723,
              "latitude": 47.6372834
          "destination": {
            "Position": {
              "longitude": -122.3075948,
              "latitude": 47.6394773
          "mode": "Drive",
          "purpose": "Shopping"


To import this JSON file into A/B Street:

cargo run --bin import_traffic -- --map=data/system/us/seattle/maps/montlake.bin --input=/path/to/input.json

This tool matches input positions to the nearest building, within 100 meters. If the point lies outside the map boundary, it's snapped to the nearest map border. The tool will fail if any point doesn't match to a building. If you pass --skip_problems, those people will be logged and skipped instead.

There are also a few tools that produce this JSON file:

Future requests

  • A way to specify some kind of numeric ID for each person, so you can later correlate results from the simulation with your input

Traffic signal data format

A/B Street uses a JSON format to describe how a single intersection with traffic signals is configured. This format changes more frequently, but the software will automatically upgrade a signal described in an old format. This way, players can save their edits to a map and, months later, still use the same edits with a new version of A/B Street. (The only exception is when OpenStreetMap IDs change; if a way near the intersection is split and assigned a new ID, we make no attempt to handle this.)

The format is complicated, so let's break down some of the concepts used in it.


We first need a way to describe a movement between two road segments. Let's use https://www.openstreetmap.org/node/53219808, the intersection of 24th Ave E and E McGraw St in Seattle as an example.

Directed road segments

So first let's identify a single road segment. For this example, let's describe the eastbound direction of the west side of McGraw St, which is https://www.openstreetmap.org/way/804788512 in OSM. As you can see, this way hits 3 intersections. We just want to describe the segment between 24th and 25th Ave. Looking at the list of nodes for that way in the order defined in OSM, we see it goes from https://www.openstreetmap.org/node/53219808 to https://www.openstreetmap.org/node/1709143541. Note that the intermediate nodes used as control points for the shape of the road or as crosswalk nodes are ignored. So, we can describe this segment by using the way's ID and the first and last node ID.


  "osm_way_id": 804788512,
  "osm_node1": 53219808,
  "osm_node2": 1709143541,
  "is_forwards": false

Finally, we have to say which direction of this road segment we're talking about. The order of the nodes in OSM is arbitrary; in this case, the road points from the traffic signal we're talking about to another intersection. But we want to refer to the inbound / eastbound direction of this road. So we say "is_forwards": false to indicate the opposite of the direction specified by OSM. This is the same concept as "forward & backward" as defined in the OSM wiki.

Turns for vehicles

So now let's describe the left turn from E McGraw to 24th going southbound. It looks like this:

  "from": {
    "osm_way_id": 804788512,
    "osm_node1": 53219808,
    "osm_node2": 1709143541,
    "is_forwards": false
  "to": {
    "osm_way_id": 6470476,
    "osm_node1": 53128048,
    "osm_node2": 53219808,
    "is_forwards": false
  "intersection_osm_node_id": 53219808,
  "is_crosswalk": false

You specify the from and to fields using the directed road segment ID. Additionally, you specify which intersection the traffic signal is at -- 53219808 in this case -- and set is_crosswalk to false to indicate a turn for vehicles or cyclists on the road.


Why is the format above so repetitive? The reason is to be able to also specify movements along crosswalks in the intersection. The from and to directed road segments refer to one half of a road, so they can be used to identify one sidewalk or another. From this diagram, you can see how there are 8 different start or end points for crosswalks at this intersection:


TODO: Walk through an example of a single crosswalk. I hope the explanation of the format above suffices, for now.


Now that we understand how to describe turns, we can describe the sequence of stages that a traffic signal cycles through. Each stage specifies the turns that are protected and permitted. Protected turns have priority (a green light), and no two protected turns in the same stage can cross each other. Permitted turns are allowed only if there's no oncoming traffic -- so this could mean a right turn on red or an unprotected left turn with a flashing arrow. Note that crosswalks always must be defined as protected, which means any vehicle turns that intersect with the crosswalk have to be specified as permitted. This captures the semantics in the US that turning vehicles must always yield to pedestrians when the crosswalk signal is on.

A single traffic signal cycles through the same list of stages over and over. Each stage also specifies its duration with the stage_type. The simple example is "Fixed": 45, meaning this stage always lasts exactly 45 seconds. A stage can also have variable timing, also known as actuation. The format is "Variable": [15, 2, 10]. This stage would last a minimum of 15 seconds, but it may last an additional 10 seconds, up to a maximum of 25 seconds. If there are no vehicles or pedestrians trying to make protected turns after 15 seconds, the stage ends. If there are, then the stage is extended by 2 seconds, and the same check repeats, until the maximum of 25 is reached.

The full example

Understanding everything above means you should be able to interpret what https://github.com/a-b-street/abstreet/blob/599c7b6d6bc078312e5ea9f57d3391be9568ef83/traffic_signal_data/data/53219808.json means. The rendering in A/B Street looks like this:


There are two stages. The first lasts 45 seconds and allows north/south movement. Left turns onto McGraw are permitted after yielding. Since the north/south crosswalks are also enabled, all right turns also have to yield. The second stage lasts only 15 seconds and lets east/west traffic move. Left turns are also unprotected here.


In reality, many traffic signals use different configuration during rush hour, late at night, on weekends, etc. There's some planned work to model one intersection having different plans.

Many intersections in Seattle now use leading pedestrian intervals, which enable a crosswalk signal to give pedestrians a head-start into the intersection before vehicles start to turn. You can model this in A/B Street by adding an extra stage that only enables the crosswalks and lasts a few seconds, followed by a stage allowing both the crosswalks and vehicle turns. We could explicitly add a parameter for LPI duration, but it would complicate the format, so this "flattened stage" format will work for now.

You'll notice the format for specifying turns works at the granularity of the entire road, not individual lanes. Most of the time, this is fine -- vehicles will only use the turn lanes available. But in some cases, we may want to distinguish two groups of vehicles moving the same direction by the lane type. In particular, sometimes there's a bidirectional protected cycle-track on one side of the road with its own dedicated signal:


A/B Street can't model this as having a separate signal yet.

Complications with importing other data

External software like vol2timing can provide much better signal configuration than A/B Street's default heuristics. We will encounter at least two major challenges to import it.


A/B Street currently does not use sidewalks, foot-paths, and crosswalks that are explicitly drawn as separate OSM ways. Instead, it makes many guesses for which roads have a sidewalk and creates many, many crosswalks. See https://github.com/a-b-street/abstreet/issues/485 for some examples of crosswalks that should not be created, but currently are.

When A/B Street imports traffic signal configuration, it validates that all possible turns at the intersection are captured by at least one stage. The problem here will be that other software may not list as many crosswalks as A/B Street expects.

Intersection merging/consolidation

OSM models bidirectional roads with some physical median as two one-ways. When these divided one-ways cross a regular road, the intersection is split into two intersections with a very short "road" in between. When two pairs of divided one-ways cross, the intersection has four nodes and short "roads" in between. This wreaks havoc in A/B Street, making the intersection geometry look strange, making it hard to edit the two or four uncoordinated traffic signals around the intersection, and causing vehicles to get stuck in the short "roads".

One solution that's promising is for A/B Street to consolidate those nodes and short "roads" into one big intersection, capturing how a human would intuitively view the area. One case where this works well is https://www.openstreetmap.org/#map=19/47.68264/-122.34426. See the before and after:


Unfortunately this process doesn't work for many more cases, so it's not enabled by default yet. Preserving turn restrictions defined by OSM and getting good results for the geometry of the consolidated intersection is hard.

In the meantime, all of this complicates the traffic signal format, because it's unclear how to specify road segments when A/B Street will arbitrarily delete some of the node IDs when it imports.

How the the A/B Street UI & drawing work

When I started A/B Street in June 2018, the Rust UI ecosystem had nothing that clearly fit my needs, so I wound up rolling something custom. This doc explains conceptually how it works and how to use it. Eventually some of this should become proper docs for widgetry.

Best advice on how to use stuff in practice is to work from examples. grep is your friend.

widgetry overview

First, the crate-level view. widgetry is the generic drawing and UI library, independent of A/B Street. map_gui builds on top of it, providing ways to render the map model used by all of the projects. Finally, game, fifteen_min, santa, and others are the runnable applications making use of this.

This section will explain how widgetry works from bottom-up. Conceptually we'll walk through how it was built from scratch, skipping all of the false turns made along the way.


Let's start just by drawing stuff and handling keyboard/mouse events. The basic loop of any winit program is to handle input events and, when winit says to, redraw everything. The earliest A/B Street prototype expressed the primitive map model imported from OpenStreetMap as a bunch of polygons, and hooked up basic mouse controls to pan and zoom over the canvas.

And that hasn't really changed -- we still only draw 2D colored polygons. Widgetry's use of OpenGL shaders is dirt simple. With the exception of some barely used texture code, all of the icons and images in A/B Street are SVGs, which can be transformed into colored polygons through the magic of the usvg and lyon crates. This includes all text -- even the text is just colored vector polygons! (Text rendering usually works by uploading a table of raster glyphs to the GPU and drawing textured quads.)

The brief story of how we got here: by November 2019, there was some basic support for uploading raster texture and drawing them. At the second Democracy Lab hackathon, a developer on Mac hit a 16 texture object limit that was different than Linux. This is also when Yuwen joined and started designing using Figma, which... conveniently had SVG export. I was also frustrated by rendering text separately from everything else; finding the bounding boxes was buggy and there were z-order issues. All of this prompted me to poke around and discover an example using lyon to tesellate the output from usvg. I thought, there's no way vectorizing EVERYTHING could be performant. But happily I was wrong.

Finally getting to the practical consequence here. It's expensive to upload stuff to the GPU, but it's cheap to draw something already uploaded. So you use a GeomBatch to build up that list of colored polygons, then upload it by doing ctx.upload(batch). Later on, you can g.redraw(&drawable) as many times as you want and it's fast. You don't keep the GeomBatch around; it's just the builder for a Drawable.

How should you batch stuff? Issuing redraw calls is fast, but not when there's lots of them. So for example, one Drawable per every building on the map would be a nightmare. Since buildings don't change, there's a single batch and Drawable for all of them. There's a balance here; sometimes you have to experiment to find it. But generally, if recalculating a batch only needs to happen every so often, just lump everything together in one for simplicity.

Stack of states

So at this point, there's logically one method to handle input events, and one method to draw. No built-in organization; all application state is just lumped somewhere. The basic insight is that an app has a stack of smaller states layered on top of each other. For example, you start with a title screen, then enter the main simulation interface, then open up a menu to show extra layers. You still want to draw the simulation underneath the menu, but not allow it to handle events. When you exit the simulation, you want to go back to the title screen and preserve any local state there.

So a widgetry app mainly consists of a stack of States. Each state implements a method to handle events and draw. Some states want to draw what's underneath them, while some want to clear the screen and fully handle everything -- so draw_baselayer specifies this.

When a state handles an event, it returns a Transition. This manipulates the stack. Transition::Keep doesn't do anything; the current state remains. Transition::Pop deletes the current state from the stack, and the previous one takes over. Transition::Push introduces a new state, preserving the current underneath. And so on.

There's actually two types of "state" (as in, data managed by the app): local and global. Local "state" is owned by the struct implementing the State trait. This is usually stuff like any UI panels (which we'll get to soon) and stuff like which road we're editing, or what building we're examining. But usually an app has a bunch of "state" that lasts for the entire lifetime of the program -- the map, the simulation, global settings. This stuff can change through the program, like loading a new map, but every single State probably wants to use it. This global stuff is stored in the App struct, which gets plumbed around. So, each State has fn event(&mut self, ctx: &mut EventCtx, app: &mut App) -> Transition -- self is the local State on the stack, ctx is a handle into the generic widgetry system, and app is that global "state". And there's also fn draw(&self, g: &mut GfxCtx, app: &App), with GfxCtx being the hook to draw stuff. Note draw uses immutable borrows; generally you shouldn't modify anything while drawing. (When you need to, like for caching, usually RefCell is the answer.)

This system of states and transitions mostly works, but there's one super awkward problem. Sometimes, state1 needs to push on state2 in order to prompt the user for input (free-form text, a menu, or even something more complicated) and use the result of state2 in order to do something else. In normal programming, this would just be calling a function and using the return value. How do we make that work with the event/draw interface? The answer is for state2 to return Transition::Pop and Transition::ModifyState, downcast the previous State into a particular struct, and shove the return value somewhere. This is incredibly gross, but I'm not sure what else to do.


What's been described so far is kind of only useful for drawing and interacting with stuff in "map-space", aka, the scrollable canvas. What about normal GUI elements that live in "screen-space" on top of everything else -- buttons, dropdowns, checkboxes, frobnozzlers? There needs to be a way to create these, arrange them in some kind of layout, and use them for interaction. There's a bunch of ways that GUI frameworks manage the problem of synchronizing application "state" with the UI widgets, and it's more complex than usual in Rust, because you hit crazy lifetime and borrowing issues if you try to do anything with callbacks.

So sticking to the widgetry philosophy of seeing how far the low-level abstractions stretch, widgets are just temporary "state" managed by a State. They're always managed as part of a Panel, even if you have just a single button. Constructing Panels is hopefully straightforward from examples; you assemble a tree of rows and columns, with some occasional styling and layouting hints thrown in. Underneath, widgetry uses stretch for CSS Flexbox-style layouting. There are some quirks, most of which we've worked out and hopefully papered over (like padding and margin don't work on most widgets directly; you have to wrap them in a Widget::row or Widget::col).

So how do you know if a button has been clicked, a toggle toggled, a slider slidden, a frobnozzler frobnuzzled? In some languages, you might expect callbacks, but here in widgetry, you have to explicitly ask. Most States will match self.panel.event(ctx) somewhere near the top. This takes the current event (a low-level keypress or mouse movement) and lets all of the widgets inside the Panel possibly use it. If the event caused the widgets to do something interesting, the entire Panel will return Some(Outcome::Clicked("button name")) or Some(Outcome::Changed("spinner name")). The State code can then interpret that UI-level event appropriately.

Currently, the widgets in a Panel are identified by lovely type-unsafe hardcoded strings. This isn't the best, but in practice, it's rare to get out of sync between the two places in one file that talk about the same widget.

Some widgets have more information than just "the button was clicked". Whenever you need to, you can just query their state -- self.panel.slider("time").get_percent(), self.panel.dropdown_value("mode"), self.panel.spinner("duration"). These last two are generic, and the compiler usually infers the type of the value contained. (Internally, we just downcast to that type, so you'd get a runtime panic if you mess up.)

Updating panels

Sometimes you need to change a Panel, often in response to something done on that panel -- like say you toggle between showing raw data points versus aggregating in a heatmap, and want to expose extra heatmap settings. There are two choices for how to do this: build a new panel entirely, or replace one widget.

Often it's simplest to just split the method that builds a Panel into its own method, and call it again with some different parameters. Very very occasionally when you take this approach, you'll need to do new_panel.restore(ctx, &self.panel); self.panel = new_panel; to retain internal widgetry state, such as "this scrollbar is in the process of being dragged by the mouse."

Or you can just replace one widget (which may be an entire row or column of stuff; it's just based on the string ID you specify). self.panel.replace(ctx, "edit", new_button) does the trick.


The free-formed nature of State::event is sometimes overwhelming; how do you order all of the things that need to happen? You could also implement SimpleState when you only have a single Panel. This gives a slightly more opinionated interface, telling you when a button was clicked, slider was changed, when the mouse was moved, etc. If you're confused, see how it implements fn event -- it's just organizing some typical different things that happen to handle an event.

Higher layers

Someday we want to release widgetry for general use to the Rust community. But there's also lot of code shared between game, fifteen_min, and other apps that handles UI concerns specific to map_model, which isn't something most people will care about. This stuff goes in map_gui.

Lots of the map_gui code implements widgetry States, but those are parameterized by a particular App struct. Since each of the top-level crates uses a different struct, there's an AppLike trait to handle this level of indirection. If it walks like a duck...

Rendering maps

There are generally two strategies for drawing the map. In the unzoomed view, we tend to have a single Drawable for all buildings, another for all roads, etc. In the zoomed view, only a few map elements (bus stops, lanes, buildings) are visible at a time, so we can afford to show more detail, and store a Drawable per object. In fact, there's no need to even calculate all of this zoomed-in detail upfront; many maps are huge, and a player won't zoom into every section in a particular session. So internally, most of the Renderables use RefCell and lazily calculate what to draw.

DrawMap internally manages a quadtree to figure out what to draw and to help figure out what object is under the mouse cursor.

async madness

If you want to see some scary Rust, check out load.rs. There's a fatal flaw with the core winit event loop -- it was written before Rust async landed. It's generally bad to spend more than a few milliseconds in either event or draw; the app will appear sluggish, and at some point, the window manager warns that the app is frozen. This happens when we synchronously/blockingly load a big file from disk, or worse, from the network.

We're starting to figure out some of the workarounds. When there's "proper" async Rust code, like for downloading a file on native, the trick is to spawn a separate thread to execute the async block. The main thread (where event and draw and most things run) stashes a Future inside of the State. In event, it non-blockingly polls the future to see if its done. If not, immediately return control to the window manager and just ask it to wake things up again in a few milliseconds. Proper loading screens can be drawn this way.

All of this gets more complicated on the web, because you can't really spawn a thread without somme web worker magic. It's still possible to make async HTTP fetches work, but it's not all wired together yet.

Map model

A/B Street transforms OpenStreetMap (OSM) data into a detailed geometric and semantic representation of the world for traffic simulation. This chapter describes that map model, with the hopes that it'll be useful for purposes beyond this project.


A Map covers everything inside some hand-drawn boundary, usually scoped to a city or a few of a city's districts. Unlike OSM, it doesn't cover the entire world; it only has areas specifically extracted for some purpose.

A map consists of many objects. Mainly, there are roads, broken down into individual lanes, and intersections. A road is a single segment connecting exactly two intersections (as opposed to OSM, where a single "way" may span many intersections). Lanes within a road have a specific type, which dictates their direction of travel (or lack of travel, like on-street parking) and uses. Sidewalks are represented as bidirectional lanes. Roads connect at intersections, which contain an explicit set of turns, each linking a source lane to a destination lane.

Maps also contain parking lots and buildings, which connect to the nearest driveable lane and a sidewalk. Maps have water and park areas, only used for drawing. They also represent public transit stops and routes.

How is a map used?

Unlike some GIS systems, maps don't use any kind of database -- they're just a file, anywhere from 1 to ~500MB (depending on the size of their boundary). Once loaded into memory, different objects from the map can be accessed directly, along with a large API to perform various queries.

Most of the map's API is read-only; once built, a map doesn't change until user-created edits are applied.

The pipeline to import a map from OSM data (and also optional supplementary, city-specific data) is complex and may take a few minutes to run, but it happens once offline. Applications using maps just read the final file.


Why use A/B Street's map model instead of processing OSM directly?

TODO: Order these better. For each one, show before/after pictures

Area clipping

Bodies of water, forests, parks, and other areas are represented in OSM as relations, requiring the user to stitch together multiple polylines in undefined orders and handle inner holes. A/B Street maps handle all of that, and also clip the area's polygon to the boundary of the entire map -- including coastlines.

Road and intersection geometry

OSM represents roads as a polyline of the physical center of the road. A/B Street infers the number and type of lanes from OSM metadata, then creates individual lanes of appropriate width, each with a center-line and polygon for geometry. At intersections, the roads and lanes are "trimmed back" to avoid overlapping, and the "common area" becomes the intersection's polygon. This heuristic process is reasonably robust to complex shapes, with special treatment of highway on/off-ramps, although it does still have some bugs.


At each intersection, A/B Street infers all legal movements between vehicle lanes and sidewalks. This process makes use of OSM metadata about turn lanes, inferring reasonable defaults for multi-lane roads. OSM turn restriction relations, which may span a sequence of several roads to describe U-turns around complex intersections, are also used.

Parking lots

OSM models parking lots as areas along with the driveable aisles. Usually the capacity of a lot isn't tagged. A/B Street automatically fills paring lots with individual stalls along the aisles, estimating the capacity just from this geometry.

Stop signs

At unsignalized intersections, A/B Street infers which roads have to stop, and which have right-of-way.

Traffic signals

OSM has no way to describe how traffic signals are configured. A/B Street models fixed-timer signals, automatically inferring the number of stages, their duration, and the movements that are prioritized and permitted during each stage.


A/B Street can determine routes along lanes and turns for vehicles and pedestrians. These routes obey OSM's turn restriction relations that span multiple road segments. They also avoid roads that're tagged as not allowing through-traffic, depending on the route's origin and destination and vehicle type. The pathfinding optionally makes use of contraction hierarchies to greatly speed up query performance, at the cost of a slower offline importing process.

Bridge z-ordering

OSM tags bridges and tunnels, but the roads that happen to pass underneath bridges aren't mapped. A/B Street detects these and represents the z-order for drawing.


Similar to areas, A/B Street consolidates the geometry of OSM buildings, which may be split into multiple polygons. Each building is also associated with the nearest driveable lane and sidewalk, and metadata is used to infer a land-use (like residential and commercial) and commercial amenities available.

Experimental: public transit

A/B Street uses bus stops and route relations from OSM to build a model of public transit routes. OSM makes few guarantees about how the specifics of the route are specified, but A/B Street produces specific paths, handling clipping to the map boundary.

... All of this isn't the case yet, but it's a WIP!

Experimental: separated cyclepaths, tramways, and walking paths

Some cyclepaths, tram lines, and footpaths in OSM are tagged as separate ways, with no association to a "main" road. Sometimes this is true -- they're independent trails that only occasionally cross roads. But often they run alongside a road. A/B Street attempts to detect these and "snap" them to the main road as extra lanes.

... But this doesn't work yet at all.

Map model details

A/B Street builds a rich representation of a city map using OpenStreetMap (OSM) and other sources. This chapter describes how.

TODO: Integrate pictures from these slides.

This recorded presentation covers some of this.

The map

A single city is broken down into different pieces...

A/B Street comes with a few maps, each defined by a bounding/clipping polygon for some portion of Seattle. Each map has these objects:

  • Roads: A single road connects two intersections, carrying OSM metadata and containing some child lanes.
  • Lanes: An individual lane of traffic. Driving (any vehicle), bus-only, and bike-only lanes have a direction. On-street parking lanes don't allow any movement, and they have some number of parking spots. Sidewalks are bidirectional.
  • Intersections: An intersection has references to all of the incoming and outgoing lanes. Most intersections have a stop sign or traffic signal policy controlling movement through it.
    • Border intersections on the edge of the map are special places where agents may appear or disappear.
  • Turns: A turn connects one lane to another, via some intersection. (Sidewalks are bidirectional, so specifying the intersection is necessary to distinguish crosswalks at each end of a sidewalk.)
  • Buildings: A building has a position, OSM metadata, and a front path connecting the edge of the building to the nearest sidewalk. Most trips in A/B Street begin and end at buildings. Some buildings also contain a number of off-street parking spots.
  • Area: An area has geometry and OSM metadata and represents a body of water, forest, park, etc. They're just used for drawing.
  • Bus stop: A bus stop is placed some distance along a sidewalk, with a pointer to the position on the adjacent driving or bus lane where a bus stops for pick-up.
  • Bus route: A bus route has a name and a list of stops that buses will cycle between. In the future, they'll include information about the frequency/schedule of the route.
  • Parking lot: A parking lot is connected to a road, has a shape, and has some internal driving "aisles." The number and position of individual parking spots is auto-generated.

Coordinate system

A/B Street converts (longitude, latitude) coordinates into a simpler form.

  • An (x, y) point starts with the top-left of the bounding polygon as the origin. Note this is screen drawing order, not a Cartesian plane (with Y increasing upwards) -- so angle calculations account for this.
  • The (x, y) values are f64's trimmed to a few decimal places, with way more precision than is really needed. These might become actual fixed-point integers later, but for now, a Pt2D skirts around Rust's limits on f64's by guaranteeing no NaN's or infinities and thus providing the full Eq trait.
  • A few places in map conversion compare points using different thresholds, usually below 1 meter. Ideally these epsilon comparisons could be eliminated in favor of a fixed-point integer representation, but for now, explicit thresholds are useful.


Ideally, the finalized maps would satisfy a list of invariants, simplifying the traffic simulation and drawing code built on top. But the input data is quite messy and for now, most of these aren't quite guaranteed to be true.

  • Some minimum length for lanes and turns. Very small lanes can't be drawn, tend to break intersection polygons, and may lead to gridlocked traffic.
  • Some guarantees that positions along adjacent lanes actually match up, even though different lanes on the same road may have different lengths. Examples include the position of a bus stop on the sidewalk and bus lane matching up.
    • Additionally, parking lanes without an adjacent driving lane or bus stops without any driving or bus lanes make no sense and should never occur.
  • Connectivity -- any sidewalk should be reachable from any other, and most driving lanes should be accessible from any others. There are exceptions due to border intersections -- if a car spawns on a highway along the border of the map, it may be forced to disappear on the opposite border of the map, if the highway happens to not have any exits within the map boundary.


For a single mode, each lane is connected to two intersections. Turns connect two lanes. There are no turns between sidewalks and driving/bike/bus lanes.

All buildings and parking lots have driveways. This must connect to a sidewalk, allowing pedestrians to enter/exit that object. The driveway OPTIONALLY connects to the nearest driveable lane. This allows cars to enter/exit that object for parking.

Public transit stops are located somewhere on a sidewalk. They're associated with a driveable position where the bus or train stops. In the future, this will need to account for dedicated surface-level platforms and for underground transit stations, likely associated with a building.

There's a concept of "parking blackholes." If you treat every road as bidirectional without access restrictions, then the graph is connected. But the more detailed view has to factor in one-way roads and things near the map border. These blackholes influence where cars will try to look for parking (since we don't want them entering a blackhole and getting stuck) and also, for temporary/unintentional reasons, where pedestrian<->bicycle transitions will happen.


This chapter describes the process of transforming OSM extracts into A/B Street's map model. The steps are:

  1. A large .osm file is clipped to a hand-drawn boundary region, using osmconvert
  2. The convert_osm crate reads the clipped .osm, and a bunch of optional supplementary files, and produces a RawMap
  3. Part of the map_model crate transforms the RawMap into the final Map
  4. Other applications read and use the Map file

The importer crate orchestrates these steps, along with automatically downloading any missing input data.

The rest of these sections describe each step in a bit more detail. Keeping the docs up-to-date is hard; the best reference is the code, which is hopefully organized clearly.

Don't be afraid of how complicated this pipeline seems -- each step is relatively simple. If it helps, imagine how this started -- just chop up OSM ways into road segments, infer lanes for each road, and infer turns between the lanes.

From OSM to RawMap (convert_osm crate)

The first phase of map building reads in data from OSM files and a few others, producing a serialized RawMap.

Only major steps are described; see the code for the rest.


Read .osm, extracting the points for road-like ways, buildings, and areas

  • Areas usually come from a relation of multiple ways, with the points out of order. Gluing all the points together fails when the .osm has some ways clipped out. In that case, try to trace along the map boundary if the partial area intersects the boundary in a clear way. Otherwise, just use a straight line to try to close off the polygon.
  • Also read traffic signal locations and turn restrictions between OSM ways


Split OSM ways into road segments

  • OSM ways cross many intersections, so treat points with multiple ways and the points at the beginning and end of a way as intersections, then split the way into road segments between two intersections.
  • This phase remembers which road segment is the beginning and end of the OSM way, for per-lane turn restrictions later
  • Apply turn restrictions between roads here. Since OSM ways cross many intersections, the turn restrictions only apply to one particular road segment that gets created from the way. Make sure the destination of the restriction is actually incident to a particular source road.


Clip the map to the boundary polygon

  • osmconvert options preserve ways that cross the boundary
  • Trim roads that cross the boundary. There may be cases where a road dips out of bounds, then immediately comes back in. Disconnecting it isn't ideal, but it's better to manually tune the boundary polygon when this happens than try to preserve lots of out-of-bounds geometry.
  • Area polygons are intersected with the boundary polygon using the clipping crate

Road/intersection geometry: RawMap to InitialMap

The remainder of map construction is done in the map_model crate. There's one intermediate structure between RawMap and Map, called InitialMap.

  • make/remove_disconnected.rs: Remove disconnected roads
    • Just floodfill from some road, assuming all roads are bidirectional, to get different partitions.
    • Remove roads from all but the largest partition
  • make/initial/mod.rs and make/initial/lane_specs.rs: Interpret OSM tags to figure out what lanes are on each side of each road, also figuring out the total width of the road.
  • make/initial/geometry.rs: Figure out the polygon for each intersection, and trim back road center-lines to end at a face of the polygon.
    • For every road touching the intersection, get the polyline of each side, based on the road's width
      • See appendix for how to shift polylines
    • Sort all the polylines by the angle to the intersection's shared point
    • Intersect every polyline with every other polyline
      • More specifically -- the second half of each polyline, to get the correct collision point
      • Look at the perpendicular infinite line to the collision point on the shifted polyline, then find where it hits the original center line. Trim back the center line by the max distance from these collisions.
    • Compute the intersection's polygon by considering collisions between adjacent roads' polylines
    • Deal with short roads and floating point issues by deduping any adjacent points closer than 0.1m

InitialMap to Map

Still in the map_model crate.

  • map.rs's make_half_map: Expand roads to lanes, using the list of lane types from before
  • make/turns.rs: Generate turns for every intersection.
    • Vehicle turns (for cars, bikes, buses)
      • Consider every pair of roads in the intersection. Try to match up lane types -- if there's a bike lane on both roads, don't add a turn from driving->bike or bike->driving. If there's not, then fallback to transitions between different lane types.
      • Classify the turn based on the difference between the angle of the incoming lane's last line and the outgoing lane's first line
        • For straight turns, use the Cartesian product to link every incoming with every outgoing lane. If the indices dont match up, the turn becomes a LaneChangeLeft or LaneChangeRight turn. This is used later for intersection policies to prioritize turns appropriately.
        • Right and left turns only originate from the one lane on the appropriate side
    • Walking turns for pedestrians
      • Consider pairs of adjacent roads around the intersection
        • Make a crosswalk to the other side of the road, assuming there's a sidewalk on both sides
        • Make a shared sidewalk corner over to the adjacent road
        • If the adjacent road doesn't have a sidewalk on the close side, then consider skipping that road and making a crosswalk over to the next road. An example of this is a crosswalk over a highway on/off ramp.
    • Verify all the turns so far are unique
    • Filter by the OSM turn restrictions ("only straight" between road1 and road2)
    • Try to apply the OSM per-lane restrictions ("straight or left" from lane 3)
      • The number of lanes in the OSM metadata might not match up with how many lanes created
      • Some of these OSM tags are just completely wrong sometimes. If the filter makes an incoming lane lose all of its turns, then ignore that tag.
  • make/parking_blackholes.rs: Find well-connected roads near "blackhole" lanes.
    • Starting from most driving/biking lanes, most other lanes are reachable. Some aren't -- such as one-way highways inevitably leading from or to a border. These are "blackholes" -- pathfinding to or from here may fail.
    • Find the largest strongly-connected component (SCC) in the driving graph. From every other lane (a blackhole), floodfill both forwards and backwards to find the nearest driving lane part of the main SCC.
    • Later, if a car needs to park by a building on a blackhole road, it'll instead start searching for parking at the redirect. This prevents it from being forced to instead exit the map through a border.
  • make/buildings.rs: Match buildings up with sidewalks
    • Find the closest sidewalk polyline to each building's center. Then draw a straight line for the front path between the edge of the building and the sidewalk point.
    • Filter out buildings too far away from any sidewalk
    • The front path might cross through other buildings; this is probably not worth fixing.
  • make/buildings.rs: Same for parking lots
    • Similar process to match parking lots to nearest sidewalk and driving lane
    • Try to place parking spots along both sides of parking aisles
    • Filter out overlapping spots
  • make/bridges.rs: Find what roads lie beneath bridges, and update their Z-order accordingly for later drawing.
  • stop_signs.rs: Instantiate default stop sign policies
    • Rank incoming roads by OSM priority (arterial beats residential)
    • If there's only one rank, then make an all-way stop
    • Otherwise, the highest rank gets priority and others stop
      • Check if there are any conflicts based on this. If so, then fall-back to an all way stop.
  • traffic_signals.rs: Instantiate default traffic signal policies
    • Apply the first predefined policy that works.
      • 4-way 4 stage, 4-way 2 stage, 3-way 3-stage, degenerate policy for 2 roads, 2-stage for 4 one-ways
      • Fallback to a greedy assignment that just randomly starts a new stage, adds all compatible turns, and repeats until all turns are present priority in some stage.
  • pathfind/mod.rs: Prepare pathfinding
    • A/B Street uses contraction hierarchies (CH) for fast routing, using the fast_paths crate.
    • pathfind/vehicle.rs: For cars, bikes, buses
      • There's a separate CH for cars, buses, and bikes, since they can use slightly different sets of lanes.
      • Building the CH for buses and bikes is much faster than the one for cars, because the algorithm can re-use the node ordering from the first CH.
      • Every lane is a node in the graph, even if it's not an appropriate lane type -- it might change later, and reusing orderings is vital for speed.
      • If two lanes are connected by a turn, then there's an edge in the graph.
        • The edge weight is the length of the lane and turn. Later this could take into account speed limit, penalize lane-changing and left turns, etc.
    • pathfind/walking.rs: For pedestrians
      • Only sidewalk lanes are nodes in the graph -- sidewalks can't ever be changed in A/B Street, so there's no concern about reusing node orderings.
      • All turns between two sidewalks become edges, again using length
      • When actually pathfinding, we get back a list of sidewalks. The actual paths used in the traffic simulation specify forwards or backwards on a sidewalk. Looking at adjacent pairs of sidewalks lets us easily stitch together exact directions.
  • make/bus_stops.rs: Match bus stops with a sidewalk
    • Also precompute the position where the bus stops on the adjacent driving or bus lane.
    • This "equivalent position on another lane" process has a few weird cases, since two lanes on the same road might have different lengths. Right now, the same distance from the start of the lane is used, with clamping for shorter lanes. Ideally, the position would be found by projecting a perpendicular line out from one lane to the other.
  • make/bus_stops.rs: Finalize the list of bus routes
    • Between each pair of adjacent bus stops, run pathfinding to verify there's actually a path for the bus to follow. If any are disconnected, remove the bus route
    • Remove bus stops that have no routes serving them.
  • pathfind/walking.rs: Precompute the CH for pedestrians who will use buses
    • Nodes in the graph are sidewalks and every bus stop
    • There's an edge with weight 0 between a bus stop and its sidewalk
    • There's also an edge with weight 0 between bus stops that're adjacent via some route. Ideally this weight would account for the time until the next bus and the time spent on the bus, etc.
    • Later when figuring out which bus to use for a pedestrian, the resulting list of nodes is scanned for the first and last bus stop along the same route.

Development tricks

  • Separate phases for fast incremental development
    • Don't reimport all data from OSM every time there's a change to part of the map construction code!
    • For slow steps that don't change often, make them separate binaries -- hence convert_osm being separate from the rest.
  • Don't be afraid of manual intervention
    • The data isn't perfect. It's easy to spend lots of time fiddling with code to automatically handle all problems
    • Instead of automatically resolving problems, prefer good tooling for finding and specifying fixes
    • Be careful of derivative structures that could get out of sync with OSM. Prefer contributing real fixes to OSM.
  • Screenshot diff testing
    • When working on the code for intersection geometry, it's easy to check a few example cases get fixed by some change. But what if another part of the map regresses somehow?
    • Take screenshots of the entire map, keep the checksums under version control, look at the diffs visually, and manually verify any changes.
    • Implementation details: One huge gif or png is too slow to read and write, so take a bunch of tiled screenshots covering everything. Amusingly, rendering to a file with glium is slow unless compiling in release mode (which isn't an option for quick incremental development). So instead, pan to each section of the map, render it, call an external screenshot utility, and move on -- just don't wiggle the mouse during this process!
  • Different IDs for objects make sense during different phases
    • For the final product, lanes and such are just a contiguous array, indexed by numeric IDs.
    • But sometimes, we need IDs that're the same between different boundary polygons of maps, so that player edits can be applied anywhere. Using (longitude, latitude) pairs hits floating-point serialization and comparison issues, so referring to roads as (OSM way ID, OSM node ID 1, OSM node ID 2) works instead.

Appendix: PolyLines

Add some pictures here to demonstrate how polyline shifting works, the explode-to-infinity problem, and the bevel/miter fix.

Live edits

A key feature of A/B Street is the player editing the map and seeing how traffic responds. The possible edits include:

  • Change lane types (driving, bus, bike, parking -- sidewalks are fixed)
  • Change speed limits
  • Reverse a lane
  • Change a stop sign policy (which roads have a stop sign and which have priority)
  • Change a traffic signal policy

The map conversion process outlined above takes a few minutes, so reusing this process directly to compute a map with edits wouldn't work at all for real gameplay. Instead, the process for applying edits is incremental:

  • Figure out the actual diff between edits and the current map
    • This is necessary for correctness, but also speeds up a sequence of edits made in the UI -- only one or two lanes or intersections actually changes each time. Of course when loading some saved edits, lots of things might change.
  • For any changed roads, make sure any bus stop on it have a good pointer to their equivalent driving position for the bus.
  • For any modified intersections, recompute turns and the default intersection policies
  • Recompute all the CHs for cars, buses, and bikes -- note sidewalks and bus stops never change
    • This is the slowest step. Critically, the fast_paths crate lets a previous node ordering be reused. If just a few edge weights change, then recomputing is much faster than starting from scratch.
    • While making edits in the UI, we don't actually need to recompute the CH after every little tweak. When the player exits edit mode, only then do we recompute everything.

A list of lanes and intersections actually modified is then returned to the drawing layer, which uploads new geometry to the GPU accordingly.

A/B Street's map model as a platform

A/B Street's representation of a city, built mostly from OSM and lots of heuristics, is likely useful to other projects. This doc brainstorms what it would look like to properly expose it to other users.

To sum up what the map model provides: geometry + semantics.

Use cases

  • Different UIs (particularly 3D / VR) for exploring cities as they are or as they could be, like Streetmix 3D and Complete Street Rule
  • Importing slices of a city as assets into a game engine like Godot
  • A new OSM viewer/editor, particularly focused on POIs
  • Something focusing on 15-minute neighborhoods, with isochrones and nearby amenities

TODO: Give a quick Python example of what interacting with the end goal could look like.

Just data is not enough

At first glance, the existing Map structure could be written to some format with a nicely documented schema. This would certainly be useful, but it's not nearly enough. Interpreting the data sometimes requires lots of code, which already exists -- so why not expose it to users as well?

Examples in OSM where I wish "standard libraries" existed to interpret the data:

A/B Street solves these problems (or at least it tries to), but by itself, the resulting data isn't always useful. So some examples of where a library would be needed too:

  • Pathfinding. ABST does lots of work especially to handle "live" map edits and cheaply regenerate contraction hierarchies. Also, pathfinding requires obeying OSM turn restrictions that span multiple roads -- this prevents even plain old Dijkstra's from working correctly.
  • Getting geometry in different forms. Lanes are stored as a PolyLine, but what if a consumer wants the thickened Polygon, either as points, or maybe even pre-triangulated vertices and indices?

How would an API/library work?

The traditional approach is to link against part of A/B Street as a library and call it through language-specific bindings. The more language-agnostic option is defining an API (maybe JSON or protobuf) and having clients run a local A/B Street server, making HTTP requests to it. This is like the "sidecar" pattern in microservice-land.


Really have to think through this carefully. Some examples of big changes on the horizon:

  • Additive: separate cycleways and tramways. Likely no schema change.
  • Modify: traffic signals will get more complex
  • Modify: we'll likely try again to merge tiny intersections together, which would get rid of the current guarantees that a road/intersection is associated to one particular OSM object


Clients should be able to opt into different data layers. For example, A/B Street strips out OSM building tags right now to keep filesizes small. But an OSM viewer would want to keep this (and likely discard the large contraction hierarchies). So some pieces of the map model need to be teased apart into optional pieces, and probably loaded in as separate files.

The bigger vision

Depending what other open source projects are on board, the general idea is to start assembling an ecosystem of libraries/tooling to make it easier to build new things off of open GIS data.

The end state might look like this. A few separate applications would exist, all running both natively and in the browser:

  • A/B Street the game, more or less in its current form
  • A new OpenStreetMap viewer, likely focused on visualizing roads and points-of-interest in detail
  • The street parking OSM editor, and other OSM editors specialized for mapping certain things
  • A new app focusing on 15-minute neighborhoods, using isochrones to show amenities available nearby
    • Ideally, allow editing current land use / zoning, to let people explore how new policies might get closer to a 15-minute neighborhood.
    • Possibly GOAT does all of this already, and this new thing shouldn't be built
  • A new app for creating story maps, showing events that occur over time, with lots of detail about the surrounding environment

All of these would make use of some common libraries, which should be extracted out cleanly from A/B Street today:

  • the map model and OSM importer
  • the widgetry UI library
  • some common code for specifically interacting with maps in widgetry
  • a tool to generate a traffic demand model from OSM data, optional census data, etc
  • the discrete-event traffic simulation that A/B Street uses today
  • core geometry/utility libraries

But note only the first application would use things like the simulation library. The point of more cleanly modularizing these pieces is to make it easier for new people to build different pieces, without having to understand and be coupled to everything else. Also, as appropriate, these pieces should use common data formats (like shared-row) to be interoperable with Streetmix, Complete Streets, etc.

A/B Street's Traffic Simulation

This article describes how cars, bikes, buses, and pedestrians are modeled in A/B Street. All code lives in the sim crate.

This recorded presentation covers some of this.

Discrete-event simulation

The traffic simulation models different agents (cars, bikes, buses, pedestrians, and intersections) over time. Agents don't constantly sense and react to the world every second; instead, they remain in some state until something interesting happens. This is a discrete-event architecture -- events are scheduled for some time in the future, and handling them changes the state of some agents. The core simulation loop simply processes events in order -- see scheduler.rs and the step method in sim.rs.


(Note: Cars, bikes, and buses are all modeled the same way -- bikes just have a max speed, and buses/bikes can use restricted lanes.)

Cars move through a sequence of lanes and turns (movements through an intersection). They queue and can't over-take a slow lead vehicle. The main simplifying assumption in A/B Street is that cars can instantly accelerate and decelerate. This wouldn't model highway driving at all, where things like jam waves are important, but it's reasonable for in-city driving. The essence of scarcity is the capacity on lanes and the contention at intersections. What happens in between isn't vital to get exactly right.

A car has a few states (mechanics/car.rs):

  • Crossing some distance of a lane/turn over some time interval
  • Queued behind another car on a lane/turn
  • WaitingToAdvance at the end of a lane, blocked on an intersection
  • A few states where the car stays in one place: Parking, Unparking, and Idling (for buses at a stop)

State transitions happen in mechanics/driving.rs. This is best explained by an example sequence:

  • A car enters the Unparking state, taking a fixed 30s to exit a parking spot and enter the adjacent driving lane. The driving lane is blocked during this time, to mimic somebody pulling out from a parallel parking spot.
  • The car is now fully somewhere on the driving lane. It enters the Crossing state, covering the remaining distance to the end of the road. The time interval is calculated assuming the car travels at the max speed limit of the road.
  • After that time, the car checks if there's anybody in the queue before it. Nope? Then it attempts to initiate a turn through the intersection, but the stop sign says no, so the car enters the WaitingToAdvance state.
  • Some time later, the stop sign wakes up the car. The car starts the turn, entering the Crossing state again.
  • After finishing the turn, the car starts Crossing the next lane. When it's finished, it turns out there are a few cars ahead of it, so it enters the Queued state.
  • When the lead vehicle directly in front of the car exits the lane, it wakes up the car, putting it in the Crossing state, starting at the appropriate following distance behind the lead vehicle. This prevents the car from immediately warping to the end of the lane when the lead vehicle is out of the way.
  • And so on...

Exact positions

For a discrete-event simulation, we don't usually care exactly where on a lane a car is at some time. But we do need to know for drawing and for a few cases during simulation, such as determining when a bus is lined up with a bus stop in the middle of a lane. mechanics/queue.rs handles this, computing the distance of every car in a lane. For cars in the Crossing state, we linearly interpolate distance based on the current time. Of course, cars have to remain in order, so Queued cars are limited by the lead vehicle's position + the lead vehicle's length + a fixed following distance of 1m.

Another case where we need to know exact positions of cars is to prevent the first vehicle on a lane from hitting the back of a car who just left the lane. All vehicles have length, and position is tracked by the front of the car. When a car's front leaves a lane, its back is still partly in the lane. Logically, the new lead car in the lane still needs to act like it's Queued. So each lane keeps a "laggy head", pointing to the car with its back partly in the lane. After the laggy head has made it sufficient distance along its new turn or lane, the laggy head on the old lane can be erased, unblocking the lead vehicle. This requires calculating exact distances and some occasionally expensive cases where we have to schedule frequent events to check when a laggy head is clear.


Lane-changing (LCing) deserves special mention. A/B Street cheats by not allowing it on lanes themselves. Instead, at intersections, cars can perform turns that shift them over any number of lanes. These LCing turns conflict with other turns appropriately, so the contention is still modeled. Why do it this way? In a previous project, I tried opportunistic LCing. If a car had room to warp to the equivalent distance on the adjacent lane without causing a crash, it would start LCing, then take a fixed time to slide over, blocking both lanes throughout. This meant cars often failed to LC when they needed to, forcing them to reroute, botching their trip times. In many cases the cars would be permanently stuck, because pathfinding would return paths requiring LCing that couldn't be pulled off in practice due to really short roads. Why not try making the car slow down if needed? Eventually it might have to stop, which could lead to unrealistic gridlock. This LCing model was using a detailed discrete-time model with cars accelerating properly; maybe it's easier with A/B Street's simplified movement model.

Currently in A/B Street, cars will pick the least backed-up lane when there's a choice. They make this decision once when they reach the front of a queue; look for opportunistically_lanechange in router.rs. The decision could be improved.


Pedestrian modeling -- in mechanics/walking.rs is way simpler. Pedestrians don't need to queue on sidewalks; they can "ghost" through each other. In Seattle, there aren't huge crowds of people walking and slowing down, except for niche cases like Pike Place Market. So in A/B Street, the only scarce resource modeled is the time spent waiting to cross intersections.


I need to flesh this section out. See mechanics/intersections.rs for how stop signs and traffic signals work. Two things I need to fix before making this section interesting:

  • Only wake up relevant agents when a previous agent finishes a turn.
  • Don't let an agent start a low-priority turn (like an unprotected left) if it'll force a high-priority vehicle approaching to wait. The approaching vehicle is still in the Crossing state, so we need to notify intersections ahead of time of intended turns and an ETA.

One contributor to permanent gridlock is cars and bikes being stuck in an intersection, preventing conflicting turns from being performed. To help avoid this, one of the last checks that stop signs and traffic signals perform before accepting a new turn request is that the target lane has enough space for the new vehicle. This is "reserved" space, not necessarily currently occupied by vehicles in that lane. This accounts for other vehicles performing a turn bound for that lane. See try_to_reserve_entry in mechanics/queue.rs. When a car completely leaves a lane (determined by the "laggy head" described above), this space is freed, and blocked cars are woken up.

Appendix: discrete-time simulation

A/B Street's first traffic model was discrete-time, meaning that every agent reacted to the world every 0.1s. Cars had a more realistic kinematics model, accelerating to change speed and gradually come to a halt. Cars did a worst-case estimation of how far ahead they need to lookahead in order to satisfy different constraints:

  • Don't exceed any speed limits
  • Don't hit the lead vehicle (which might suddenly slam on its brakes)
  • Stop at the end of a lane, unless the intersection says to go

After fighting with this approach for a long time, I eventually scrapped it in favor of the simpler discrete-event model because:

  • It's fundamentally slow; there's lots of busy work where cars in freeflow with nothing blocking them or stopped in a long queue constantly check to see if anything has changed.
  • Figuring out the acceleration to apply for the next 0.1s in order to satisfy all of the constraints is really complicated. Floating point inaccuracies cause ugly edge cases with speeds that wind up slightly negative and with cars coming to a complete stop slightly past the end of a lane. I wound up storing the "intent" of an action to auto-correct these errors.
  • The realism of having cars accelerate smoothly didn't add value to the core idea in A/B Street, which is to model points of contention like parking capacity and intersections. (This is the same reason why I don't model bike racks for parking bikes -- in Seattle, it's never hard to find something to lock to -- this would be very different if Copenhagen was the target.) Additionally, the kinematics model made silly assumptions about driving anyway -- cars would smash on their accelerators and brakes as hard as possible within all of the constraints.

Travel demand

A/B Street simulates people following a schedule of trips over a day. A single trip has a start and endpoint, a departure time, and a mode. Most trips go between buildings, but the start or endpoint may also be a border intersection to represent something outside the map boundaries. The mode specifies whether the person will walk, bike, drive, or use transit. Without a good set of people and trips, evaluating some changes to a map is hard -- what if the traffic patterns near the change aren't realistic to begin with? This chapter describes where the travel demand data comes from.


A scenario encodes the people and trips taken over a day. See the code.


  • talk about vehicle assignment / parked car seeding

Data sources

Seattle: Soundcast

Seattle luckily has the Puget Sound Regional Council, which has produced the Soundcast model. They use census stats, land parcel records, observed vehicle counts, travel diaries, and lots of other things I don't understand to produce a detailed model of the region. We're currently using their 2014 model; the 2018 one should be available sometime in 2020. See the code for importing their data.


  • talk about how trips beginning/ending off-map are handled


This work is stalled. See the code. So far, we've found a population count per planning area and are randomly distributing the number of residents to all residential buildings in each area.

Proletariat robot

What if we just want to generate a reasonable model without any city-specific data? One of the simplest approaches is just to spawn people beginning at residential buildings, make them go to some workplace in the morning, then return in the evening. OpenStreetMap building tags can be used to roughly classify building types and distinguish small houses from large apartments. See the proletariat_robot code for an implementation of this.

Census Based

Trips are distributed based on where we believe people live. For the US, this information comes from the US Census. To take advantage of this model for areas outside the US, you'll need to add your data to the global population_areas file. This is one huge file that is shared across regions. This is more work up front, but makes adding individual cities relatively straight forward.

Preparing the population_areas file

See popdat/scripts/build_population_areas.sh for updating or adding to the existing population areas. Once rebuilt, you'll need to upload the file so that popdat can find it.


Collaborators at ASU are creating https://github.com/asu-trans-ai-lab/grid2demand, which does the traditional four-step trip generation just using OSM as input. The output of this tool can be directly imported into A/B Street. From the scenario picker, choose "Import Grid2Demand data" and select the input_agents.csv file.


Robin Lovelace is working on an R package to transform aggregate desire lines between different zones into A/B Street scenarios.

Desire lines (for the UK)

The UK has origin/destination (aka desire line) data, recording how many people travel between a home and work zone for work, broken down by mode. We have a tool to disaggregate this and create individual people, picking homes and workplaces reasonably using OSM-based heuristics. See the pipeline for details about how it works. The code that parses the raw UK data is here. If you have similar data for your area, contact me and we can add support for it!

Custom import

See here.

Modifying demand

The travel demand model is extremely fixed; the main effect of a different random number seed is currently to initially place parked cars in specific spots. When the player makes changes to the map, exactly the same people and trips are simulated, and we just measure how trip time changes. This is a very short-term prediction. If it becomes much more convenient to bike or bus somewhere, then more people will do it over time. How can we transform the original demand model to respond to these changes?

Right now, there's very preliminary work in sandbox mode for Seattle weekday scenarios. You can cancel all trips for some people (simulating lockdown) or modify the mode for some people (change 50% of all driving trips between 7 and 9am to use transit).



Here "gridlock" refers to the general problem of trips getting permanently stuck, preventing the full simulation from completing. Most of the work is tracked here.

The general lesson is: you can't code your way around all edge cases. The data in OSM often needs manual fixes. It's often useful to spend coding effort on tools to detect and fix OSM problems.


The choices in the movement model matter. Some gridlock is inherent to any system with queueing and conflicting turns. But in reality, people wiggle around partly blocked turns. And some of this comes from the treatment of the front/back of vehicles.

  • Short roads in OSM causing very weird geometry
  • Intersection geometry being too large, requiring too much time to cross
  • Unrealistic traffic patterns caused by everyone trying to park in one big garage (downtown) or take some alley (the UW soundcast issue)
  • Too many people try to take an unprotected left turn (often at a stop sign)
  • Bad individual traffic signals, usually at 5- or 6-ways
  • Groups of traffic signals logically acting as a single intersection
  • Separate traffic signals along a corridor being unsynchronized
  • Vehicles performing illegal sequences of turns
  • Vehicles are stuck with their plan and not reacting to traffic by changing route
  • Real traffic would result in a gridlock without a deliberate actions to avoid it. Such actions range from individual decisions of drivers to police manually controlling traffic. Intelligent avoidance of gridlock is not simulated and is extremely hard to simulate.
  • Vehicles will wait in lane filled with already waiting vehicles, even if there is a completely empty lane allowing travel in desired direction. It makes easier for entire lane between crossings to fill, contributing to gridlocks. Note that while this and other clearly stupid behaviors are clearly unrealistic, it is not trivial to implement more realistic and more efficient decisions.
  • Issues caused by the unrealistic lane-changing model
    • Two turns that go to the same lane (one going "straight", the other often a lane-change) conflict. The conflict is coarse, at the granularity of the entire intersection. So if vehicles are piled up in two lanes trying to merge into one, then one group is likely to go through as expected, but the second group will wait for the first to completely clear the intersection. Until then, it looks like a conflicting turn is being done.


Divide into implemented or not.

  • Synchronizing pairs of signals
  • Uber-turns
    • for interpreting OSM turn restrictions
    • for synchronizing a group of signals
    • for locking turn sequences
      • Once a vehicle starts an uber-turn, prevent others from starting conflicting turns on nearby intersections. Until groups of traffic signals are configured as one, this is necessary to prevent somebody from making it halfway through a sequence then getting blocked.
  • Cycle detector
  • block-the-box protection
    • the manual list of overrides
    • likely shouldn't apply during uber-turns
    • is it always fine to block the box at degenerate intersections?
  • hacks to allow conflicting turns at really broken intersections
  • manually timing signals
  • penalties for lane choice to make lane usage realistic

Not implemented

  • Dynamic rerouting
  • Allow multiple vehicles through intersection at once if there is enough space on lane where given vehicle is going. Currrently vehicles travel through crossings one by one (or, with --disable_block_the_box enabled - will enter crossing even if leaving it will be impossible).
  • Last resort: if someone's waiting on a turn >5m, just go.
  • Uber-turns
    • Group both stop sign and traffic signal intersections when looking for uber-turns. Even a single traffic signal surrounded by tiny roads with stop signs is causing problems.

Strategy for resolving

Because there are so many different causes all tangled together, my approach is to simplify the simulation as much as possible. A problem is much easier to understand and fix when it's isolated. I've been trying this to get the downtown weekday scenario to complete. A list of different techniques to simplify, in no particular order:

  • Use the --infinite_parking flag to just let everyone park directly in their destination buildings. This is useful since downtown has many large parking garages with high capacity, but I don't have a data source describing them.
  • Use the --disable_turn_conflicts flag, which greatly reduces realism, but lets conflicting turns happen simultaneously. (Even with this and other flags, downtown still gridlocks!) It also disables traffic signals, so bad inferred timing isn't an issue.
  • Use the --disable_block_the_box flag to workaround short roads.
  • If you notice problems forming from cars stacking up behind slower cyclists, there's no over-taking implemented yet. Use the scenario modifiers to convert all biking trip to driving: --scenario_modifiers='[{"ChangeMode":{"to_mode":"Drive","pct_ppl":100,"departure_filter":[0.0,86400.0],"from_modes":["Bike"]}}]'
  • If all else fails, use the scenario modifiers to bluntly cancel some percentage of all trips.

A quick method for "disabling" buggy short roads is to use edit mode and close the lanes. This forces traffic to reroute around the problematic area, which might create other problems, but it can be useful.

Fixing data used in simulation

Give more examples of changesets.

Multi-modal trips

A single trip consists of a sequence of TripLegs -- walking, operating a vehicle (car or bike), and riding the bus. Depending whether a trip begins or ends at a border or building, there are many combinations of these sequences. This is a way to categorize them into three groups. I'm not sure it's the simplest way to express all the state transitons.

Walking-only trips


Trips starting from a border


Trips starting from a building


Spawning code overview

As of January 2021, starting a traffic simulation works like this:

  1. Something creates a Scenario, which defines a bunch of people. Each person has a schedule of trips, carrying them between TripEndpoints via some TripMode, leaving at some Time.
  2. When a scenario is instantiated, not much happens besides scheduling the trip to start at the appropriate time and filling out TripInfo in TripManager. Some state in StartTripArgs has to be plumbed forward in the Command::StartTrip.
  3. When the command gets run later, TripManager::start_trip happens. The origin, destination, and mode flow through TripSpec::maybe_new.
  4. TripSpec::to_plan further validates these and attempts to repair impossible plans. Each TripSpec is turned into a list of TripLegs.
  5. Each TripSpec has its own initialization logic to kick off the first leg of the trip.
  6. Most trips have multiple legs. When the first car, bike, or pedestrian reaches their goal, TripManager gets called with some sort of transition function to initiate the next leg of the trip, or declare the trip finished. These transition functions also record stats from that leg of the trip, like total blocked time.

What're the different TripSpec cases, and what TripLegs do they get turned into?

  • VehicleAppearing: Starts at a border. Drive, then maybe walk to the destination building.
  • SpawningFailure: No trip legs; just create and immediately cancel the trip.
  • UsingParkedCar: Starts at a building. Walk (to the parked car), drive, then maybe walk to the destination building (after parking again).
  • JustWalking: Just walk between two buildings/borders.
  • UsingBike: Starts at a building. Walk to the nearest bikeable lane, drive, then maybe walk to the destination building. (Note that starting a bike from a border uses VehicleAppearing.)
  • UsingTransit: Walk, ride the bus, maybe walk again. No transfers yet; only rides one bus between two stops.

TripManager has a whole bunch of transition functions:

  • car_reached_parking_spot: drive -> walk, unless the destination building is where we parked
  • ped_reached_parking_spot: walk -> drive
  • ped_ready_to_bike: walk -> bike
  • bike_reached_end: bike -> walk
  • ped_reached_building: walk -> done
  • ped_reached_bus_stop: walk -> wait or ride bus
  • ped_boarded_bus: waiting -> ride bus
  • person_left_bus: riding bus -> walk
  • ped_reached_border: walk -> done
  • transit_rider_reached_border: ride bus -> done
  • car_or_bike_reached_border: drive -> done

There are at least a few use cases motivating the cleanup of all of this structure:

  • Capping trips through congested areas. Sometimes this just changes the driving route, but sometimes it needs to cancel trips, convert driving trips to walking/transit, or delay the trip's start.
  • Multiple public transit rides in a single trip, aka transferring
  • Handling live map edits in the middle of a trip

Live edits

When the player edits the map, there's an efficient process for applying the edits at the map model and rendering layer. In the middle of a simulation, it's less obvious how to apply all edits. Most of the time currently, edits cause the simulation to reset to midnight. Applying edits to the sim without reset is important for running machine learning experiments and for improving the gameplay experience (by having more immediate feedback about the consequences of a change).

The UI has a dirty_from_edits bit to track when changes have been applied without reset. This lets us tell the player that by the end of the day, any score / results are tentative, because their edits might have a different effect earlier in the day.

What works today

Changes to traffic signals are simple -- incremental_edit_traffic_signal happens at the map layer, and then handle_live_edited_traffic_signals at the sim layer just resets the current stage to 0 if the previous configuration had more stages.

TODO: Recalculating paths

Many of the edits will influence routes. For trips that haven't started yet, there's nothing to do immediately. Paths are calculated right before the trip starts, so slight changes to the start/end of the path due to map edits (like where somebody starts biking, for example) are captured naturally.

For currently active trips, in some cases, rerouting would be ideal but not necessary (like if speed limits changed). In other cases -- like changing access restrictions, modifying lane types, closing intersections -- the route must be recomputed. As a simple first attempt, we could just cancel all active trips whose path crosses an edited road or intersection. Later, we can figure out rerouting.

And actually, the only other case to handle is ChangeRouteSchedule, which should just be rescheduling the StartBus commands.

TODO: Parking

What happens if you modify a parking lane while there are cars on it? For now, just delete them. Trips later making use of them will just act as if the car never had room to be spawned at all and get cancelled or fallback to walking.

A better resolution would be to relocate them to other parking spots. If the owner is home, it'd be neat to have them walk outside, move the car, and go back in. But this greatly complicates the simulation -- the edited lane is in a transition state for a while, it modifies schedules, the person might not be around, etc.


In some areas, lots of traffic is caused by people circling around the block in search of parking. And often, a great deal of space in a city is spent on street parking, surface lots, and garages. So, A/B Street attempts to model parking as a step at the beginning and end of every driving trip.

Types of parking

Every parking spot can be categorized as public or private. Anybody can use public spots, but only trips beginning or ending at a particular building can use private spots inside of it. There's no modeling yet of time limits or fees.

On-street parking acts as one lane of the road. The full length of the lane gets divided up into spots with a per-city configurable length, called street_parking_spot_length, with a default of 8 meters. This is the only type of parking that players can edit in-game, by changing lane types. Street parking is always public.

Parking lots are also always public. OSM includes the service "aisles" running through the lot, and A/B Street automatically packs in individual spots, winding up with an estimate of the capacity.


Off-street parking is part of a building. Physically this could mean a parking garage, a small private lot behind some apartments, or a few spots in a carport and driveway -- there's no distinction in A/B Street. Off-street parking can be public or private.

Data sources

On-street parking comes from OSM, using the parking:lane tags. This field is rarely mapped; see here to help with that. Different cities can optionally configure some percentage of residential roads to automatically infer a parking lane or two.

Parking lots only come from OSM. Individual spots and capacity is almost never mapped, so A/B Street doesn't use that data.

Public off-street parking is only configured for Seattle right now, and comes from this dataset.

I've yet to find any data that would help estimate private off-street parking. For now, this is per-city configuration, called FixedPerBldg. We could definitely do better by estimating building size. There's an exception for two Seattle maps currently -- the number of off-street spots is set to equal the number of driving trips that originate from that building over the course of the entire day. This is also unrealistic, but was some attempt at improving those two maps.


This seems like a good place to preface that A/B Street doesn't have any kind of car-sharing, ride-share, or even multiple people in one car yet.

Seeding cars

See seed_parked_cars for reference.

When a simulation starts at midnight, where are cars initially placed? That process first figures out how many cars are needed per building through the day. If 100 driving trips originate from that building, you might think the building needs 100 cars nearby. But many of these trips might actually arrive at the building earlier using a car, in which case, when the same person later departs, they'll take their car.

After this count per building is produced, we place each car as close as possible to its building, only going far away as we run out of capacity. We always prefer seeding a car inside a building than on the street or in a lot. The list of cars to seed is shuffled, so that roughly helps with fairness -- we don't fill in all of one building's cars first, then go the next -- that would arbitrarily place one building's cars closer.

There's also a detail in this randomization process about the available spots. The consequence is this: when a player makes a few changes to the amount of street parking in an area, the effects shouldn't percolate everywhere and change how cars are seeded far away -- unless the change adds/removes so much capacity that the effect really would spill out that far.

How people pick a spot

When a car gets to the last lane before its target building, it starts looks for a free spot. If there's one on the current lane, it goes for it. If not, it uses a breadth-first search, using distance, to explore from its current lane until it finds a free spot. To avoid many cars in one area all contending for the same spot, the distance used in the search has some randomized scaling applied. Omnisciently knowing where free spots are located far away isn't realistic, but attempting some kind of human-realistic random walk seems even harder to get right.

Infinite parking

If you pass --infinite_parking on the command line, every building gets unlimited public spots. This effectively removes the effects of parking from the model, since driving trips can always begin or end at their precise building (except for blackhole cases). This is useful if a particular map has poor parking data and you need to get comparative results about speeding up some trips. Often the A/B testing is extremely sensitive, because a parking space close to someone's destination is filled up quickly, slowing down the trip.


Vehicles don't know how to turn left (or right for UK folks) from a driveway yet. This means that some buildings and street parking lanes near map boundaries might not be reachable from most of the map. Consequently, even though there might be parking there, the simulation ignores it, because it's "blackholed".


Data viz

There are 3 tools in the game that'll help you understand the effects of how parking is modeled.


The parking occupancy layer shows where parking spots are currently available.


The parking efficiency layer shows how far away from their destination somebody was forced to park. If you see lots of dark red dots, then people wound up parking somewhere, then walking a long way to actually get to their building. This is probably unrealistic, likely due to bad guesses about capacity in different areas.


The parking overhead dashboard looks at every driving trip that included a parking phase. It measures how much of the total trip time was spent actually driving, vs looking for parking and covering any remaining distance by foot.

Project logistics

This has some background/logistics about the project.


The core group:

Collaborators from other projects:

Full credits


Graphics / design:

Software dependencies:




Lots of people are interested in this project, with very diverse backgrounds. This page isn't meant to be prescriptive or limit options; it's just a starter.


The biggest hurdle here is that A/B Street is written in Rust, a language that may have a high entry barrier. If you're more comfortable in Python, R, or something else, there are still ways to contribute -- particularly producing input data for A/B Street to simulate.

Machine/reinforcement learning

If you're a researcher looking for a complex multi-agent simulation with lots of things to optimize, you've found the right place. We have a simple JSON API already you can plug into, but we'd love to improve this based on your use case. Particularly cool ideas could be auto-tuning traffic signal timing or calibrating travel demand scenarios to real-world throughput/delay measurements.

Transportation modelling

Do you sling around population and aggregated origin/destination data with R or Python? Great, help us generate realistic travel demand models! Work in whatever language you like best; we'll settle on a common format.

Working on A/B Street itself

You'll need to learn at least some Rust to start here. We can help you a bit with that, especially by recommending starter projects or giving feedback in PRs, but this requires a fair bit of independence.

There's so many different types of things to work on -- see our hiring page and Github issues.

Data visualization / analysis

A/B Street generates loads of individual data through the simulation, but deciding what's important, how to aggregate things, what filters/controls to give to the user, and how to communicate trade-offs is really hard!

We can export data however's useful -- there's a few CSV files that the UI can export today. You can work on analyzing and visualizing the data using whatever tools you like. To actually implement a new dashboard in A/B Street, we'll implement it in Rust, but we can implement/copy what you produce elsewhere.

Some examples where we need help:

  • How to aggregate and compare the number of "risky events" somebody encounters on their trip, possibly using some kind of contingency matrix. See this PR for some discussion and open questions
  • How to show roads with more/less traffic. See this discussion


We wouldn't have made it to this point without loads of help from our UX designer. We could always use more help here. We use Figma for designs currently.


From what I've seen, no other street map attempt to include lane markings and as much detail as A/B Street. This means there's not much cartography to take inspiration from. Some places we need help:

  • Showing arterial vs small residential roads while zoomed in
  • Conveying more information about buildings and land use -- is it a small house, some apartments, a shop, a mall, a multi-use development?
  • Tuning the color-scheme, especially in a color-blind friendly way
  • Visualizing low traffic neighborhoods
  • Many people can't recognize the pedestrians and cyclists; how do we represent them better in our 2D top-down view? What should e-scooters look like?


We didn't start A/B Street with a clear set of product requirements or a storyboard for how it should work -- and we've been paying the price of this all along. But newer tools like the 15-minute explorer or the idea for a low-traffic neighborhood planner are focused and young enough where we could properly plan out the features and UI.

Game design

A/B Street tries to be a game, but it falls pretty short. Want to help us improve the tutorial? Have some ideas how to split up levels in the challenge mode? Know how to write a story? Help us!

Or maybe you can imagine a new spin-off game reusing some of the work that exists today. That's how 15-minute Santa happened!

Using A/B Street

You have an idea for fixing something in your city

That's why we made this! Just go try and make the change, initially without help from us. Write down your experience and all of the problems you hit. Then tell us the problems and any ideas for fixing them.

Then write up a proposal and start advocating for it!

You're an advocacy group

If you want to use A/B Street to argue for some change in your city, get in touch! It's easiest if you:

  • Tell us very clearly what you need
  • Draw a study area
  • Have a pretty specific idea of what roads/intersections you want to actually change
  • Have an idea of how you want to communicate your idea; browse our proposals for inspiration

Researching car-free cities

Maybe you're studying urban planning and think our software can help. We have some of the tools to get you started -- importing a new map, changing road configuration and access, measuring comparative results. But you'll probably have to work with us directly to fix up the map data and get a proper demand model. You can help us by giving usability feedback about what's hard to do and ideas for things to add. We're not experts in planning -- if you can tell us how software can help you do your job better, let's talk!

Usability studies

Every so often, we conduct formal usability studies. You'll spend an hour on a videocall doing something in A/B Street and vocalizing your thought process and what problems you hit. We'll use your feedback to find and fix problems.

We only run these every now and then; just contact us to get on the list for the next round.

Reporting bugs

You're bound to hit problems using our software -- just file an issue when you do.

Improving data quality

A/B Street relies on having an accurate and up-to-date model of your city. What do you do when it's wrong?

Fix incorrect lane data

The most common problem is that a road has the wrong number of lanes, or turn/bike/parking lanes are missing. If you know how to edit OpenStreetMap already, go fix it. If not, you can use the lane editor in A/B Street to fix the problem and make the road match reality. Send us your edits, and we can help make the fix in OSM.

OpenStreetMap mapper

Are you already involved in OSM and want to validate your work? Use our tool to help you visualize lane tagging easily. Keep in mind there are some problems with this viewer that might not reflect problems with the data in OSM -- particularly separate foot/cyclepaths and weird intersection geometry.

Or maybe you think your city is incredibly well-mapped already -- I bet it's missing one thing. Please map street parking -- we need this to guess road widths.

Project logistics

Networking / marketing

Know an advocacy group or city authority who would want to use our work? Make the connection!

Project management

Organizing all of the information, ideas, and people involved with this project is hard. Just writing this page and website took way too many tears and caffeine! We could also use help prioritizing work and coming up with requirements for new ideas.

Write documentation

This website is easy to edit. We could especially use help writing a user guide -- describe how different tools in A/B Street work, or work through examples of how to do stuff.


Our funding is pretty nonexistent, but if we had some, we could hire more people and build useful things faster. Do you know how to write NSF grants? Have you thought of a business model compatible with our values but that would let us financially sustain ourselves? Do you think we should be an LLC, a B corp, a non-profit, something else? Help needed!


A/B Street is almost entirely unfunded / self-funded. All contributors volunteer their time; thank you!

Funding received:

Dustin has personally funded two open-source libraries needed by A/B Street, but hopefully usable by other projects:


A/B Street is hiring a Rust engineer!

The project

A/B Street aims to get the average citizen more involved with local transportation planning and accelerate plans to make cities more friendly to people biking, walking, and using public transit. This massive undertaking involves building a realistic model of any city’s street network from open data, designing a user interface to easily edit streets and intersections, getting a traffic simulation to run with some degree of realism, using data visualization to explain the impacts of changes, and advocating for real changes using results from the software.

A/B Street has yet to focus on public transit. That’s where you come in. Today, there’s only preliminary and broken support for importing bus routes from OpenStreetMap and simulating people using them. A vision of what proper public transit support in A/B Street should do:

  • Let people draw entirely new bus and light rail routes, then understand the impact on individual people and the aggregate community.
  • Explore how small changes affect bus performance -- like changing bus stop locations, adding a bus-only lane, or configuring a traffic signal to prioritize buses.
  • Gamify the process of planning for public transit, by gradually introducing editing tools and a budget, to teach the public about the trade-offs involved with planning.

These goals will involve many tasks, such as incorporating data from OpenStreetMap and GTFS into A/B Street’s map model; modifying pathfinding to decide which route a person should use; defining and visualizing metrics for performance of a bus route; and changing the map model and UI to allow bus stops and routes to be created and edited.

Although your main focus will be public transit, A/B Street is a project with a wide scope, and there are other areas we need help, many of which good public transit support depends on:

Your qualifications

  • Can work independently, but still communicate effectively with the rest of the team. We need to focus our efforts on other parts of the project, but we'll keep up with your work closely
    • One way to demonstrate this: have you created your own project and maintained it for a while? Have you worked on other open source projects?
  • Can ramp up quickly on Rust, OpenStreetMap, GTFS, etc. If you already know these, great. If not, you should feel comfortable learning enough to start contributing to the code-base in a few days.
  • Are fine with (and happy to have) all of your work being Apache licensed and designed/discussed transparently on Github and Slack
  • Bonus if you have enough design sense to mock up UIs, but no worries if not

The job

Our funding is all but nonexistent, so I am personally carving out some of my savings for this. Because of this, the bar for hiring and my expectations are pretty high.

  • The amount is negotiable, but no more than about $30,000 USD total for the duration of the contract.
  • Ideally this contract would be full-time (35-40 hours a week) for about 6 months -- so that's about $5,000 per month.
  • Can't offer any benefits, sorry -- we're not a company yet!
  • Remote only. Anywhere in the world is fine, but most of us are in Seattle (PST).

If we can figure out how to financially sustain ourselves, this could become a permanent position, but don't count on it.


If you think you’d be a good fit, email dabreegster@gmail.com with any relevant material -- open source projects, a resume, etc. We can schedule a video call.

In lieu of a traditional interview, I’d like to ask you to submit a PR to A/B Street to make progress on one of our starter bugs or any other fix/feature you’d like to see (there’s plenty of things that need improving, and being able to find and fix them without much guidance is what we’re looking for). Please focus on good communication in your PR, as it’s something we’d expect you to keep up the entire time -- see this as an example, where the before/after of the change is clear, and any uncertain design decisions are brought up.


The 2021 roadmap is in a Google doc -- feel free to comment.

The rest of this doc covers plans for April-June 2021.


A/B Street has been under active development since June 2018, but we haven't yet used the software to advocate for anything specific in the real world. Ideally we'd attract other people to do this and just focus on making the software strengthen arguments as much as possible, but it's hard to pitch the idea of an "explorable explanations" blog post without an example. So, we'll produce some ourselves.

Map / simulation

The main project will be widening existing roads. Today, you can't transform one driving lane into a pair of bidirectional cyclepaths, even though that'd usually physically work width-wise. You also can't correct the OSM / inferred data about street parking. It's a complicated technical change, but essential. Hopefully building entirely new roads is possible to implement after this -- for things like the Northgate bridge or mocking up light rail expansion ideas -- but consider it a stretch goal.

I think one of the intermediate steps to implement the above will be letting cars enter and exit driveways from either side of the road. This should also help with gridlock, since many vehicles today loop around strangely to approach a building from the right side of the road.

Score functions

A/B Street's main metric for success is impact to trip time, but this is the kind of vehicle-centric, outdated way of thinking that we're trying to defeat. It's just the simplest to implement. We'll start tracking safety/pleasantness of trips too, exposing that in the UI as prominently as time, with the same ability to compare changes. Specifically, we can measure cases when cars over-take bikes (or at least want to), biking in the door-zone next to parking, and cars turning from a road with a high speed limit over a crosswalk with pedestrians. We have historic collision data for Seattle and the UK, and we can see if the dangerous areas A/B Street finds match that data.

Relatedly, it's finally time to implement some form of mode shift. When you edit the map and make it more pleasant to bike, some people should switch over to doing it. There are many ways to do this, but we'll at least start with something.

Stretch: lane-overtaking

There are a few varieties of this, passing using another lane in the same direction or against oncoming traffic, and something specific for shared walking/biking paths. I think this is less important and riskier than the other work, but I want to start it.


Implement Yuwen's new info panels, including the consolidated lane/intersection editing.

Leftovers - help wanted!

There aren't enough hours in the day, so probably not much work on:

Although the funding story is unclear, I'd like to hire somebody in the next few months to work on these.

Project motivations

I thought it'd be helpful to explain what motivates my work in A/B Street. These are just my personal values; I don't intend to make a careful argument about these here. In no particular order:

  • Transparency and reproducibility: if city government uses data, modeling, or simulation to inform a decision affecting the general public, then anybody ought to be able to repeat that analysis.

    • This means code and data should be open.
    • Businesses like Sidewalk Lab's Replica and Remix still need to generate income, but it's unclear why governments use taxes to pay for something only they see.
    • Decision making should be documented clearly. Why were the 35th Ave bike lanes scrapped? Was the amount of on-street parking on nearby residential roads factored in? Was there analysis of how trip time is impacted by parking in the neighborhood and walking a few blocks to a business on the arterial?
    • I'm personally inspired by approaches like vTaiwan and PDIS
  • Accessibility leads to participation: There's overhead to taking small ideas to advocacy groups or inconveniently timed public meetings. If the planning process is easier to interact with, more people will participate.

  • Short-term changes: ST3 is exciting, but 2040 isn't close. There are much cheaper changes that can be implemented sooner.

    • Most of the edits in A/B Street are inspired by tactical urbanism; they could be prototyped with signs and paint.
  • The US is too dependent on cars: This has an unacceptable impact on the environment. Even ignoring that, many cities are out of room to build more roads. We can't keep scaling population like this.

  • Autonomous vehicles will NOT save the day: They can squeeze more throughput out of existing infrastructure, but only up to a point. They might encourage people to move and tolerate longer commutes. Mass transit and dense land-use patterns handle population growth better.

  • Compromise and trade-offs: I see lots of rhetoric calling for extreme, sudden change. I don't want to ban all cars from downtown Seattle, because that's not realistic. I want to focus on immediate steps forward. I want to come up with estimates about impacting drivers by a median 3 minutes in order to save a bus route 1 minute, and to shift public discourse towards that.

Project history

As of June 2020.

tldr: A/B Street has been in active development since June 2018, but the idea has been festering since I was about 16.


What poor judgments have cost me the most time?

  • UI churn: I should've studied some UX on my own and started with a clear idea of how to organize everything
  • OSM data quality: I should've gained the confidence to upstream fixes earlier
  • Intersection geometry: I should've realized sooner that simulation robustness is more important than nice appearance.
  • Geometry primitives: I sunk too much time into the polyline problem and f64 precision.


  • The name was almost "Unstreet" or "Superban" (superb urban)
  • I hope you enjoy and/or are baffled by the release names


I originally wanted to tell a much longer story here of how I came to work on A/B Street, but I'm not sure this is the right time yet. So consider this the quick version.

I grew up in Baton Rouge, where driving is effectively the only mode of transport. (I've gone back and made a point of taking long walks to confirm how antagonistically the city is designed towards walking.) Very early on, I fell in love with a Nintendo 64 game called Banjo Kazooie, which led me to the online fan communities of the early 2000's. I wanted to create games too, so I started learning programming via library books and lots of questions on IRC. Because I never had any confidence in art, I wound up working on roguelikes, which led to a fervent interest in pathfinding algorithms and collaborative diffusion. When I started driving in high school, I quickly realized how bad people were at it. I remember being stuck at the intersection of Florida Blvd and Cloud and first wondering if the pathfinding algorithms could help with traffic. Can you see where this is going?

Impatience is a virtue

I moved to Austin for college. One of the first days of class, I shuffled down the stairs of Gearing Hall past a crackly old speaker apocalyptically announcing the weather forecast (details add color, right?) into a seminar demanding a totally open-ended first assignment to do something interesting. After I left, somebody stopped to ask me for directions, but I didn't know campus well yet. I thought about how Google Maps gave really silly walking directions. So I decided I'd hand-draw a map of campus, showing all of the construction, how to cut through the labryinth that is Welch Hall on hot days, and where to find the 24/7 robot coffee machines, and hack together a routing engine to help people find the shortest path between their classes. The feedback I got on this assignment included something along the lines of, "I was really pretty impressed first that you would be so stupid as to actually try to do this..."

Hand-mapping UT Austin

But I did, and that led me to discovering OpenStreetMap, which it turns out was pretty pivotal. (The first version of my campus map was seeded vaguely off an official paper map, but mostly I walked around and invented half-assed surveying methods on the spot.) Next semester, I joined a freshman research stream with somebody who had worked on AIM, UT's demonstration that autonomous vehicles wouldn't need traffic lights. Everything came together, and I started a 3 year journey of building AORTA, a traffic simulator for AVs. Guided by the research lab, I explored the really bizarre idea of letting AVs bid to turn lights green sooner and micro-tolling all roads to disincentivize congestion. Both of these mechanisms would be incredibly unfair to people without the spare cash to back up their high value-of-time, but I brushed this off by saying the currency could be based on carpooling, EVs, etc.

Approximately Orchestrated Routing and Transportation Analyzer

It was great to try research in college; I learned I really dislike munging data and compressing my work into 6 pages of conference paper LaTeX. So I moved to Seattle to work in industry instead, on something completely unrelated to transportation. Lots of things began unravelling for me in Seattle, but one of them was biking. In Austin, I had picked up mountain biking, and all but stopped driving; it was an amazing place to explore and commute by bike. Seattle was different. There were many more cyclists around, but the experience felt more stressful, the drivers more aggressive. I had plenty of near-misses. I kept commuting by bike, but the joy of it was gone. I started noticing how many cars were parked on narrow arterials and wondering why that was a fair use of space. I started paying attention to the public discourse around bike infrastructure in Seattle and feeling like the conversation was... chaotic.

Manhattan took walkability seriously

Fast forward to late 2017. This is where I'll omit chunks of the story. I visited London, my first experience with a city that took public transit seriously. When I returned, lots of latent ideas stopped fermenting and started exploding. I threw together a prototype of A/B Street and started the arduous process at work of open-sourcing it and applying to a program to let me work it on for a few quarters. A few months later, I wound up quitting instead, and began to work on A/B Street in earnest.

Year 1 (June 2018-2019)

I skimmed through git and summarized roughly what I was working on each month, calling out milestones. "UI churn" is pretty much constantly happening.

  • June: polyline geometry and lanes, building paths, protobuf -> serde

  • July: pedestrians, bikes, parked cars, lane edits

  • August: porting AORTA's discrete-time driving model

  • September: multi-leg trips, buses, the first ezgui wizard, randomized scenarios

  • October: A/B test mode (and so per-map plugins), forking RNG for edit-invariance, intersection geometry

  • November: clipping / borders, using blockface for parking, time travel mode, test runner framework

  • December: bezier curves for turns, traffic signal editor, a first attempt at merging intersections, right-click menus, a top menu, modal menus

    • the grand colorscheme refactor: a python script scraped cs.get_def calls at build-time
  • January: careful f64 resolution, ezgui screencapping, synthetic map editor

    • grand refactor: piston to glium
  • February: attempting to use time-space intervals for a new driving model, new discrete-event model instead

    • Feb 19-27: conceiving and cutting over to the new discrete event model
  • March: fleshing out DES model (laggy heads), first attempt to build on windows, gridlock detection

  • April: first public releases, splash screen and rearranging game modes

  • May: fancier agent rendering, attempting to use census tracts, finding real demand data

    • milestone: discovered PSRC Soundcast data, much more realistic trips

Year 2 (June 2019-2020)

Circa October 2019

  • June: contraction hierarchies for pathfinding, stackable game states

  • July: OSM turn restrictions, misc (I think I was in Europe?)

  • August: pedestrian crowds, agent color schemes, parking blackholes, a big raw_data refactor to store Pt2D, attended first hackathon

  • September: offstreet parking, associating parked cars with buildings using Soundcast (before that, anybody could use any car!), implemented texture support for some reason, doing manual MapFixes at scale to fix OSM bugs

    • milestone: got the smallest montlake map to run without gridlock
  • October: parking sim fixes, opportunistic lane-changing, starting challenge modes

  • November: prebaked sim results, time-series plots, undo for edit mode, traffic signal editor grouping turns

    • milestone: Yuwen joins project
  • December: the UI reform begins (flexbox, minimap, trip timelines, cutting over to SVGs, info panels, scrolling), started naming releases sensibly

    • Project leaked to HN, woops
  • January: UI reform continues, the modern tutorial mode appears

  • Feburary: UI and tutorial, all text now pure vectors, port to glow+WASM

  • March: lockdowns start in US, start grouping trips as a person, population heatmap, left-hand driving, info panel and typography overhauls. started engaging with Greenways, started effort to map traffic signals

  • April: Orestis joins and starts the pandemic model, trip tables, the optimize commute challenge, refactor for people's schedules and owned vehicles, trip time dat viz, MAJOR progress fixing gridlock at the sim layer

  • May: gridlock progress, upstreaming fixes in OSM, differential throughput and first real write-up, long-lasting player edits, dedicated parking mapper, maybe vanquished the HiDPI bugs, multi-step turn restrictions, random bios for people, and docs like this to prep for launch ;)

    • milestone: relying on pure OSM, no more MapFixes

Year 3 (June 2020-2021)

  • June: parking lots, real minimap controls, road labels
  • July: loads of bugfixes, map geometry improvements, UI cleanups, access-restricted zones for private neighborhoods and no-through-traffic, better traffic generation between home<->work for new maps, complete overhaul to bus routes and introduction of light rail, commute pattern explorer, importing Krakow and Berlin, smarter lane-changing, walkable shoulders for roads without sidewalks
  • August: Michael joins, multiple traffic signals can be edited together, started a headless JSON API, support for other languages in OSM data, started congestion capping, backwards-compatible and more robust map edits, two-way cycletracks, more cities imported, slurry of bugfixes and performance improvements
  • September: full support for driving on the left, textured color scheme, rendering isometric buildings, editing traffic signal offsets, a big round of UI changes, infinite parking mode, trip purpose, alleyways
  • October: unit tested turn generation, web version launched with async file loading, thought bubbles showing agent goals, slow parts of a trip highlighted, more UI overhauls, dedicated OSM viewer mode started, major simulation performance optimizations, major progress towards live map edits, automatically picking boundaries for arbitrary cities
  • November: switched from Dropbox to S3, download new maps in-game, collision dataviz UI, day/night color switching, unit testing lane changing behavior, starting the 15 min walkshed tool, simplified simulation spawning code, recording and replaying traffic around a few intersections, refactoring to split out separate tools
  • December: lane-changing fixes, blocked-by explorer in debug mode, non-Latin font support on web, saving player state on web, census-based scenario generation started, 15 minute Santa


Every time I upload a new binary release, I'll list major changes here.


  • First binary release


  • drawing arrows better
  • start with a splash screen, make it easy to change maps in-game


  • totally revamp GUI by organizing everything into distinct gameplay modes


  • new warp tool that autocompletes street names
  • hideable menus, place context menus better, remove top menu bar, add a simple OSD
  • loading screens reflect what's printed to the terminal
  • depict pedestrians and bikes with more detail
  • tool to scroll through an agent's route
  • make simulation speed controls actually work


  • improve stop sign editor UI (toggle entire roads)
  • better mouseover / selection rendering
  • better traffic signal rendering (show time left, use outlines for yields)
  • make cars actually stop and briefly wait at stop signs
  • improve edit mode diff visualization (cross-hatching)
  • render actual stop signs, not just red lines
  • fix intersection policies confused by conflicting straight turns with lane-changing
  • fix mac scrolling
  • better turn indicators
  • nicer unzoomed view of roads, with different colors for big/small roads


(release file size jumped from ~15MB to ~70MB because of new PSRC trips)

  • improve UX of intersection editors
  • define a better set of maps included by default
  • improve drawing speed by batching more stuff
  • better default traffic signal policies for many cases
  • import and visualize census data
  • fix missing sidewalks on downtown one-ways
  • import and visualize PSRC trip data


  • slider widget for controlling time and speed
  • fixing bad polyline geometry in most cases; visualizing routes should no longer be buggy
  • handle PSRC trips that begin or end out-of-bounds
  • draw agents in unzoomed mode in a way simpler way
  • improve edit mode: detect reverts to original, easier lane type switching
  • lots of fixes for buses: handle edits better, read sequence of stops correctly from GTFS
  • set up A/B tests faster


  • bulk and revert tools in edit mode
  • improve turns and default intersection policies when bike/bus lanes involved
  • new tool to manually hint for short roads and weird intersections. some problems have now been manually fixed
  • scoreboard of trip results for sandbox and A/B test mode
  • reduce lag when sim is running at full speeds, but system is too slow
  • switch to easbar's contraction hierarchy crate, making all pathfinding INSANELY fast
  • remove weird rules about the world freezing when traffic signals are in "overtime"


  • edit mode: convert to a ped scramble cycle, simplify stop sign editor by removing individual turns
  • ui: put labels next to sliders, organize modal menus into sections, add a minimize/maximize icon
  • A/B test mode: savestate, include time controls and agent following/route tools here
  • use more OSM data for turn lanes, turn restrictions from lanes, turn restrictions between entire roads
  • dont attempt to cross a traffic signal if there's absolutely no hope
  • improve bus route UI tools and make routes using transit more sane
  • user-defined shortcuts for jumping between views of a map


  • sliders to pick times in wizards
  • fix hidpi scaling
  • traffic signal diagram scrolls properly
  • easier to instantiate a scenario, show all trips involving a building for a scenario
  • colorschemes to show trip duration or time blocked
  • label buses with route number
  • represent overlapping pedestrians as a labeled crowd
  • massive performance boost via real priority queue
  • prevent cars from "blocking the box"
  • prevent all? aborted trips (due to parking blackholes mostly)
  • smarter roam-around-for-parking router


  • sim
    • parking in off-street garages and on-street lanes on the off-side of oneways now mostly works
    • detect and handle parking blackholes; cars should never get stuck looking for parking now
    • let lower-priority turns happen at traffic signals when higher-priority ones blocked
    • get closer to FCFS ordering at stop signs
    • basic opportunistic lane-changing
    • a bus should be seeded for every route now
  • demand data
    • show trips to/from buildings and borders
    • make PSRC trips seed and attempt to use parked cars
  • UI
    • different heatmap overlays, like parking availability and busiest areas
    • show colorscheme legends when relevant
    • interactively seed parked cars, spawn more types of trips
    • fix major A/B test mode bug (mismatched scenarios and map edits)
    • adjusting sliders, menu placement, dynamic items
    • consolidating different tools into a single info panel for objects
    • bus route explorer shows entire route, current bus location
  • map quality
    • degenerate intersections only have one crosswalk now
    • revamped the map editor for fixing geometry problems, used it in many places
    • nicer yellow center lines (dashed when appropriate)
    • handling OSM turn restriction relations properly
    • fix empty traffic signal phases
    • handling bike lanes on certain sides of the road
    • starting to upstream manually-verified parking lanes into OSM
  • new gameplay: reverse direction of lanes


  • small UI fixes: fixed width traffic signal diagram, skip info phase of menus when empty
  • start drawing (but not using) shared left-turn lanes from OSM
  • fix OSM polylines with redundant points (fixing an issue in ballard)
  • improved traffic signal policies in some cases
  • started upstreaming some sidewalk tags in OSM to fix inference issues
  • fixed misclassified right turns
  • adjusting map colors
  • handling lakes/ocean polygons from OSM way better
  • reorganized sim analytics, added stuff for bus arrivals
  • adding new internal road points to map editor. almost ready to really aggressively use it
  • skipping parking lanes with no nearby sidewalks, since they're unusable
  • fix z-order of bridges/tunnels in unzoomed view
  • allow unzooming indefinitely
  • move lots of sandbox mode controls (and other modes) to menus under buttons and dedicated buttons
  • basic support for marking a lane closed for construction
  • improved geometry of sidewalks at dead-ends


  • reorganize everything as different challenge modes. start implementing 3: optimizing a bus route, speeding up all trips, or causing as much gridlock as possible
  • improved bus route explorer
  • some UI fixes (popup messages in a few places, moving mouse tooltips to the OSD)
  • lots of analytics and time-series plots


  • analytics: prebake baseline results properly. hover over plot series. some new modes to see bus network, throughput of a road/intersection over time
  • log scale for the speed slider
  • add a bulk spawner in freeform mode (F2 on a lane)
  • rendering: nicer routes, crosswalks, zoomed car colors
  • map data: better stop sign and sidewalk heuristics
  • fixed the mac hidpi text rendering issue once and for all?!


  • better crosswalk generation when there's only a sidewalk on one side of a road
  • edit mode UI revamp: paintbrush-style buttons to apply changes to lanes
  • show error messages and prevent edits, like disconnecting sidewalks
  • properly ban bikes from highways (revamped rules for vehicles using a lane)
  • new freeform mode tool to spawn bikes
  • WIP (not working yet): make bikes prefer bike lanes. some debug heatmaps for path cost
  • edit mode has proper undo support


  • minor bugfixes with reverting lane types, preserving stop signs
  • incorporate edits into the challenge splash screen, make sure edits are reset when appropriate
  • starting a new challenge mode, just focused on traffic signals
  • can't leave traffic signal editor with missing turns
  • render pedestrian crowds on building front paths
  • traffic signals support an offset parameter
  • traffic signal visualization and editing revamped to group related turns together
  • can preview traffic using a signal from the editor
  • actually apply priority at intersections, so protected turns get first dibs over yield turns


  • fix Mac crashing with texture limit bug by switching to texture arrays
  • fix crashing simulation when a border intersection was used
  • started to implement a new UI design for starting the game


  • more work on the pre-game UI, with some flexbox layouting
  • prototype a minimap in sandbox mode. doesn't pan or scroll yet.
  • prototype a new speed/time control panel from the mockup
  • nicer time warp loading screen
  • record and show detailed trip timeline, including time to park


  • map data: infer more building addresses
  • some analytics on how long people spend parking and intersection delay over time
  • create an options panel, allowing runtime customization of color scheme, traffic signal rendering, etc
  • internal changes to map building pipeline to make it much easier for new devs to onboard
  • organizing challenges into sub-stages, starting to flesh out specifics for the fix traffic signal track
  • much more realistic pedestrian pathfinding
  • fix minimap on mac (dpi issues)
  • visual tweaks to cars to make front/back easier to distinguish
  • internal change to switch most assets from PNG to SVG


  • some challenge modes show a histogram for counting faster/slower trips
  • new visualization of current demand per direction at a traffic signal
  • implementing some of Yuwen's UI changes: agent counter, split time/speed panel, moved functionality out of the old drop-down menus into a bottom-left tool panel, hiding debug functionality
  • replaced right-click context menus with left click to open info panels
  • fixed random issues reported by people from HN


  • moved some UI functionality around, pulling graphs into info panel
  • interactive legend for the minimap, toggle visibility of different agents
  • nicer colors and shapes for cars
  • misc simulation bugfixes that might help huge_seattle
  • pedestrians choose to use transit more realistically, factoring in time for the bus to drive


  • switch some analytics dashboards to use buttons, not old non-scrolling menus
  • scrollbars... at least a start
  • preview traffic signal changes from live sim as the base
  • traffic signal preview has normal time/speed controls
  • traffic signal editor has undo support
  • minimap has buttons to pan


  • minimap zoom controls
  • traffic signal rendering overhaul
  • heatmap colors improved, heatmap appears on minimap
  • bus info panel, a start to live delay analytics


  • UI revamps: speed panel, minimap controls, heatmap chooser
  • bus timeline
  • hide internal IDs normally
  • limit map zoom
  • fix bugs with crosswalks conflicting with vehicle turns


  • overhaul traffic signal editor UI, and add redo support
  • update main edit mode UI, and add redo support
  • limit max unzoom
  • fix the infamous HiDPI bug once and for all; minimaps should work everywhere
  • almost bug-free support for floating, horizontally and vertically scrolling panels
  • overhaul top-center panel, rename scenarios to be less confusing
  • expose bus analytics outside of challenge mode
  • live info panel can exist during a running simulation
  • consolidated agent route/trip information into info panel


  • overhauled the tutorial
  • tuned top-center panel for sandbox and challenge modes
  • make bike and bus lanes more obvious
  • show map edits as an overlay anywhere
  • tune info panel contents, and show relationships between parked cars and buildings
  • fixes to traffic signal editor, like making all-walk conversion idempotent
  • nicer throughput and delay plots (sliding windows, grid lines)


  • tutorial improved in a few places
  • map data: thinner sidewalks, associate buildings with named amenities
  • traffic model: vehicles can spawn on all lanes from a border
  • much better gameplay speed (previously was too fast)
  • UI tuning: lane editor, minimap, signal editor, heatmap legends don't overwrite minimap
  • traffic signal challenge communicates score more clearly


  • edit mode revamped: click to edit stuff. no more lane paintbrushes. autosaving and save as.
  • tutorial: can quit and resume tutorial now
  • challenge picking flow simplified
  • UI: layouting fixes to full-screen / into stuff, popup menus go beneath buttons, plots improved
  • internal change to render all text using vector graphics. other than a few text layouting issues, shouldn't be noticeable, except now tooltips in plots don't get covered up
  • misc perf improvements (cache SVGs, drawing many circles for unzoomed agents, dont reload prebaked data)
  • upgraded winit, glutin, glium -- hopefully no new bugs introduced on any platforms


  • patch to fix a crash with empty text dimensions on things like building info panels


  • all info panels revamped
  • some tutorial stages are much more clear, with an updating goal
  • traffic signal scorecard generalized to work for some tutorial too
  • adjust how selected agents look
  • X button on popup menus


  • new tool to convert between stop signs and traffic signals
  • lane editor easier to edit multiple lanes
  • info panels: IDs, mostly avoid horizontal scrolling, better info about trips to/from somewhere, move buttons up
  • traffic signal editor UI overhaul
  • different data in top-right agent meters panel
  • tooltips to communicate keybindings better
  • new jump-to-time panel, showing when rush hours occur
  • speed controls use more useful speeds
  • include ongoing trips in measured trip times
  • jump to next challenge after completing one
  • lots of tutorial tweaks


  • show additional info about traffic patterns and buggy maps
  • revamp tutorial UI to group tasks and messages better
  • handle different mode transitions when info panel open on an agent
  • select entire roads in unzoomed edit mode
  • show total time an agent has spent moving / blocked
  • use 2-phase traffic signals by default, making the 23rd map successfully complete!
  • jump-to-time now optionally points out traffic jams forming
  • challenge splash screen improved


  • overhauled trip timeline in agent info panels
  • overhauled traffic signal details panel and the per-lane turn explorer
  • settings page: show all options at once. add way to scale up text/UI elements for high-DPI displays, and an alternate pan/zoom control scheme
  • traffic signal edits can now be exported and used in any slice of Seattle. will be using this to hand-map many of them.
  • many small tutorial fixes


  • some UI work on giving focus to textboxes, improving dropdown menus
  • road/intersection plots display baseline sim data too
  • start associating people with multiple trips, exposing this a little in the UI
  • bring back elevation data, introduce a new overlay. the elevation data is still really bad.


  • new "population" overlay, showing people (not just current trips). heatmap and dot map to visualize.
  • improved the "delay" overlay to handle roads and intersections
  • removed the confusing and useless alternate color schemes for agents
  • initial left-hand driving side, tested in Perth, also drawing more arrows for all one-way roads
  • loads of internal GUI code refactorings, preparing for a standalone release of the library
  • fixed z-buffering and alpha values for web backend


  • info panels have been totally overhauled again. multiple tabs, way more clear representation of agents, trips, and people.
  • draw people inside of a building
  • applied consistent typography everywhere
  • lots of internal refactoring


  • more info panel work, particularly for trips and buses. change plot settings live.
  • prototype of a SEIR pandemic model based on time spent in shared spaces, by orestis
  • slight heatmap improvements, more coming
  • more typography changes
  • mouse cursor now changes for buttons and dragging!
  • overhaul minimap controls, make layers behavior zoomed in a little better
  • new speed panel and jump-to-time modal


  • overhauled simulation data page, with a table to find slow trips and some initial summary visualizations
  • plots can change windowing and show/hide series
  • layers: fade map to contrast more, better scales/legends
  • show relative trip times in info panels
  • tools to rewind/ffw to watch particular trips
  • refocusing efforts on challenge modes; level 1 of a new one is pretty much ready
  • some simulation fixes around parking and a corner case of cars temporarily forming a cycle
  • orestis improved the population/pandemic heatmaps


  • optimize commute challenge: high score, live sentiment, second stage
  • parked cars are owned by people, not buildings
  • info panel improvements for trips
  • bike layer suggests places where bike lanes could be helpful
  • many improvements to scatter plot
  • a new histogram-ish thing for understanding faster/slower trips
  • handling scenarios longer than 24 hours better (for pandemic model)
  • prototype of commute visualization, grouping buildings by blocks
  • sim bugfixes: crosswalk / vehicle turn conflicts, start bikes in bike lanes from borders


  • major internal changes to ensure people's schedules don't have impossible gaps, to associate fixed bikes/cars to a eprson, handle delayed starts to trips
  • parking changes: show path to closest free spot, utilization of a lane over time, every building includes at least 1 offstreet spot by default
  • progress on removing unrealistic gridlock: detect turn conflict cycles and temporarily allow conflicts, trim last steps of a laggy head
  • internal sim alert system. speeds up debugging, could be used for player-facing "traffic jam!" alerts


  • switched to proper OSM-based maps; no more brittle, manual geometry fixes
  • more sorting and filtering options in trip table and parking overhead tables
  • improve offstreet parking rendering. park closer to destination buildings
  • easier process for importing new cities. introducing Los Angeles, Austin, Barranquilla.
  • new data updater tool so people can opt-in to new cities
  • many internal fixes to prevent gridlock. smarter cycle detection, manual OSM fixes and traffic signal timings


  • differential throughput layer to understand routing diversions
  • map edits now reference longer-lasting OSM IDs, can work cross-map
  • basemap updates: new areas for west seattle, mt baker, lots of upstreamed fixes in OSM and traffic signals, smarter border matching
  • parking: optionally filter on/off-street spots in the layer, allow disconnecting spots via edits
  • render some tunnels with lower opacity
  • new feature to change speed limits and bulk road selection tools
  • first write-up of a real use case (closing lake wash through arboretum)
  • make the traffic signal challenge act like a game, with a failure/win state and scoring


  • added a mode to map parking


  • new parking mapper tool
  • include a one-shot .osm importer in the release
  • new layer to find different types of amenities / businesses
  • adjust traffic signal rendering style
  • bulk lane editor for changing speed limits and lane types
  • including west seattle and udistrict maps
  • include some OSM buildings that were being skipped
  • dont pause after opening something from sandbox mode
  • adjust turn signals for lane-changing cars
  • lots of fixes for monitors with different DPIs, enabled by default


  • many misc UI bugfixes, especially for high-DPI screens
  • managing turns across multiple nearby intersections: tool to visualize, handling multi-way OSM turn restrictions, using this to ban illegal movements at the pathfinding layer, starting a traffic signal editor variant to edit these
  • rendering improvements: unzoomed agent size, visualizing routes on trip table, transparent roads beneath bridges, draw harbor island
  • overhauled street/address finder
  • parking mapper: shortcut to open bing


  • new map picker!
  • UI polish: traffic signal editor, layers, bus stops, delay plots
  • generate more interesting biographies for people
  • tuned all the map boundaries
  • fleshing out lots of docs in preparation for the alpha release...


  • spawner UI revamped
  • model parking lots! and finally model public/private parking
  • fix up tutorial
  • starting a story map mode


  • overhauled challenge cutscenes and hints
  • traffic signal challenge: fix score detection, add meter, much faster startup, no reset-to-midnight required
  • layers: use gradient for a few, delay comparison, new UI for picker
  • overhauled minimap controls, should be intuitive now
  • edit mode changelist UI started

0.2.0 (alpha launch)

  • road names now shown by default, in a new style
  • all layers now use gradients and show up zoomed in. worst traffic jam layer revamped.
  • scatter and line plot improvements
  • internal UI fixes: proper word wrap
  • bugfixes for following people riding the bus
  • rainbow crosswalks in one neighborhood
  • final polishing for launch


  • busy week due to launch, but many new features in the pipeline
  • many bug fixes
  • edit mode: proper autosave, load proposals, jump between lane/intersection editors
  • very first steps on light rail... importing the tracks
  • starting a new traffic scenario modifier system, to repeat entire scenario or outright cancel trips for some people. many more ideas for filters and actions coming soon.
  • starting to represent private roads
  • add a very simple actuated traffic signal


  • the default traffic signal configuration is much smarter now, handling roads with some sidewalks missing and automatically synchronizing pairs of adjacent lights
  • much faster startup time for large maps
  • better UX for handling unsaved edits
  • access-restricted zones: changing existing zones almost completely works, except for granting new access to pedestrians
  • new sidewalk corner rendering, more rounded
  • ui style standardized for margins, padding
  • Javed got camera panning when your cursor is at the edge of the screen to work; enable it in settings
  • pulling bus stop/route info from OSM, not GTFS. steps towards light rail.
  • experimenting with controls for hiding bridges to see roads underneath; try them in dev mode (ctrl+S)
  • many bug fixes


  • lane geometry is dramatically fixed, especially for one-ways
  • importing lanes from OSM improved
  • UI: bulk select includes select-along-a-route, show all bus routes in the layer, unzoomed zordering for roads/intersections
  • traffic scenario modifier can now convert trip modes
  • slight progress on light rail, although the train only makes one stop
  • vehicles moving through complex intersections with multiple traffic signals will now make it through multiple lights, even if they're unsynchronized
  • new random traffic scenario generator that makes people go between houses and workplaces
  • access-restricted zones: granular editing of individual roads now mostly works
  • removing the hardcoded relative directories, which many people have been having problems with
  • many many bug fixes, and some optimizations to reduce release file size


  • bus/train routes overhauled; they're now one-way, regularly spawn every hour, and may begin or end at a border
  • new commute pattern explorer tool
  • new character art to give cutscenes a bit more personaliy
  • some progress on gridlocking maps, both from manual fixes and an attempt to reduce conflicts in multi-turn sequences
  • misc UI: show cars seeking parking in unzoomed mode, plot arrival rate at border intersections, consolidate bulk selection controls
  • trips modified by an experiment can now be filtered in summaries
  • buses, trains, and passengers on them are now properly distinguished in different stats
  • include krakow and berlin in release
  • buildings with holes in the middle are now rendered properly


  • cars pick lanes better
  • overhaul bus/stop/route info panels
  • UI: better autocomplete, commuter pattern improvements by Michael, toggles instead of checkboxes, contours for heatmaps, edit mode loader revamp
  • internal refactors: turn creation, osm tags, osm parsing
  • import living streets from OSM as restricted-access zones, and other importer tweaks for berlin, krakow, san jose, sydney


  • many roads without sidewalks now have a tiny shoulder lane, still enabling pedestrian movement, but with a penalty
  • bike trips will stop/start at a better position along the sidewalk now
  • support parking lanes on the off-side of a one-way
  • UI: search by building names, commuter patterns shows borders better
  • transit: make people ride off-map, spawn buses on short roads
  • internal cleanups for buttons


  • many intersections with on/off ramps have much better geometry
  • lane-changing banned on turn lanes
  • lots more work matching bus stops/routes to the map. some progress, also some regressions.
  • fixing spawning on tiny borders
  • bus spawn rates from GTFS for seattle. started an editor for the schedule.
  • internal ezgui refactorings


  • multiple traffic signals can now be synchronized and edited together
  • new dashboard for "traffic signal demand" over the entire day and map
  • started experimenting with controlling the headless runner via a JSON API
  • epic ezgui fix by Michael to consolidate handling of HiDPI scaling
  • got a bunch of huge cities importing and loading quickly
  • you can now save the trips you manually spawn in freeform mode, then replay them later


  • import Xi'an, add a Chinese font, and add a tool for that group to import their external demand data
  • control A/B Street through a graphics-less API, with a Python example
  • improve UI for per-direction traffic signal demand
  • on/off ramp geometry fixed in a few more cases
  • fix some missing parking lot aisles, handle parking lots with 0 spots, and extract parking garages from OSM
  • switch road/building language in settings, if OSM data exists
  • congestion capping prototype: declare a max number of vehicles that can pass through a zone per hour, view/edit it, and very simple implementation in the sim layer
  • add custom-drawn trips to the main scenario, for exploring new demand from a new building
  • mkirk fixed up the glow/wasm ezgui backends, letting us remove glium
  • make map edit JSON backwards compatible
  • better lane/turn markings


  • two-way cycletracks and arbitrary direction changes for roads
  • fix map editing for lane reversals, make edits backwards compatible, and massively speed up applying edits
  • fleshing out the headless API and tooling for controlling the simulation from any language
  • import a few more places, redo left-hand driving support so far
  • various bug/performance fixes


  • disabled support for editing the map without resetting the simulation. needs more work, but solid start.
  • improvements to API, activity model, congestion capping
  • small UI tweaks for parking, editing multiple signals
  • fixed last bugs for left-handed driving, should work just as well now
  • lots of graphics experiments from the hackathon, not merged yet


  • new textured color scheme and isometric buildings, in settings
  • new layer to show how far away people parked
  • Massive UI overhauls: jump to time/delay, edit mode, traffic signal editor (now with offsets), lane editor, bulk lane edit, traffic signal demand (individual intersections and all), loading screen
  • the Go API example compares trip times and detects gridlock
  • infinite parking mode
  • show how long a car has been parked in one spot
  • bugfix for some pathfinding costs around uber-turns
  • start to show a trip's purpose


  • alleyways from OSM imported
  • traffic signal minimum time now constrained by crosswalks; thanks Sam!
  • UI changes in progress for trip tables, summaries, bulk edit
  • more API / Python example work for congestion capping
  • bug fixes: isometric buildings, documentation links, dropdown widgets, turn restrictions


  • improve turn generation, with goldenfile tests
  • UI adjustments: unzoomed routes, better delay layer, include reasons for cancelled trips, throughput layer counts
  • small map importing fixes: multipolygon parking lots
  • fix infinite parking and blackholed buildings


  • large internal change allowing asynchronously loading extra files over HTTP for web
  • the release of the first web version!
  • cars looking for parking now have a "thought bubble" showing this, by Michael
  • slow sections of a trip are now shown in the info panel, by Sam
  • fix by Michael for handling window resizing in panels
  • fix original routes on edited maps
  • internal code organization and documentation


  • UI: click unzoomed agents, switch between metric/imperial units, show reason for cancelled trips, new "faded zoom" color scheme based on mapbox, more detailed agent counts in the top-right panel's tooltips
  • started a new dedicated OpenStreetMap viewer, will split out from A/B Street later
  • fix alpha colors on web
  • bugfixes for the new asynchronous map loading
  • some substantial simulation performance gains (168s to 90s on one benchmark!)
  • lots of progress towards editing the map without resetting the simulation to midnight. please test with --live_map_edits and report any issues
  • internal refactoring and code documentation


  • tooling to automatically extract different shapes around cities without an explicit bounding polygon
  • imported many maps for an OSM viewer demo
  • misc bug fixes, UI tweaks, and perf improvements, especially for the web version
  • start using OSM sidewalks data properly in krakow -- more work needed, but better start


  • overhaul data/system management: switch from Dropbox to S3, reorganize files, add an in-game updater
  • started a UI for collision dataviz, with data in the UK and Seattle
  • improve turns between separate footways
  • simplify the process of importing a new city


  • added experimental day/night support; run with --day_night
  • slight performance improvements by avoiding applying no-op edits
  • new tests for lane-changing behavior, used to more safely allow more realistic behavior blocking "degenerate" intersections
  • experimenting with filling in gaps between one-way roads, to represent medians


  • prototyped a new 15-minute neighborhood tool
  • overhaul internal simulation input code -- better performance, way simpler
  • debug tool to record traffic around a few intersections and replay later


  • split separate tools into their own executables
  • misc bug fixes and other refactoring, focused on GUI code mostly
  • most of a prototype done for an experiment
  • map added for north seattle


  • vehicles will lane-change less erratically during uber-turns (sequences of turns through multiple traffic signals close together)
  • debug mode has a "blocked-by graph" tool to understand dependencies between waiting agents
  • try multiple OpenGL video mode options if the first choice fails (thanks Michael!)
  • refactoring trip starting code and the minimap
  • non-Latin fonts now supported on web too, thanks to rustybuzz release
  • new small maps in Seattle included in the release, and NYC added to optional cities
  • saving some player state on the web (mostly camera position per map, for the main game)
  • partial prototype of a new census-based scenario generator, thanks to help from the Amazon SSPA hackathon
  • significant progress on the experiment, about one week left...


  • released the 15-minute Santa experiment!
  • trip info panels now show more continuous progress along a route
  • fixing inactive buttons stretching too much


  • variable traffic signal timing, thanks to Bruce
  • 15 min explorer: more walking options (require shoulders, change speed), more organized business search
  • 15 min santa: remember upzoning choices
  • misc bugfixes and refactoring
  • 2021 roadmap drafted


  • huge breakthrough on merging intersections to handle short roads. applied to just a few places so far
  • support one-way roads with default traffic signal heuristics
  • 15 min explorer: find residences close to user-specified businesses, see unwalkable roads
  • bugfix from Bruce to prevent sim from crashing when a short road is over capacity
  • automatically fetch census data for any US map and use for scenario generation, from Michael
  • some initial experiments to bring A/B Street's lane rendering to the web using Leaflet


  • dramatically improve initial web loading and be able to start with any map
  • city picker UI now better organizes other regions with many maps
  • new tool to convert SUMO networks into A/B Street maps
  • taking screenshots of the entire map now much faster, portable. trying to use for Leaflet raster tiles, but not working yet.
  • widgetry UI library now doesn't depend on anything specific to A/B Street
  • internal refactoring for error handling


  • dramatically speed up starting scenarios by deferring when public transit riders pick their route
  • start importing separate cyclepaths and pedestrian plazas for Cambridge, many adjustments to make these start working
  • full panel for picking a scenario to start
  • import trip data from the actdev project for Cambridge
  • improve inferred map elements (stop signs and crosswalks) near short roads
  • heuristics for automatically finding and merging short roads. disabled, but solid start


  • massive button overhaul by Yuwen and Michael
  • the color scheme automatically switches between day/night based on simulation time!
  • significant progress on editing the map without resetting the simulation
  • various bugfixes, new maps, improvements to running the importer, faster updater


  • new map, Rainier Valley, is the 3rd ever to finish without gridlock! One of the fixes was collapsing traffic circles into a normal intersecton
  • bugfixes for map importing: cycleways with left-handed driving, matching traffic signals, odd number of lanes, u-turns
  • further fixes after the great button refactor


  • split documentation into a separate git repo
  • UI: filter throughput layer by mode, render stop signs better
  • started a debug tool to live-tune routing parameters and see the effects on a single route or all trips
  • Michael improved the web loading screen and added webgl1 support for ios browsers
  • cars looking for parking now randomize a bit, eliminating the unrealistic "parking snakes/parades" effect
  • Bruce fixed a critical bug with uber-turns, helping ease gridlock in some maps
  • new tool to procedurally generate houses along empty residential roads, useful when OSM is missing most data


  • loads of work integrating abstreet with the actdev project, and importing tons of maps
  • new tool to find the geofabrik OSM source best fitting a boundary
  • fix crosswalks and traffic signal policies in left-handed driving countries
  • fix initial zoom when starting with flags to copy the viewport from OSM
  • Bruce added lagging green signal heuristics
  • make it easier to click tiny bikes and pedestrians
  • change the URL in the web version as you change maps/scenarios
  • split the list of cities by country, and improved the UI for picking a place


  • easier to share web URLs with the current viewport
  • more actdev integration work
  • misc bugfixes, especially with uber-turns and the city picker UI
  • revived the map_editor tool for drawing small test maps and iterating faster on merging intersection geometry


  • import service roads and separate cyclepaths in all maps!
  • brand new day theme, designed by Yuwen and implemented by Michael!
  • improved rendering where sidewalks and shoulders of roads meet
  • fix overlapping panels on HiDPI screens with low resolution
  • prototype of loading abst in the background on a webpage
  • adjusted routing, penalizing unprotected left turns onto bigger roads, which often contribute to gridlock
  • fixed some turn restrictions at merged intersections
  • widgetry: change fonts mid-line, dynamically load extra fonts (for a Taipei map)


  • new travel demand generator using UK origin/destination data
  • the 15m and OSM viewer web apps also have nicer URLs now
  • 2 more seattle maps complete without gridlock!
  • less seattle maps bundled with the release by default, to keep file size sane
  • many UI changes for the actdev integration, pending rollout to abst generally
  • initial experiments with implementing lane over-taking and road-based pathfinding


  • vast improvements to roundabouts, gridlock, and ordering at stop signs
  • consolidated UI panels in simulation modes; should work much better for smaller screens
  • measure intersection delay more intuitively
  • use straight lines for right/left turns at small intersections
  • various bugfixes and small UI improvements
  • started a tool to import from OSM without the command line; should be ready next week


  • released a new tool to import anywhere from OSM from within the game!
  • UI: finished trips meter, consolidated agent counters on minimap, move layers to the left for small screens, new tab style, use new day colorscheme in other apps
  • fixed a bug with comparing the route somebody took before/after changes
  • start integrating Eldan's elevation data and reviving parts of the UI that use the data
  • better routing into/out of access-restricted neighborhoods


  • import elevation data for Seattle!
  • add layers showing steep streets, elevation contours, and show elevation profile for bike/pedestrian routes
  • faster, simpler pathfinding for access-restricted zones
  • faster map importer and some UI fixes


  • change walking and biking speed based on elevation
  • import elevation data for all maps -- though there may still be quality issues
  • filter trip table by start/end location on the map
  • Michael made the web deployment much more flexible, in preparation for a blog post


  • massive internal change to make pathfinding use roads, not lanes. paves the way for many exciting things, like...
  • changing the number and width of lanes per road! WIP, debug mode-only UI for now


  • fix bugs running the importer
  • import grid2demand scenarios from the UI
  • implement more of road widening internals
  • new --minimal_controls option by Michael for screencasts
  • ran the first UX study in a long time, lots of feedback on the tutorial and traffic signal editor


  • first prototype of the new road editor!
  • much easier way to download new cities -- just try to open a map you don't yet have. much smaller .zip release now
  • track two "risk exposure" events -- when cars want to overtake cyclists, and passing through a large intersection -- and start to show data on trip panels, a new "problem map" layer, and a WIP summary dashboard
  • better geometry for some merged intersections, and applying the algorithm all around Tempe
  • another UX study and some tutorial UX fixes


  • improvements to experimental road editor
  • 2 more UX studies and a slew of small fixes


  • starting to import GMNS traffic signal timing. this will eventully let us import from Synchro!
  • more work on the new road editor, but still not ready. lane widths are now more varied and realistic.
  • Trevor overhauled the OSM amenity categories and added a multi-source isochrone to the 15m tool to find places without access to some amenity
  • partial work running the map importer in a Docker container, to eventually speed up the import process in the cloud
  • signal editor UI adjustments
  • time warp UI fixes and a new risk exposure contingency matrix by Michael
  • internal code cleanup using clippy, by Vinzent


  • experimental road editor finally released! more improvements still planned
  • save/restore settings (like camera angle and units) between sessions
  • Michael improved performance of scrolling panels
  • Michael adding more dataviz to travel time and risk exposure dashboards
  • regenerating all maps now happens on the cloud -- faster and less harmful to my poor laptop
  • exploring some debug tools and strategies for consolidating intersections


  • map importing fixes: multiple left/right turn lanes, combo bus/turn lanes, tidy up degenerate intersections along cyclepaths, allow explicitly tagged U-turns, better stop sign placement heuristics
  • in Seattle, snap trips entering/leaving map through borders more carefully
  • prebake data to cover trips starting near midnight. down from about 3000 cancelled trips in montlake to 300
  • road editor UI: explain disabled actions
  • Michael added new "arterial crossing" risk for pedestrians
  • improved GMNS signal timing importer


  • lots of internal writing, presentations... not quite ready for an audience yet
  • fixed weird turn lane arrows
  • GMNS signal import now works for many intersections, thanks to crosswalk inference
  • road editor UI fixes
  • speed up starting a simulation with infinite parking
  • possible breakthrough with intersection consolidation


  • simulation speedup by storing turns better
  • new website/docs maybe halfway done
  • sped up rust build process
  • improved some geometry around Tempe
  • fix broken screenshot diff test
  • trevor added the "cloud of uncertainty" in the 15m tool to show borders


Example use cases

Groups that may be eventually interested

Similar projects


SDOT asking for feedback:

Seattlites with opinions and ideas:

Other projects

Notes from related work

SMARTS (https://people.eng.unimelb.edu.au/etanin/tist17.pdf)

  • Split map into sections, simulate in parallel, load-balance
  • has an IDM equation
  • tests against real TomTom data of average speed per link


SimCity, Cities: Skylines https://steamcommunity.com/sharedfiles/filedetails/?id=583429740 https://github.com/fegennari/3DWorld

Open source urban planning



Sidewalk Labs Model

Maps for people



section 6.3 talks about offset polylines http://gamma.cs.unc.edu/RoadNetwork



Discrete Event Simulation papers

  • section 5.1 of Advanced tutorial on microscopic discrete-event traffic simulation refers to some DES systems

    • Florian, Mahut, and Tremblay 2008
    • Sumaryo, Halim, and Ramli 2013
    • Salimifard and Ansari 2013
    • Burghout, Koutsopoulos, and Andreasson 2006
    • Thulasidasan, Kasiviswanathan, Eidenbenz, Galli, Mniszewski, and Romero 2009
  • A Dynamic Traffic Assignment Model for Highly Congested Urban Networks

    • section 2.2 models lanes as a moving and queueing part, references other possibly useful papers
    • dont worry about multiple lanes for the moving part, just the turn queues at the end

Tactical urbanism


Most recent first.

Minor talks/demos

  • May 2021: Open:Data:Night for Open Manchester
  • January 2021: TRB Network Models in Practice


Don't forget to link to these!

  • Actdev demo video
  • OSM Connect 2021
  • FOSS4G 2021