Skip to main content

Classify OpenStreetMap ways by Level of Traffic Stress (LTS) using the Furth methodology.

Project description

osm-lts

PyPI version Python versions CI License: MIT

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

osm_lts-0.2.0.tar.gz (12.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

osm_lts-0.2.0-py3-none-any.whl (11.0 kB view details)

Uploaded Python 3

File details

Details for the file osm_lts-0.2.0.tar.gz.

File metadata

  • Download URL: osm_lts-0.2.0.tar.gz
  • Upload date:
  • Size: 12.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for osm_lts-0.2.0.tar.gz
Algorithm Hash digest
SHA256 cd73f201d9096ed1705220d28f16da47fa9f35a33ee4e0eb52dd7ff92787dbf7
MD5 3bbe7e4f83132761df818b7ed2aee8bb
BLAKE2b-256 da9a75d846a79123cafb7d619283e8996d07ef602c74d386b08ca19987b077c2

See more details on using hashes here.

Provenance

The following attestation bundles were made for osm_lts-0.2.0.tar.gz:

Publisher: release.yml on bikestreets/osm-lts

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file osm_lts-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: osm_lts-0.2.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

Hashes for osm_lts-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f17bdd56ade11442bdc79fb8e7482f379a2fb8e43225e1580642c87cac85d99c
MD5 aa99f29f38109e8f4d696b77a9315cd0
BLAKE2b-256 22c398cb469ee5015f64e02949352f456b8aaa56f144ee2e51a352631362f673

See more details on using hashes here.

Provenance

The following attestation bundles were made for osm_lts-0.2.0-py3-none-any.whl:

Publisher: release.yml on bikestreets/osm-lts

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page