Classify OpenStreetMap ways by Level of Traffic Stress (LTS) using the Furth methodology.
Project description
osm-lts
Classify OpenStreetMap ways by Level of Traffic Stress (LTS) using the Furth methodology.
LTS is a 1–4 scale from "kid-comfortable" (1) to "strong-and-fearless only" (4). It's the standard advocacy and planning input for "where is the bike network actually rideable for a typical adult" — far more honest than miles of "bike infrastructure" because it captures whether that infrastructure is on a calm street or a six-lane arterial.
Install
pip install osm-lts
Pure Python, no dependencies, Python 3.9+.
Use
from osm_lts import classify
classify({"highway": "residential", "maxspeed": "25 mph"})
# <LTS.MOST_ADULTS: 2>
classify({"highway": "primary"})
# <LTS.STRONG_AND_FEARLESS: 4>
classify({"highway": "cycleway"})
# <LTS.KID_COMFORTABLE: 1>
classify({"highway": "footway"})
# None — outside scope (not relevant to cyclist stress)
The function takes any Mapping[str, str] of OSM tags. Numeric tags
(maxspeed, lanes) tolerate units ("25 mph", "50 km/h", "4;3") —
only the leading digits are read. The result is an IntEnum, so
int(classify(tags)) gives you the bare LTS value for serialization.
CLI
The package ships with an osm-lts command for batch jobs:
echo '{"highway": "residential", "maxspeed": "25 mph"}' | osm-lts classify
# {"tags": {"highway": "residential", "maxspeed": "25 mph"}, "lts": 2}
osm-lts classify --in ways.jsonl --out lts.jsonl
Input is JSON, JSONL, or a single JSON array — auto-detected.
How it works
The classifier mirrors Furth's published rules:
| Tier | Description | Example triggers |
|---|---|---|
| LTS 1 | Suitable for children | highway=cycleway, living_street, cycleway=track |
| LTS 2 | Most adults will tolerate | residential ≤25 mph, bike lane on a slow street |
| LTS 3 | Experienced cyclists only | tertiary, fast residential, bike lane on a faster street |
| LTS 4 | Strong-and-fearless only | primary / trunk, >35 mph, ≥3 lanes and >30 mph |
The branches evaluate top-to-bottom and short-circuit on the first match.
Order matters — a cycleway=track on a 40 mph arterial returns LTS 1
because separation wins over speed. Highways outside scope (motorway,
footway, sidewalk, steps, pedestrian) return None.
When maxspeed or lanes are missing, highway-typical defaults fill in:
from osm_lts import (
DEFAULT_SPEED_MPH_BY_HIGHWAY,
DEFAULT_LANE_COUNT_BY_HIGHWAY,
EXCLUDED_HIGHWAYS,
)
These are public so callers can read them in their own UIs (e.g. "we assumed 25 mph because the way was untagged").
Customizing the rules
Wrap a Classifier instance to override any of the defaults. Useful for
modeling a city or country whose posted-speed conventions or in-scope
highway set differ from the US-centric defaults the package ships with.
from osm_lts import Classifier, EXCLUDED_HIGHWAYS
# Stricter unknown-speed default:
strict = Classifier(speed_mph_fallback=20)
strict({"highway": "residential"}) # <LTS.MOST_ADULTS: 2>
# Drop pedestrian-priority paths out of scope entirely:
narrower = Classifier(excluded_highways=EXCLUDED_HIGHWAYS | {"path"})
narrower({"highway": "path", "bicycle": "designated"}) # None
# Per-highway speed overrides:
slower_residential = Classifier(speed_mph_by_highway={"residential": 20})
Classifier is a frozen dataclass — instances are hashable and
thread-safe to share. Use dataclasses.replace(clf, ...) for tweaked
copies.
Origin
Extracted from the Bike Streets city-mapping platform.
Development
pip install -e '.[test]'
pytest
License
MIT. See LICENSE.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file osm_lts-0.3.0.tar.gz.
File metadata
- Download URL: osm_lts-0.3.0.tar.gz
- Upload date:
- Size: 13.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bbed55080f528f05dd38bcb8b76b5a388957182727449fa874c4ae77c394a913
|
|
| MD5 |
86249de8019024860d58f4fbfb223c83
|
|
| BLAKE2b-256 |
acedecbc486094bc210df9fb98a5324a71e89f377f7ab8f68c9a98d48e723d4c
|
Provenance
The following attestation bundles were made for osm_lts-0.3.0.tar.gz:
Publisher:
release.yml on bikestreets/osm-lts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
osm_lts-0.3.0.tar.gz -
Subject digest:
bbed55080f528f05dd38bcb8b76b5a388957182727449fa874c4ae77c394a913 - Sigstore transparency entry: 1549096119
- Sigstore integration time:
-
Permalink:
bikestreets/osm-lts@334e5d9be16e1de57150c23d353f2687bedf8399 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/bikestreets
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@334e5d9be16e1de57150c23d353f2687bedf8399 -
Trigger Event:
push
-
Statement type:
File details
Details for the file osm_lts-0.3.0-py3-none-any.whl.
File metadata
- Download URL: osm_lts-0.3.0-py3-none-any.whl
- Upload date:
- Size: 11.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00ae875a3e96e22b008b0f7e65b6b76213d016b8711d21d44d1d75901ec83ba6
|
|
| MD5 |
35809e53cb89111a2ef529c489275722
|
|
| BLAKE2b-256 |
48a6da6ffb7ffa2c3f23e8baf66a28c6928db57cc38754937a9877e9a00e75e0
|
Provenance
The following attestation bundles were made for osm_lts-0.3.0-py3-none-any.whl:
Publisher:
release.yml on bikestreets/osm-lts
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
osm_lts-0.3.0-py3-none-any.whl -
Subject digest:
00ae875a3e96e22b008b0f7e65b6b76213d016b8711d21d44d1d75901ec83ba6 - Sigstore transparency entry: 1549096130
- Sigstore integration time:
-
Permalink:
bikestreets/osm-lts@334e5d9be16e1de57150c23d353f2687bedf8399 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/bikestreets
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@334e5d9be16e1de57150c23d353f2687bedf8399 -
Trigger Event:
push
-
Statement type: