- TypeScript 85.7%
- CSS 11.5%
- Astro 2.7%
| data/input | ||
| public/data | ||
| scripts | ||
| src | ||
| supabase | ||
| tests/turn-preview | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| astro.config.mjs | ||
| CLAUDE.md | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
Better Bike Turns
Static-first civic-tech web app for Karlsruhe bicycle right-turn-on-red screening. Note: the entire work has been done by @maxliesegang I (@Ivovic) am currently learning html and css and just opened the project on the open data days in Karlsruhe.
Stack
- Astro
- TypeScript
- Leaflet
- proj4
- tsx
- Optional Supabase vote sync over the REST API
- Node.js >= 22.12.0
What ships today
The repository has two main parts:
scripts/build-review-data.tsprepares static review data for Karlsruhe.- The Astro frontend loads that generated data into a Leaflet review map.
The data build:
- fetches Karlsruhe traffic signals, drivable roads, and bicycle infrastructure from Overpass
- optionally imports official city bicycle green-arrow records from
data/input/karlsruhe-official-green-arrows.csv - keeps only OSM traffic signals that can be linked to at least one plausible right turn onto a different road with bicycle infrastructure on the exit side
- writes prepared JSON assets into
public/data/
The frontend:
- loads the generated manifest and OSM signal dataset
- renders one marker per kept OSM signal
- shows a right-turn preview when a signal is selected
- shows screening notes and OSM tag status in the detail panel
- supports local upvote/downvote review state, with optional shared vote sync through Supabase
The current UI does not load the official CSV GeoJSON as a separate map layer. Instead, official CSV matches are surfaced by marking the nearest kept OSM signal as official.
Product framing
- The app is a review aid only, not an approval tool.
- Every generated OSM signal remains a plausible review item that still needs expert review.
- Official city records are used as context and matching evidence, not as automatic approval.
Local development
npm install
npm run data:build
npm run dev
If the generated files are missing, the app still boots but the review UI shows a missing-data state until public/data/ is rebuilt.
Generated files
npm run data:build writes:
public/data/karlsruhe-osm-car-signals.geojson: the primary frontend dataset, containing kept OSM signals plus screening metadatapublic/data/karlsruhe-official-green-arrows.geojson: the imported official city records as a standalone reference/export filepublic/data/karlsruhe-review-manifest.json: counts, heuristic metadata, disclaimers, and output paths
Overpass responses are cached under .cache/osm/, keyed by SHA-256 of the query body.
To force fresh Overpass responses:
npm run data:refresh
Screening heuristic
The current builder:
- skips explicitly pedestrian-only signals
- links each candidate signal to a nearby approach road and junction sample
- honors
traffic_signals:direction=forward/backwardwhen a signal is mapped onto the linked approach road - uses mapped one-way traffic flow on the linked road before falling back to side-of-road inference
- keeps only signals with at least one plausible clockwise right turn onto a different outgoing road
- requires mapped bicycle infrastructure on the exit side of that turn
- records crossing tags, signal offset, multiple plausible turns, and nearby official records as review notes instead of hard exclusions
- stores a
rightTurnHintwith bearings and turn angle so the UI can render a preview arc - lets the preview note inherit a nearby named road corridor when the outgoing connector itself is unnamed but the aligned exit road makes the reviewed turn leg clear
- lets the preview fallback stay on the signal's own road corridor when OSM splits that approach into connected continuation segments before the junction
- marks only the single closest kept OSM signal within 25 m of an official CSV record as
official
In the current generated dataset, kept OSM signals are classified as either highlighted or official.
Review UI behavior
Fit allzooms to the generated review dataset.Basemapswitches between OpenStreetMap street tiles and Esri satellite imagery.Highlightedtoggles the orange halo overlay for highlighted signals; it does not hide the underlying markers.- Clicking near overlapping markers cycles through nearby candidates. Shift+click also cycles.
- Votes are stored locally in the browser by default.
- When
PUBLIC_SUPABASE_URLandPUBLIC_SUPABASE_ANON_KEYare set, the app also loads shared vote aggregates and upserts one vote per browser session and signal.
Without Supabase, votes stay in browser local storage under better-bike-turns:review-votes:v1.
Official CSV import
Place the city export at:
data/input/karlsruhe-official-green-arrows.csv
Accepted coordinate columns (first match wins):
| Purpose | Accepted column names |
|---|---|
| Longitude / easting | lon, longitude, lng, x, easting, rechtswert |
| Latitude / northing | lat, latitude, y, northing, hochwert |
| CRS override | epsg, crs, srid |
| Record ID | id, identifier, signal_id, geometry_id |
| Street name | street, strasse, straße |
| Title / location | title, location, name |
| Status | status, state |
| Notes | note, notes, comment, comments |
If projected x/y coordinates are used without an explicit CRS column, the importer defaults to EPSG:25832.
Verification
npm test
npm run check
npm run build
Notes:
npm testexercises the right-turn preview geometry against the generatedpublic/data/karlsruhe-osm-car-signals.geojsonfixture.npm run buildbuilds the Astro site but does not regeneratepublic/data/.npm run build:allruns the data build first and then the Astro build.
Optional Supabase vote sync
Set these public environment variables to enable shared vote sync:
PUBLIC_SUPABASE_URL=
PUBLIC_SUPABASE_ANON_KEY=
Apply the schema and policies from:
supabase/review_votes.sql
The schema enforces one vote per browser session per signal with unique(signal_id, session_id). The client uses plain REST calls and falls back to local-only voting if sync is unavailable.