StressMap calculates cycling Level of Traffic Stress for roads and paths using data from Open Street Map.
The processing pipeline downloads an OpenStreetMap street network, interprets available road and bicycle-infrastructure tags, applies the configured LTS rules, and produces segment-level CSV and GeoJSON outputs. Ratings are calculated separately for each direction of travel where the available data allows it.
LTS values range from 1 to 4:
- LTS 1 represents the lowest-stress conditions.
- LTS 4 represents the highest-stress conditions.
Segments that cannot be assigned a positive rating may contain an LTS value of 0 or a missing value and are excluded from the generated map GeoJSON.
The results depend on the completeness of OpenStreetMap data. When a required value is not available, the calculation may use an assumption defined in the project configuration.
This code is adapted from Bike Ottawa's LTS code, modified to include Level of Traffic Stress for intersections by Madeleine Bonsma-Fisher.
The LTS decision tables used by the project are defined in config/tables.yml. A copy of the reference document is included at config/LTS-Tables-v2.2.pdf.
Intersection LTS functions are included in the codebase, but intersection ratings are not currently generated by the standard command-line workflow.
- Python 3
- pip
- Internet access for OpenStreetMap, Overpass, and OSMnx downloads
Run all commands from the repository root.
Create a virtual environment:
python -m venv .venvActivate it on macOS or Linux:
source .venv/bin/activateActivate it on Windows PowerShell:
.venv\Scripts\Activate.ps1Install the project dependencies:
python -m pip install -r requirements.txtProcess Cambridge and generate the GeoJSON used by the local map:
python main.py process -city Cambridge --plotThe first run downloads the required OpenStreetMap data and may take some time.
Start the local web server:
python web.pyOpen http://localhost:8000 in a browser.
The local map reads from plots/LTS.json. Plotting another region replaces that file with the newly generated region.
python main.py process -city BostonAdd --plot to generate map data immediately after processing:
python main.py process -city Boston --plotThe processing pipeline is designed to reuse intermediate files from earlier runs. Use --rebuild to regenerate the downloaded and calculated outputs.
python main.py process -city Boston --rebuildThis makes new requests to OpenStreetMap and the Overpass API, so it will take longer than reusing saved files.
Pass municipality names as a comma-separated list:
python main.py process -cities Cambridge,Boston,Somerville,Brookline --combine --plotThe combined output is saved under the region name GreaterBoston.
The current combine step concatenates the processed edge data from each municipality. It does not perform additional edge deduplication or construct a new unified regional graph.
If each municipality has already been processed:
python main.py combine -cities Cambridge,Boston,Somerville,BrooklineGenerate GeoJSON from the combined CSV:
python main.py plot -city GreaterBoston --format jsonIf a region has already been processed:
python main.py plot -city Cambridge --format jsonThis reads:
data/Cambridge_4_all_lts.csv
and creates:
plots/Cambridge_LTS.json
plots/LTS.json
The municipalities currently configured in constants.py are:
- Arlington
- Belmont
- Boston
- Brookline
- Cambridge
- Chelsea
- Everett
- Lexington
- Malden
- Medford
- Newton
- Somerville
- Waltham
- Watertown
Names passed to -city or -cities must match these values exactly.
Intermediate files are saved so that later runs do not need to repeat every step.
| File | Description |
|---|---|
query/<region>_ways.query |
Generated Overpass query for ways |
query/<region>_nodes.query |
Generated Overpass query for nodes |
query/<region>_relations.query |
Generated Overpass query for bicycle-route relations |
data/<region>_1.json |
Raw Overpass response containing ways |
data/<region>_nodes.json |
Raw Overpass response containing nodes |
data/<region>_relations.json |
Raw Overpass response containing relations |
data/<region>_2_way_tags.csv |
OSM way tags included when loading the street graph |
data/<region>_3.graphml |
Unsimplified OSMnx street graph |
data/<region>_4_all_lts.csv |
Segment-level LTS results |
data/log_filter_column_counts.csv |
Diagnostic counts for fields used during calculation |
plots/<region>_LTS.json |
GeoJSON for the selected region |
plots/LTS.json |
GeoJSON loaded by the included local map |
The data/, plots/ and generated query files are excluded from version control.
The main analytical output is:
data/<region>_4_all_lts.csv
Each row represents an edge in the OSMnx network. Important fields include:
| Field | Description |
|---|---|
u, v, key |
Identifier for an edge in the directed multigraph |
osmid |
OpenStreetMap way ID |
geometry |
Segment geometry in WKT format |
LTS_fwd |
LTS in the forward graph direction |
LTS_rev |
LTS in the reverse direction |
LTS |
Higher of the forward and reverse LTS values |
bike_allowed_fwd |
Whether bicycle travel is allowed in the forward direction |
bike_allowed_rev |
Whether bicycle travel is allowed in the reverse direction |
bike_lane_fwd, bike_lane_rev |
Interpreted bicycle-lane information by direction |
separation_fwd, separation_rev |
Interpreted physical separation by direction |
parking_fwd, parking_rev |
Interpreted parking conditions by direction |
speed |
Tagged or assumed prevailing speed |
lane_count |
Interpreted number of motor-vehicle lanes |
ADT |
Tagged or assumed average daily traffic |
zoom |
Minimum map zoom level assigned to the segment |
Many calculated fields also have a corresponding *_rule or *_condition column. These record the OpenStreetMap value, rule, or assumption used to derive the result.
For routing or accessibility analysis, use the directional fields rather than relying only on the combined LTS value.
Rows with an LTS of 0 or without a positive LTS value are excluded from the generated map GeoJSON.
Municipality definitions are stored in constants.py. Each entry contains an OpenStreetMap relation key and value used to build the Overpass queries.
For example:
CITIES = {
"Cambridge": {
"key": "wikipedia",
"value": "en:Cambridge, Massachusetts"
}
}To add another municipality:
- Find the boundary relation on openstreetmap.org.
- Identify a key and value that select the intended area in an Overpass query.
- Add the municipality to
CITIESinconstants.py. - Run the normal
processcommand using the new dictionary key.
The current OSMnx download step appends , Massachusetts to the municipality name. Supporting locations outside Massachusetts therefore requires an additional code change.
The LTS calculation is controlled by files in config/.
| File | Purpose |
|---|---|
tables.yml |
LTS decision tables |
rating_dict.yml |
Rules and assumptions used to interpret road characteristics |
lane_parse.yml |
Directional bicycle access and lane-parsing rules |
filter_test.yml |
Conditions used by the filter-testing utility |
LTS-Tables-v2.2.pdf |
Reference LTS table document |
Changes to these files can affect results across the entire dataset. Review the corresponding functions in lts_functions.py before changing the calculation rules.
.
├── main.py # Command-line entry point
├── LTS_OSM.py # Data download and processing pipeline
├── lts_functions.py # OSM interpretation and LTS calculations
├── LTS_plot.py # CSV-to-GeoJSON conversion
├── constants.py # Configured municipalities
├── web.py # Local HTTP server
├── config/ # LTS tables and parsing rules
├── query/ # Base Overpass query templates
├── map/ # Local map pages
├── mapbox/ # Mapbox Tilesets configuration and notes
├── database/ # SQLite loading utilities
├── geojson/ # Additional GeoJSON utilities
└── isochrone.py # Experimental isochrone work
main.py, LTS_OSM.py, lts_functions.py, and LTS_plot.py make up the standard processing workflow.
The intersection-LTS functions and isochrone.py are not currently part of the main command-line pipeline.
The included local map loads the generated plots/LTS.json file and displays the LTS rating for each segment.
Start it on the default port:
python web.pyUse another port if needed:
python web.py -port 8080The page uses Mapbox GL JS for the basemap, so it still requires an internet connection.
Large GeoJSON files may load slowly in the browser. Boston output has previously been observed to be around 40 MB, although the exact size depends on the OpenStreetMap data available when processing is run.
Instructions for publishing the data through Mapbox Tilesets are available in mapbox/mapbox_readme.md.
- Results reflect the OpenStreetMap data available when the analysis is run.
- Missing or inconsistent OSM tags may lead to assumed values.
- Assumptions are defined in config/rating_dict.yml and are retained in the output rule columns.
- The overall LTS field uses the more stressful of the two calculated directions.
- Intersection LTS code exists in the repository but is not enabled in the current main workflow.
- The isochrone code is experimental and requires additional graph and node outputs that are not generated by the standard CLI.
- StressMap results should be reviewed before being used for planning or policy decisions.
This project is licensed under the MIT License.