This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Calculates cycling Level of Traffic Stress (LTS) for road/path segments in a region using OpenStreetMap data. LTS values (1–4) represent how stressful a segment is for cyclists, based on Furth (2016). Adapted from Bike Ottawa's stressmodel and mbonsma's LTS-OSM.
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txtThe OVERPASS_API_URL and LTS_OUTPUT_DIR environment variables can override defaults (http://overpass-api.de/api/interpreter and output/, respectively).
# Run LTS calculation for one or more areas defined in a query JSON file
python lts_osm/lts_osm.py --query-json-file query/gta.json
# Re-run LTS calculation using already-downloaded XML files (skips Overpass download)
# Pass the areas_xml_file_path.json written to the run directory by a prior run
python lts_osm/lts_osm.py --downloaded-xml-json-map output/runs/<timestamp>/areas_xml_file_path.json
# Re-run using a single previously downloaded OSM XML file
python lts_osm/lts_osm.py --osm-file output/runs/<timestamp>/xmls/toronto.xml
# Plot results
python lts_osm/lts_plot.py --lts-csv-file output/runs/<timestamp>/lts_csv/all_lts_toronto.csv --gdf-nodes-file output/runs/<timestamp>/lts_csv/gdf_nodes_toronto.csv --city "Toronto"
# Run tests
pytest lts_osm/lts_functions.py- Query JSON file (
query/*.json) — JSON file listing one or more areas by name and wikidata ID (e.g.query/gta.json). The Overpass QL query itself is built at runtime fromquery/query_template.overpass. lts_osm/lts_osm.py— Entry point. Downloads OSM XML per area via Overpass API → builds anosmnxgraph → converts to GeoPandas GeoDataFrames (gdf_nodes,gdf_edges) cached as GeoParquet → applies LTS classification pipeline → writes outputs. All outputs for a run go into a timestamped directoryoutput/runs/<timestamp>/.lts_osm/lts_functions.py— All LTS classification logic. Pure functions operating on GeoDataFrame slices.lts_osm/lts_plot.py— Reads the CSV outputs from step 2 and produces PDF/PNG maps.lts_osm/isochrone.py— Standalone notebook-style script for isochrone analysis from a point on the LTS graph.
Each stage splits edges and assigns a rule code (e.g. p2, s3, b1, m5); the rule maps to an LTS integer:
biking_permitted— removes ways where cycling is banned (rule prefixp); remainder proceeds.is_separated_path— detects cycleways/paths/tracks (prefixs); assigned LTS 1.is_bike_lane— detects bike lanes (cycleway tags); remainder goes to mixed traffic.parking_present— splits bike-lane edges by parking presence.bike_lane_analysis_with_parking/bike_lane_analysis_no_parking— assign LTS 1–4 based on speed, lanes, width (prefixb/c).mixed_traffic— assigns LTS 1–4 based on highway type, speed, lanes (prefixm).- Node LTS — assigned in
lts_osm.pyas max of intersecting edge LTS (vectorized), reduced by traffic signals or stop signs.
get_lanes and get_max_speed (in lts_functions.py) normalize messy OSM values before comparisons:
- Lanes: handles
None, lists, delimited strings ("2;1","2|1"), decimals — takesceil(max(...))to stay conservative. - Speed: handles lists and unparseable strings — defaults to 50 km/h (
localparameter). - Speed defaults by highway type:
national→40,motorway→100,primary/secondary→80, otherwise→50.
Each run creates a timestamped directory. Within it:
| Path | Content |
|---|---|
xmls/<area>.xml |
Raw Overpass XML download per area |
areas_xml_file_path.json |
Manifest mapping area names → XML paths (pass to --downloaded-xml-json-map to skip re-downloading) |
parquet/<area>_nodes.parquet |
GeoDataFrame of nodes cached as GeoParquet |
parquet/<area>_edges.parquet |
GeoDataFrame of edges cached as GeoParquet |
lts_csv/all_lts_<area>.csv |
Edge LTS with rule codes |
lts_csv/gdf_nodes_<area>.csv |
Node LTS |
lts_geojson/all_lts_<area>.geojson |
Edge LTS (filtered LTS 1–4) |
lts_geojson/gdf_nodes_<area>.geojson |
Node LTS (filtered LTS 1–4) |
lts_geojson/combined/all_lts_combined.geojson |
Combined edges across all areas (multi-area runs only) |
lts_outputs.json |
Manifest of all output file paths for the run |
If parquet/<area>_nodes.parquet and parquet/<area>_edges.parquet already exist in the run directory, the osmnx graph build step is skipped and the GeoDataFrames are loaded directly from parquet.
tippecanoe -l lts_toronto_filtered_1_4 -n "Level of Traffic Stress" \
--no-feature-limit --extend-zooms-if-still-dropping \
--coalesce-densest-as-needed --maximum-tile-bytes=2000000 \
-P -zg -D12 -o output.mbtiles input.geojson
pmtiles convert output.mbtiles output.pmtiles