Add road type color mode to route visualization

Extract highway=* tags from BRouter tiledesc messages alongside surface
data and add a new "Road Type" color mode that colors the route polyline
and elevation chart by OSM highway classification (cycleway, residential,
path, etc.). Includes color palette, legend, hover labels, i18n (EN+DE),
unit tests for tag extraction, and E2E tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Ullrich Schäfer 2026-04-11 03:41:49 +02:00
parent f4f0cbc4c8
commit 1e2f6beded
No known key found for this signature in database
GPG key ID: A32FF691A0F752D9
18 changed files with 690 additions and 21 deletions

View file

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-04-11

View file

@ -0,0 +1,61 @@
## Context
The Planner's route visualization supports four color modes: plain, elevation, surface, and grade. These modes color both the map polyline (`ColoredRoute.tsx`) and the elevation chart (`ElevationChart.tsx`), synced via a `colorMode` key in Yjs `routeData`.
Surface data is already extracted from BRouter's tiledesc messages by parsing `surface=*` from the `WayTags` column. The same `WayTags` column contains `highway=*` tags (e.g., `highway=residential`, `highway=cycleway`, `highway=track`) which classify the road type — but this data is currently discarded.
The data pipeline is: BRouter response → `extractSurfacesFromMessages()``EnrichedRoute.surfaces[]` → Yjs `routeData.surfaces` → consumed by chart and map. Road type will follow the same path.
## Goals / Non-Goals
**Goals:**
- Add a "Road Type" color mode that colors the route by OSM highway classification
- Follow the exact same patterns as the existing surface color mode (extraction, storage, rendering)
- Provide a meaningful color palette that distinguishes road categories at a glance
- Support both EN and DE translations
**Non-Goals:**
- Custom color palettes or user-configurable road type colors
- Filtering or hiding certain road types from the route
- Road type data in the Journal app (Planner-only for now)
- Aggregated road type statistics (e.g., "42% cycleway") — can be added later
## Decisions
### 1. Extract highway tags alongside surface tags
**Decision**: Extend `extractSurfacesFromMessages` to also extract `highway=*` tags, returning both in a combined result. Add a `highways: string[]` field to `EnrichedRoute`.
**Rationale**: The WayTags column already contains both tags. Extracting them in the same pass avoids iterating the messages twice. Keeping `surfaces` and `highways` as separate parallel arrays maintains backward compatibility and matches the existing pattern.
**Alternative considered**: A single `extractTagsFromMessages` returning a map of tag→values. Rejected because it would change the surface extraction API for no benefit.
### 2. Color palette grouped by road category
**Decision**: Group OSM highway values into intuitive color families:
- **Major roads** (motorway, trunk, primary): reds/oranges — signals caution for cyclists
- **Urban roads** (secondary, tertiary, residential, unclassified): grays/blues — neutral
- **Paths & tracks** (cycleway, path, footway, track, bridleway): greens — generally preferred
- **Service & other** (service, pedestrian, steps, living_street): muted tones
**Rationale**: Cyclists typically want to see at a glance where their route uses dedicated cycling infrastructure vs. shared roads. The red-for-major, green-for-paths scheme makes this immediately visible.
### 3. Extend ColorMode union type
**Decision**: Add `"highway"` to the `ColorMode` type union in `ColoredRoute.tsx`. Use `"highway"` as the internal value (matching the OSM tag name) while displaying "Road Type" / "Straßentyp" to users.
**Rationale**: Internal names match OSM conventions. User-facing labels use i18n and can be friendlier.
### 4. Store highway data in Yjs like surfaces
**Decision**: Store as `yjs.routeData.set("highways", JSON.stringify(highways))`, following the exact pattern used for surfaces.
**Rationale**: Consistency. All per-point route metadata follows this pattern. No schema changes needed.
## Risks / Trade-offs
**[Risk] Missing highway tags in some BRouter profiles** → Some BRouter routing profiles may not include `highway` in WayTags. Mitigation: fall back to `"unknown"` (same as surface mode) and display in a neutral default color.
**[Risk] Too many highway values in the legend** → OSM has dozens of highway values. Mitigation: Show at most 6 in the inline legend (same cap as surface mode), with a "+N" overflow indicator.
**[Trade-off] `highway` internal name vs. `road-type` user-facing name** → Slight naming mismatch, but keeps code aligned with OSM terminology which is the standard data source.

View file

@ -0,0 +1,30 @@
## Why
The Planner already visualizes surface type, grade, and elevation along a route, but there is no way to see the **road type** (highway classification). Cyclists and hikers care whether their route follows a motorway-class road, a residential street, a cycle path, or a forest track — this affects comfort, safety, and legality. BRouter already returns `highway=*` tags in its tiledesc messages alongside surface data, so the information is available but unused.
## What Changes
- Add a new "Road Type" color mode to the route visualization (map polyline + elevation chart)
- Extract `highway=*` tags from BRouter tiledesc messages alongside existing `surface=*` extraction
- Pass road type data through the routing pipeline (BRouter → Yjs → chart/map)
- Add a road type color palette and legend
- Show road type name on chart hover
- Add i18n strings (EN + DE) for the new mode and chart label
## Capabilities
### New Capabilities
- `road-type-coloring`: Road type color mode for route visualization — extracting highway tags from BRouter, coloring the map polyline and elevation chart by road classification, with legend and hover info
### Modified Capabilities
- `route-coloring`: Add "Road Type" as a new color mode option in the color mode toggle, legend display, and chart hover behavior
## Impact
- `apps/planner/app/lib/brouter.ts` — extract `highway=*` tags, add `highways` field to `EnrichedRoute`
- `apps/planner/app/lib/use-routing.ts` — store highway data in Yjs routeData
- `apps/planner/app/components/ColoredRoute.tsx` — extend `ColorMode` type, add highway coloring logic
- `apps/planner/app/components/ElevationChart.tsx` — add highway chart rendering, legend, hover label
- `packages/i18n/src/locales/en.ts` + `de.ts` — new i18n keys
- `e2e/fixtures/brouter-mock.ts` — add highway data to mock responses
- E2E tests — cover the new color mode

View file

@ -0,0 +1,78 @@
## ADDED Requirements
### Requirement: Highway tag extraction from BRouter
The routing pipeline SHALL extract `highway=*` tags from BRouter tiledesc messages and include them in the enriched route data as a per-point `highways` array.
#### Scenario: Highway tags present in BRouter response
- **WHEN** BRouter returns a route with tiledesc messages containing `highway=*` in the WayTags column
- **THEN** the `EnrichedRoute` SHALL include a `highways` string array with one entry per coordinate point
#### Scenario: Highway tags missing from BRouter response
- **WHEN** BRouter returns a route without `highway=*` tags in WayTags
- **THEN** each entry in the `highways` array SHALL be `"unknown"`
#### Scenario: Highway data stored in Yjs
- **WHEN** a route is computed and enriched route data is received
- **THEN** the highway array SHALL be stored in Yjs `routeData` as a JSON-serialized string under the key `"highways"`
### Requirement: Road type color palette
The Planner SHALL define a color mapping for OSM highway classifications, grouped by road category.
#### Scenario: Major roads colored with warm tones
- **WHEN** a route segment has highway type `motorway`, `trunk`, or `primary`
- **THEN** the segment SHALL be colored in red/orange tones
#### Scenario: Urban roads colored with neutral tones
- **WHEN** a route segment has highway type `secondary`, `tertiary`, `residential`, or `unclassified`
- **THEN** the segment SHALL be colored in gray/blue tones
#### Scenario: Paths and cycling infrastructure colored with green tones
- **WHEN** a route segment has highway type `cycleway`, `path`, `footway`, `track`, or `bridleway`
- **THEN** the segment SHALL be colored in green tones
#### Scenario: Unknown highway type
- **WHEN** a route segment has an unrecognized or missing highway value
- **THEN** the segment SHALL be colored with a neutral default color
### Requirement: Road type map polyline coloring
The Planner map SHALL color the route polyline by highway classification when road type mode is active.
#### Scenario: Road type coloring on map
- **WHEN** the color mode is set to "highway"
- **THEN** the route polyline on the map SHALL be colored segment-by-segment using the road type color palette
#### Scenario: Fallback when highway data unavailable
- **WHEN** the color mode is set to "highway" but no highway data is available
- **THEN** the route SHALL fall back to plain color mode
### Requirement: Road type elevation chart coloring
The elevation chart SHALL color segments by highway classification when road type mode is active.
#### Scenario: Road type chart rendering
- **WHEN** the color mode is set to "highway"
- **THEN** the elevation chart line and fill segments SHALL be colored using the road type color palette, matching the map polyline
### Requirement: Road type legend
The elevation chart SHALL display a legend for road type mode showing the highway types present in the route.
#### Scenario: Road type legend display
- **WHEN** the color mode is "highway" and highway data is available
- **THEN** a legend SHALL show colored swatches with highway type labels for the types present in the current route, up to 6 entries with a "+N" overflow indicator
### Requirement: Road type hover information
The elevation chart hover label SHALL include the highway type when in road type mode.
#### Scenario: Road type hover label
- **WHEN** hovering the elevation chart in "highway" mode
- **THEN** the label SHALL show elevation, distance, and highway type name (e.g., "340m · 12.3km · cycleway")
### Requirement: Road type i18n
All user-facing strings for the road type color mode SHALL be translated in English and German.
#### Scenario: English labels
- **WHEN** the app language is English
- **THEN** the color mode dropdown SHALL show "Road Type" and the chart title SHALL show "Road Type Profile"
#### Scenario: German labels
- **WHEN** the app language is German
- **THEN** the color mode dropdown SHALL show "Straßentyp" and the chart title SHALL show "Straßentypenprofil"

View file

@ -0,0 +1,81 @@
## MODIFIED Requirements
### Requirement: Route color modes
The Planner SHALL support multiple route color modes that visualize per-point data along the route.
#### Scenario: Default plain mode
- **WHEN** a route is first displayed
- **THEN** it renders as a single-color blue polyline
#### Scenario: Elevation color mode
- **WHEN** a user selects the "Elevation" color mode
- **THEN** the route polyline is colored with a gradient from green (low) through yellow (mid) to red (high), based on the elevation at each point
#### Scenario: Surface color mode
- **WHEN** a user selects the "Surface" color mode and surface data is available from BRouter tiledesc
- **THEN** the route polyline is colored by surface type (e.g., asphalt=gray, gravel=brown, path=green, track=orange)
#### Scenario: Surface data unavailable
- **WHEN** a user selects "Surface" color mode but BRouter did not return surface data
- **THEN** the route falls back to plain color mode
#### Scenario: Grade color mode
- **WHEN** a user selects the "Grade" color mode
- **THEN** the route polyline is colored by steepness: green (<3%), yellow (<6%), orange (<10%), red (<15%), dark red (15%+)
#### Scenario: Road type color mode
- **WHEN** a user selects the "Road Type" color mode and highway data is available from BRouter tiledesc
- **THEN** the route polyline is colored by OSM highway classification using the road type color palette
#### Scenario: Road type data unavailable
- **WHEN** a user selects "Road Type" color mode but BRouter did not return highway data
- **THEN** the route falls back to plain color mode
### Requirement: Color mode toggle
The Planner SHALL provide a UI control to switch between route color modes.
#### Scenario: Toggle control location
- **WHEN** a route is displayed
- **THEN** a color mode select dropdown is visible inline with the elevation chart title
#### Scenario: Road type option in toggle
- **WHEN** the color mode dropdown is displayed
- **THEN** it SHALL include a "Road Type" option alongside Plain, Elevation, Surface, and Grade
#### Scenario: Color mode persists in session
- **WHEN** a user changes the color mode
- **THEN** the selection is stored in the Yjs routeData map and synced to all participants
### Requirement: Color legends
The elevation chart SHALL display a legend matching the active color mode.
#### Scenario: Grade legend
- **WHEN** the color mode is "Grade"
- **THEN** the legend shows colored swatches with percentage thresholds (<3%, <6%, <10%, <15%, 15%+)
#### Scenario: Elevation legend
- **WHEN** the color mode is "Elevation"
- **THEN** the legend shows a gradient bar with the route's minimum and maximum elevation in meters
#### Scenario: Surface legend
- **WHEN** the color mode is "Surface"
- **THEN** the legend shows the surface types present in the route with colored swatches
#### Scenario: Road type legend
- **WHEN** the color mode is "Road Type"
- **THEN** the legend shows the highway types present in the route with colored swatches, up to 6 entries
### Requirement: Contextual hover information
The elevation chart hover label SHALL show mode-specific information.
#### Scenario: Grade hover
- **WHEN** hovering the chart in "Grade" mode
- **THEN** the label shows elevation, distance, and grade percentage (e.g. "340m · 12.3km · +4.2%")
#### Scenario: Surface hover
- **WHEN** hovering the chart in "Surface" mode
- **THEN** the label shows elevation, distance, and surface type name (e.g. "340m · 12.3km · asphalt")
#### Scenario: Road type hover
- **WHEN** hovering the chart in "Road Type" mode
- **THEN** the label shows elevation, distance, and highway type name (e.g. "340m · 12.3km · cycleway")

View file

@ -0,0 +1,41 @@
## 1. Data Extraction & Pipeline
- [x] 1.1 Extend `extractSurfacesFromMessages` in `brouter.ts` to also extract `highway=*` tags, returning a `Map<number, string>` for highways
- [x] 1.2 Add `highways: string[]` field to `EnrichedRoute` interface in `brouter.ts`
- [x] 1.3 Populate the `highways` array in `mergeGeoJsonSegments` using the extracted highway tags
- [x] 1.4 Store highway data in Yjs `routeData` in `use-routing.ts` (same pattern as surfaces)
## 2. Color Palette & Types
- [x] 2.1 Extend `ColorMode` type in `ColoredRoute.tsx` to include `"highway"`
- [x] 2.2 Define `HIGHWAY_COLORS` mapping and `DEFAULT_HIGHWAY_COLOR` in `ColoredRoute.tsx` (reds for major roads, grays/blues for urban, greens for paths/cycling)
- [x] 2.3 Export `HIGHWAY_COLORS` and `DEFAULT_HIGHWAY_COLOR` for use in the elevation chart
## 3. Map Polyline Coloring
- [x] 3.1 Add highway coloring branch in `ColoredRoute` component — color segments by highway type from a `highways` prop
- [x] 3.2 Pass `highways` data from `PlannerMap.tsx` to `ColoredRoute`
## 4. Elevation Chart
- [x] 4.1 Read `highways` from Yjs `routeData` in `ElevationChart.tsx` (same pattern as surfaces)
- [x] 4.2 Add highway-colored segment rendering in `drawChart` (fill + line, matching surface pattern)
- [x] 4.3 Add road type legend display when `colorMode === "highway"` (colored swatches, max 6 with overflow)
- [x] 4.4 Add highway type name to hover label when `colorMode === "highway"`
- [x] 4.5 Update chart title to show "Road Type Profile" when in highway mode
## 5. UI & Color Mode Toggle
- [x] 5.1 Add "Road Type" / "highway" option to the color mode `<select>` dropdown in `ElevationChart.tsx`
## 6. Internationalization
- [x] 6.1 Add English i18n keys: `colorMode.highway` ("Road Type"), `elevation.highway` ("Road Type Profile")
- [x] 6.2 Add German i18n keys: `colorMode.highway` ("Straßentyp"), `elevation.highway` ("Straßentypenprofil")
## 7. Testing
- [x] 7.1 Add `highways` data to BRouter mock fixtures in `e2e/fixtures/brouter-mock.ts`
- [x] 7.2 Add unit tests for highway tag extraction in `brouter.ts`
- [x] 7.3 Add E2E test: switch to road type color mode and verify chart renders
- [x] 7.4 Add E2E test: verify road type hover label shows highway type name