Skip to main content

bikelts

Bicycle Level of Traffic Stress (LTS 1-4) for Python. bikelts classifies street segments and intersections for cycling from OpenStreetMap tags or from your own tabular attributes, records why each segment got its score and which inputs were assumed, and analyses the connectivity of the resulting low-stress network.

It is written for active-transportation planners and researchers who need a transparent, reproducible LTS map rather than a black box.

  • Three selectable, documented rulesets: Mekuria, Furth & Nixon (2012), Furth (2017) v2.0, and the Bike Ottawa OSM tag rules.
  • Directional results (lts_forward, lts_backward) with contraflow handling.
  • Per-segment reasons and an assumption log, e.g. maxspeed missing -> assumed 50 km/h from highway=residential urban default for country DE.
  • Intersection criteria: right-turn-lane approaches and unsignalized crossings.
  • Network analysis in pure Python: LTS shares, low-stress islands, connectivity metrics, Dijkstra with an LTS cap, origin-destination reachability and detour ratios.
  • Offline readers for OSM XML, Overpass JSON and GeoJSON; optional Overpass download.
  • No required dependencies; optional geopandas and networkx converters.

Installation

pip install bikelts              # core, no dependencies
pip install "bikelts[geo]"       # + geopandas/shapely (to_geodataframe)
pip install "bikelts[graph]"     # + networkx (to_networkx)
pip install "bikelts[all]"

Python 3.9 or newer.

Quick start

Command line

# Optional: fetch highways for a bounding box (south west north east) from Overpass
bikelts download --bbox 41.000 29.000 41.010 29.015 -o area.json --timeout 60

# Classify (OSM XML .osm, Overpass .json or GeoJSON) and write GeoJSON with LTS + reasons
bikelts classify area.osm -o lts.geojson --method furth2017 --country DE --intersections

# Low-stress islands, LTS shares and OD reachability
bikelts islands lts.geojson --max-lts 2 -o islands.geojson
bikelts stats lts.geojson --max-lts 2
bikelts reach lts.geojson --max-lts 2 --pairs 500 --max-detour 1.25

# Tabular input: CSV in, CSV out (columns listed below)
bikelts classify segments.csv -o segments_lts.csv --method mekuria2012

bikelts methods      # list rulesets with their sources

Every analysis command accepts --json for machine-readable output.

Python

from bikelts import LTSParameters, classify_tags

params = LTSParameters(country="DE")
r = classify_tags(
    {"highway": "secondary", "maxspeed": "50", "cycleway:right": "lane", "parking:right": "lane"},
    method="furth2017",
    params=params,
)
r.lts_forward, r.lts_backward       # (2, 3)
r.reasons
# ['forward: bike lane alongside parking: 1 lane per direction, reach 12-14 ft (reach 13 ft), 30 mph -> LTS 2',
#  'backward: mixed traffic, 1 thru lane per direction, ADT 3000+, 30 mph -> LTS 3']
r.assumptions
# ['ADT missing -> assumed 9000 veh/day from highway=secondary default',
#  'lanes missing -> assumed 2 from highway=secondary two-way default', ...]

A whole extract and its network:

from bikelts import (LTSNetwork, LTSParameters, classify_osm, connectivity_metrics,
                     detect_crossings, low_stress_islands, od_reachability,
                     random_od_pairs, read_osm, to_geojson, write_geojson)

params = LTSParameters(country="DE")
data = read_osm("area.osm")                       # .osm, Overpass .json or .geojson
ways = classify_osm(data, "furth2017", params)
crossings = detect_crossings(data, ways, params)
write_geojson("lts.geojson", to_geojson(ways, crossings, data))

net = LTSNetwork.from_classified(ways, crossings)
islands = low_stress_islands(net, max_lts=2)
metrics = connectivity_metrics(net, max_lts=2)
results, summary = od_reachability(net, random_od_pairs(net, 500, seed=1), max_lts=2)
summary["low_stress_connected_share"]           # share of OD pairs connected at LTS <= 2

Tabular records (dicts or CSV rows) use classify_record / classify_records. See examples/ for a runnable script, a synthetic OSM extract and a sample CSV.

Rulesets

Select with method= (Python) or --method (CLI).

mekuria2012 furth2017 (default) bikeottawa
Source Mekuria, Furth & Nixon (2012), MTI Report 11-19 Furth (2017), LTS Criteria for Road Segments v2.0 Bike Ottawa stressmodel.js v1.0.1
Mixed traffic total lanes (2-3 / 4-5 / 6+) x speed (<=25 / 30 / 35+ mph); lower value when no centreline and ADT <= 3000 thru lanes per direction (unlaned / 1 / 2 / 3+) x ADT x speed (<=20 ... 50+ mph); effective ADT = 1.5 x ADT on one-way roads maxspeed (km/h), total lanes, highway=residential, service types
Bike lane, no parking weakest link over lanes/median, width (6 ft), speed, blockage lanes x width (6+ / 4-5 ft) x speed; < 4 ft does not qualify lanes, explicit width, speed, residential
Bike lane with parking weakest link over lanes, reach (15 / 14 / 13.5 ft), speed, blockage lanes x reach (15+ / 12-14 ft) x speed (<= 35 mph); reach < 12 ft does not qualify lanes, explicit width, speed, residential
Separated paths, cycle tracks LTS 1 LTS 1 LTS 1 (also highway=footway)
Blocked lanes blockage row (LTS 3) rated as mixed traffic not used
ADT, centreline ADT <= 3000 + no centreline footnote central not used
Direction per direction per direction one score per way
Intersections Table 5 and 6 (below) Table 5 and 6 (below) Table 6 if requested

Table numbers for the 2012 criteria follow Furth's republished LTS Criteria Tables: Table 1 segments by bikeway type, Table 2 bike lanes alongside a parking lane, Table 3 bike lanes not alongside a parking lane, Table 4 mixed traffic, Table 5 intersection approaches with a right-turn lane, Table 6a/6b unsignalized crossings without/with a crossing island. The 2017 v2.0 document has three unnumbered tables (mixed traffic; bike lanes and shoulders not adjacent to a parking lane; bike lanes alongside a parking lane). The test suite checks every cell of all of these tables.

Intersections

  • Right-turn lanes (Table 5): right_turn_config(length_ft, bike_lane_position, turning_speed_mph, rt_lanes, option_lane) returns one of short_straight (LTS >= 2), long_straight (>= 3), shift_left (>= 3) or other (4). A table row can carry rt_lane_config; the floor is applied to bike lanes and mixed traffic, not to separated facilities. OSM does not describe these geometries, so this is table input only.
  • Unsignalized crossings (Table 6): unsignalized_crossing_lts(lanes, speed_mph, island). In OSM data, detect_crossings finds nodes where a dominant street (the hardest to cross, plus ways sharing its name/ref) passes through and a less major way meets it. Signals (highway=traffic_signals, crossing=traffic_signals, crossing:signals=yes) give LTS 1; islands come from crossing:island=yes, crossing=island or traffic_calming=island. Furth's 2017 update revised segments only, so the 2012 intersection tables are used with every ruleset.

In network analysis, a crossing with LTS above the cap blocks movements between two non-dominant approaches (going straight across), while turning onto the dominant street is governed by that street's own segment LTS.

Inputs, defaults and assumptions

OSM tags understood

Topic Tags
Access highway, bicycle (no, use_sidepath, dismount close the way), access, vehicle, motor_vehicle
Direction oneway (yes/-1/reversible), oneway:bicycle, junction=roundabout, cycleway=opposite*, cycleway:<side>:oneway
Bikeways cycleway, cycleway:left/right/both = lane, track, shared_lane, share_busway, opposite_lane, opposite_track, opposite, separate, no; subkeys :lane (advisory, pictogram), :width, :buffer, :separation (kerb, bollard, flex_post... make a lane a protected lane), sidewalk[:side]:bicycle=designated, shoulder[:side] + shoulder:access:bicycle
Speed maxspeed (50, 30 mph, DE:urban, FR:zone30, GB:nsl_single, none, walk), maxspeed:forward/backward, zone:maxspeed, source:maxspeed, bicycle_road/cyclestreet
Lanes lanes, lanes:forward/backward, lanes:bus/psv (excluded), lane_markings, dual_carriageway
Parking new scheme parking:<side>=lane/street_side/on_kerb/half_on_kerb/no/separate, old scheme parking:lane:<side>=parallel/diagonal/perpendicular/no_parking/no_stopping, parking:<side>:width
Other surface (paved / cobbled / unpaved classes), adt/aadt if you add counts

Sides are mapped to travel directions using the driving side of the country (left-hand traffic for GB, IE, JP, AU, NZ, IN, ZA and others).

Defaults (all logged when used)

Missing input Default
maxspeed national urban/rural/motorway/living-street limit for 35 countries (LTSParameters(country=..., context=...)), generic 50/90/120 km/h otherwise; service/track capped at 30 km/h
lanes by class: trunk 4, primary...residential 2 (one-way: half)
centreline none on residential, living_street, service, track; marked elsewhere
ADT planning-level guess by class: residential 600, unclassified 1500, tertiary 4000, secondary 9000, primary 15000, trunk 25000 veh/day (adt_overrides to change)
bike lane width 1.5 m (about 5 ft); buffer yes = 0.5 m; shoulder 1.2 m
parking next to a bike lane absent (assume_parking=True to flip); parking lane 2.4 m
blockage rare (default_blockage="frequent")
speed columns metric speeds are rounded to the nearest 5 mph before the mph tables (50 km/h -> 30 mph); speed_binning="exact" disables this
widths rounded to the nearest 0.5 ft before the ft tables

Tabular columns

id, highway, facility (mixed | bike_lane | shoulder | separated | no_motor_traffic), speed_mph or speed_kmh, lanes or lanes_per_direction, oneway, contraflow, centerline, adt, median, residential, blockage (rare | frequent), bike_lane_width_ft|_m, buffer_ft|_m, parking, parking_width_ft|_m, reach_ft|_m, rt_lane_config, permitted, surface. All optional; missing values are assumed and logged.

Network analysis

  • lts_share(net) - length and share by LTS (worst permitted direction).
  • low_stress_islands(net, max_lts=2) - components of the network of edges with LTS <= k in at least one direction, broken at crossings whose LTS exceeds k.
  • connectivity_metrics(net, max_lts) - low-stress length and share, number of islands, largest-island shares, concentration index sum(L_i^2)/(sum L_i)^2, alpha/beta/gamma indices.
  • shortest_path, distances_from - Dijkstra with direction, an LTS cap and crossing restrictions (states are node + incoming edge at crossing nodes).
  • od_reachability(net, pairs, max_lts=2, max_detour=1.25) - for each OD pair the unrestricted and the low-stress shortest distance and their ratio; the summary gives the share reachable at low stress and the share connected within the detour allowance (25 % by default, following Mekuria et al. 2012).
  • to_networkx(net, max_lts) and to_geodataframe(ways) with the optional extras.

Limitations

  • LTS is only as good as the tags. OSM rarely records ADT, bike lane width, parking, medians or right-turn lane geometry; check the assumptions field and supply better data (tags, adt_overrides, table input) where it matters.
  • The speed used is the posted limit, not the prevailing (85th percentile) speed the criteria prefer; pass measured speeds through table input when you have them.
  • Divided roads mapped as two one-way ways are counted per carriageway; use dual_carriageway=yes or LTSParameters(infer_dual_carriageway=True) for the 2012 street width.
  • Crossing detection is a heuristic (dominant street by Table 6 LTS, lanes and speed); left turns, roundabouts and the draft roundabout criteria are not modelled.
  • The bikeottawa port follows the reference rules but takes speeds and lanes from the bikelts parser (country defaults, units) and uses widths only when tagged; see the module docstring.
  • Surfaces are reported but not scored unless you opt in with unpaved_min_lts, which is an extension and not part of any published criteria.
  • Default speed tables reflect general national limits; regional and local rules differ.

References

  • Mekuria, M. C., Furth, P. G., & Nixon, H. (2012). Low-Stress Bicycling and Network Connectivity (MTI Report 11-19). Mineta Transportation Institute, San Jose State University.
  • Furth, P. G. LTS Criteria Tables (republication of the 2012 criteria, Tables 1-6), Northeastern University.
  • Furth, P. G. (2017). Level of Traffic Stress Criteria for Road Segments, version 2.0, June 2017. Northeastern University.
  • Bike Ottawa. stressmodel (version 1.0.1), https://github.com/BikeOttawa/stressmodel
  • OpenStreetMap Wiki. Key:cycleway, Key:parking, Default speed limits.
  • Kansky, K. J. (1963). Structure of Transportation Networks. University of Chicago.

If you use bikelts in research, please cite the method papers above and this software (see CITATION.cff).

Development

pip install -e ".[dev]"
pytest --cov=bikelts
ruff check . && ruff format --check src tests && mypy src

License

MIT, see LICENSE. OSM data is (c) OpenStreetMap contributors under the ODbL; the sample extract in examples/ is synthetic.

Metadata

Release files for bikelts 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for bikelts 0.1.0
File Size Uploaded Provenance Actions
bikelts-0.1.0.tar.gz 79.9 kB With provenance Details for bikelts-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for bikelts 0.1.0
File Interpreter ABI Platform Actions
bikelts-0.1.0-py3-none-any.whl Python 3 none any Details for bikelts-0.1.0-py3-none-any.whl

Total release size: 139.5 kB

Release files / bikelts-0.1.0.tar.gz

Download URL bikelts-0.1.0.tar.gz
Size 79.9 kB
Tags Source
SHA-256 checksum
How to use checksums
f593d8b6e1d409f1901fbf364c861ec73851c0cf48931abaf6de60ae20352a12
BLAKE2b-256 checksum
How to use checksums
a96a1898dd8c57a78428ff01884985e36aff98838c6d290ca52517c52edf5565
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

Release files / bikelts-0.1.0-py3-none-any.whl

Download URL bikelts-0.1.0-py3-none-any.whl
Size 59.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
a4d688dbed38a646d5e2d79e8682801cee4a6bb0c8d174b3555397a09155219c
BLAKE2b-256 checksum
How to use checksums
4def247477eeda99e7ae769cd2c81a826478f648ba67d963a6b24d29d55bab71
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

Security for bikelts 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for bikelts-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 8, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page