diff --git a/apps/planner/app/components/ColoredRoute.tsx b/apps/planner/app/components/ColoredRoute.tsx index 753429c..c8b8f36 100644 --- a/apps/planner/app/components/ColoredRoute.tsx +++ b/apps/planner/app/components/ColoredRoute.tsx @@ -2,7 +2,7 @@ import { useMemo } from "react"; import { Polyline } from "react-leaflet"; import type L from "leaflet"; -export type ColorMode = "plain" | "elevation" | "surface" | "grade" | "highway" | "maxspeed"; +export type ColorMode = "plain" | "elevation" | "surface" | "grade" | "highway" | "maxspeed" | "smoothness" | "tracktype" | "cycleway" | "bikeroute"; interface ColoredRouteProps { coordinates: [number, number, number][]; // [lon, lat, ele] @@ -10,6 +10,10 @@ interface ColoredRouteProps { surfaces?: string[]; highways?: string[]; maxspeeds?: string[]; + smoothnesses?: string[]; + tracktypes?: string[]; + cycleways?: string[]; + bikeroutes?: string[]; } const SURFACE_COLORS: Record = { @@ -64,6 +68,51 @@ const HIGHWAY_COLORS: Record = { const DEFAULT_HIGHWAY_COLOR = "#9ca3af"; +const SMOOTHNESS_COLORS: Record = { + excellent: "#22c55e", + good: "#16a34a", + intermediate: "#eab308", + bad: "#f97316", + very_bad: "#ef4444", + horrible: "#991b1b", + very_horrible: "#7f1d1d", + impassable: "#450a0a", +}; + +const DEFAULT_SMOOTHNESS_COLOR = "#9ca3af"; + +const TRACKTYPE_COLORS: Record = { + grade1: "#22c55e", + grade2: "#84cc16", + grade3: "#eab308", + grade4: "#f97316", + grade5: "#ef4444", +}; + +const DEFAULT_TRACKTYPE_COLOR = "#9ca3af"; + +const CYCLEWAY_COLORS: Record = { + track: "#22c55e", + lane: "#84cc16", + shared_lane: "#eab308", + share_busway: "#f97316", + opposite_lane: "#818cf8", + separate: "#16a34a", + no: "#ef4444", +}; + +const DEFAULT_CYCLEWAY_COLOR = "#9ca3af"; + +const BIKEROUTE_COLORS: Record = { + icn: "#dc2626", + ncn: "#f97316", + rcn: "#eab308", + lcn: "#22c55e", + none: "#d4d4d8", +}; + +const DEFAULT_BIKEROUTE_COLOR = "#d4d4d8"; + export function routeGradeColor(grade: number): string { const absGrade = Math.abs(grade); if (absGrade < 3) return "#22c55e"; @@ -96,7 +145,7 @@ export function maxspeedColor(speed: string): string { return "#991b1b"; // >100 dark red } -export function ColoredRoute({ coordinates, colorMode, surfaces, highways, maxspeeds }: ColoredRouteProps) { +export function ColoredRoute({ coordinates, colorMode, surfaces, highways, maxspeeds, smoothnesses, tracktypes, cycleways, bikeroutes }: ColoredRouteProps) { const segments = useMemo(() => { if (colorMode === "plain" || coordinates.length < 2) { return null; @@ -179,6 +228,78 @@ export function ColoredRoute({ coordinates, colorMode, surfaces, highways, maxsp return result; } + // smoothness mode + if (colorMode === "smoothness") { + if (!smoothnesses || smoothnesses.length < coordinates.length) return null; + + const result: { positions: L.LatLngExpression[]; color: string }[] = []; + for (let i = 0; i < coordinates.length - 1; i++) { + const smoothness = smoothnesses[i] ?? "unknown"; + result.push({ + positions: [ + [coordinates[i]![1], coordinates[i]![0]], + [coordinates[i + 1]![1], coordinates[i + 1]![0]], + ], + color: SMOOTHNESS_COLORS[smoothness] ?? DEFAULT_SMOOTHNESS_COLOR, + }); + } + return result; + } + + // tracktype mode + if (colorMode === "tracktype") { + if (!tracktypes || tracktypes.length < coordinates.length) return null; + + const result: { positions: L.LatLngExpression[]; color: string }[] = []; + for (let i = 0; i < coordinates.length - 1; i++) { + const tracktype = tracktypes[i] ?? "unknown"; + result.push({ + positions: [ + [coordinates[i]![1], coordinates[i]![0]], + [coordinates[i + 1]![1], coordinates[i + 1]![0]], + ], + color: TRACKTYPE_COLORS[tracktype] ?? DEFAULT_TRACKTYPE_COLOR, + }); + } + return result; + } + + // cycleway mode + if (colorMode === "cycleway") { + if (!cycleways || cycleways.length < coordinates.length) return null; + + const result: { positions: L.LatLngExpression[]; color: string }[] = []; + for (let i = 0; i < coordinates.length - 1; i++) { + const cycleway = cycleways[i] ?? "unknown"; + result.push({ + positions: [ + [coordinates[i]![1], coordinates[i]![0]], + [coordinates[i + 1]![1], coordinates[i + 1]![0]], + ], + color: CYCLEWAY_COLORS[cycleway] ?? DEFAULT_CYCLEWAY_COLOR, + }); + } + return result; + } + + // bikeroute mode + if (colorMode === "bikeroute") { + if (!bikeroutes || bikeroutes.length < coordinates.length) return null; + + const result: { positions: L.LatLngExpression[]; color: string }[] = []; + for (let i = 0; i < coordinates.length - 1; i++) { + const bikeroute = bikeroutes[i] ?? "none"; + result.push({ + positions: [ + [coordinates[i]![1], coordinates[i]![0]], + [coordinates[i + 1]![1], coordinates[i + 1]![0]], + ], + color: BIKEROUTE_COLORS[bikeroute] ?? DEFAULT_BIKEROUTE_COLOR, + }); + } + return result; + } + // surface mode if (!surfaces || surfaces.length < coordinates.length) return null; @@ -194,7 +315,7 @@ export function ColoredRoute({ coordinates, colorMode, surfaces, highways, maxsp }); } return result; - }, [coordinates, colorMode, surfaces, highways, maxspeeds]); + }, [coordinates, colorMode, surfaces, highways, maxspeeds, smoothnesses, tracktypes, cycleways, bikeroutes]); const plainPositions = useMemo( () => coordinates.map((c) => [c[1], c[0]] as L.LatLngExpression), @@ -235,4 +356,11 @@ export function findSegmentForPoint( return 0; } -export { SURFACE_COLORS, DEFAULT_SURFACE_COLOR, HIGHWAY_COLORS, DEFAULT_HIGHWAY_COLOR }; +export { + SURFACE_COLORS, DEFAULT_SURFACE_COLOR, + HIGHWAY_COLORS, DEFAULT_HIGHWAY_COLOR, + SMOOTHNESS_COLORS, DEFAULT_SMOOTHNESS_COLOR, + TRACKTYPE_COLORS, DEFAULT_TRACKTYPE_COLOR, + CYCLEWAY_COLORS, DEFAULT_CYCLEWAY_COLOR, + BIKEROUTE_COLORS, DEFAULT_BIKEROUTE_COLOR, +}; diff --git a/apps/planner/app/components/ElevationChart.tsx b/apps/planner/app/components/ElevationChart.tsx index 648135a..382d917 100644 --- a/apps/planner/app/components/ElevationChart.tsx +++ b/apps/planner/app/components/ElevationChart.tsx @@ -2,7 +2,16 @@ import { useEffect, useState, useRef, useCallback } from "react"; import { useTranslation } from "react-i18next"; import type { DayStage } from "@trails-cool/gpx"; import type { YjsState } from "~/lib/use-yjs"; -import { elevationColor, maxspeedColor, SURFACE_COLORS, DEFAULT_SURFACE_COLOR, HIGHWAY_COLORS, DEFAULT_HIGHWAY_COLOR, type ColorMode } from "~/components/ColoredRoute"; +import { + elevationColor, maxspeedColor, + SURFACE_COLORS, DEFAULT_SURFACE_COLOR, + HIGHWAY_COLORS, DEFAULT_HIGHWAY_COLOR, + SMOOTHNESS_COLORS, DEFAULT_SMOOTHNESS_COLOR, + TRACKTYPE_COLORS, DEFAULT_TRACKTYPE_COLOR, + CYCLEWAY_COLORS, DEFAULT_CYCLEWAY_COLOR, + BIKEROUTE_COLORS, DEFAULT_BIKEROUTE_COLOR, + type ColorMode, +} from "~/components/ColoredRoute"; function gradeColor(grade: number): string { const absGrade = Math.abs(grade); @@ -77,6 +86,10 @@ export function ElevationChart({ yjs, onHover, days }: ElevationChartProps) { const [surfaces, setSurfaces] = useState([]); const [highways, setHighways] = useState([]); const [maxspeeds, setMaxspeeds] = useState([]); + const [smoothnesses, setSmoothnesses] = useState([]); + const [tracktypes, setTracktypes] = useState([]); + const [cycleways, setCycleways] = useState([]); + const [bikeroutes, setBikeroutes] = useState([]); const canvasRef = useRef(null); const pointsRef = useRef([]); pointsRef.current = points; @@ -109,6 +122,30 @@ export function ElevationChart({ yjs, onHover, days }: ElevationChartProps) { } else { setMaxspeeds([]); } + const smoothnessesJson = yjs.routeData.get("smoothnesses") as string | undefined; + if (smoothnessesJson) { + try { setSmoothnesses(JSON.parse(smoothnessesJson)); } catch { setSmoothnesses([]); } + } else { + setSmoothnesses([]); + } + const tracktypesJson = yjs.routeData.get("tracktypes") as string | undefined; + if (tracktypesJson) { + try { setTracktypes(JSON.parse(tracktypesJson)); } catch { setTracktypes([]); } + } else { + setTracktypes([]); + } + const cyclewaysJson = yjs.routeData.get("cycleways") as string | undefined; + if (cyclewaysJson) { + try { setCycleways(JSON.parse(cyclewaysJson)); } catch { setCycleways([]); } + } else { + setCycleways([]); + } + const bikeroutesJson = yjs.routeData.get("bikeroutes") as string | undefined; + if (bikeroutesJson) { + try { setBikeroutes(JSON.parse(bikeroutesJson)); } catch { setBikeroutes([]); } + } else { + setBikeroutes([]); + } }; yjs.routeData.observe(update); update(); @@ -265,6 +302,102 @@ export function ElevationChart({ yjs, onHover, days }: ElevationChartProps) { ctx.fillStyle = color + "40"; ctx.fill(); + ctx.beginPath(); + ctx.moveTo(toX(p0.distance), toY(p0.elevation)); + ctx.lineTo(toX(p1.distance), toY(p1.elevation)); + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.stroke(); + } + } else if (colorMode === "smoothness" && smoothnesses.length >= points.length) { + // Smoothness-colored segments + for (let i = 0; i < points.length - 1; i++) { + const p0 = points[i]!; + const p1 = points[i + 1]!; + const smoothness = smoothnesses[i] ?? "unknown"; + const color = SMOOTHNESS_COLORS[smoothness] ?? DEFAULT_SMOOTHNESS_COLOR; + + ctx.beginPath(); + ctx.moveTo(toX(p0.distance), PADDING.top + chartH); + ctx.lineTo(toX(p0.distance), toY(p0.elevation)); + ctx.lineTo(toX(p1.distance), toY(p1.elevation)); + ctx.lineTo(toX(p1.distance), PADDING.top + chartH); + ctx.closePath(); + ctx.fillStyle = color + "40"; + ctx.fill(); + + ctx.beginPath(); + ctx.moveTo(toX(p0.distance), toY(p0.elevation)); + ctx.lineTo(toX(p1.distance), toY(p1.elevation)); + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.stroke(); + } + } else if (colorMode === "tracktype" && tracktypes.length >= points.length) { + // Track type-colored segments + for (let i = 0; i < points.length - 1; i++) { + const p0 = points[i]!; + const p1 = points[i + 1]!; + const tracktype = tracktypes[i] ?? "unknown"; + const color = TRACKTYPE_COLORS[tracktype] ?? DEFAULT_TRACKTYPE_COLOR; + + ctx.beginPath(); + ctx.moveTo(toX(p0.distance), PADDING.top + chartH); + ctx.lineTo(toX(p0.distance), toY(p0.elevation)); + ctx.lineTo(toX(p1.distance), toY(p1.elevation)); + ctx.lineTo(toX(p1.distance), PADDING.top + chartH); + ctx.closePath(); + ctx.fillStyle = color + "40"; + ctx.fill(); + + ctx.beginPath(); + ctx.moveTo(toX(p0.distance), toY(p0.elevation)); + ctx.lineTo(toX(p1.distance), toY(p1.elevation)); + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.stroke(); + } + } else if (colorMode === "cycleway" && cycleways.length >= points.length) { + // Cycleway-colored segments + for (let i = 0; i < points.length - 1; i++) { + const p0 = points[i]!; + const p1 = points[i + 1]!; + const cycleway = cycleways[i] ?? "unknown"; + const color = CYCLEWAY_COLORS[cycleway] ?? DEFAULT_CYCLEWAY_COLOR; + + ctx.beginPath(); + ctx.moveTo(toX(p0.distance), PADDING.top + chartH); + ctx.lineTo(toX(p0.distance), toY(p0.elevation)); + ctx.lineTo(toX(p1.distance), toY(p1.elevation)); + ctx.lineTo(toX(p1.distance), PADDING.top + chartH); + ctx.closePath(); + ctx.fillStyle = color + "40"; + ctx.fill(); + + ctx.beginPath(); + ctx.moveTo(toX(p0.distance), toY(p0.elevation)); + ctx.lineTo(toX(p1.distance), toY(p1.elevation)); + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.stroke(); + } + } else if (colorMode === "bikeroute" && bikeroutes.length >= points.length) { + // Bike route-colored segments + for (let i = 0; i < points.length - 1; i++) { + const p0 = points[i]!; + const p1 = points[i + 1]!; + const bikeroute = bikeroutes[i] ?? "none"; + const color = BIKEROUTE_COLORS[bikeroute] ?? DEFAULT_BIKEROUTE_COLOR; + + ctx.beginPath(); + ctx.moveTo(toX(p0.distance), PADDING.top + chartH); + ctx.lineTo(toX(p0.distance), toY(p0.elevation)); + ctx.lineTo(toX(p1.distance), toY(p1.elevation)); + ctx.lineTo(toX(p1.distance), PADDING.top + chartH); + ctx.closePath(); + ctx.fillStyle = color + "40"; + ctx.fill(); + ctx.beginPath(); ctx.moveTo(toX(p0.distance), toY(p0.elevation)); ctx.lineTo(toX(p1.distance), toY(p1.elevation)); @@ -374,12 +507,24 @@ export function ElevationChart({ yjs, onHover, days }: ElevationChartProps) { const s = maxspeeds[highlightIdx]!; label += ` · ${s === "unknown" ? s : `${s} km/h`}`; } + if (colorMode === "smoothness" && smoothnesses[highlightIdx]) { + label += ` · ${smoothnesses[highlightIdx]}`; + } + if (colorMode === "tracktype" && tracktypes[highlightIdx]) { + label += ` · ${tracktypes[highlightIdx]}`; + } + if (colorMode === "cycleway" && cycleways[highlightIdx]) { + label += ` · ${cycleways[highlightIdx]}`; + } + if (colorMode === "bikeroute" && bikeroutes[highlightIdx]) { + label += ` · ${bikeroutes[highlightIdx]}`; + } const labelX = hx + 8 > w - 80 ? hx - 8 : hx + 8; ctx.textAlign = hx + 8 > w - 80 ? "right" : "left"; ctx.fillText(label, labelX, PADDING.top + 10); } }, - [points, colorMode, surfaces, highways, maxspeeds, days, t], + [points, colorMode, surfaces, highways, maxspeeds, smoothnesses, tracktypes, cycleways, bikeroutes, days, t], ); useEffect(() => { @@ -438,7 +583,7 @@ export function ElevationChart({ yjs, onHover, days }: ElevationChartProps) {

- {colorMode === "grade" ? t("elevation.grade") : colorMode === "highway" ? t("elevation.highway") : colorMode === "maxspeed" ? t("elevation.maxspeed") : t("elevation.profile")} + {colorMode === "grade" ? t("elevation.grade") : colorMode === "highway" ? t("elevation.highway") : colorMode === "maxspeed" ? t("elevation.maxspeed") : colorMode === "smoothness" ? t("elevation.smoothness") : colorMode === "tracktype" ? t("elevation.tracktype") : colorMode === "cycleway" ? t("elevation.cycleway") : colorMode === "bikeroute" ? t("elevation.bikeroute") : t("elevation.profile")}

{colorMode === "grade" && (<> @@ -478,6 +623,38 @@ export function ElevationChart({ yjs, onHover, days }: ElevationChartProps) { {"≤100"} {"100+"} )} + {colorMode === "smoothness" && smoothnesses.length > 0 && (<> + {[...new Set(smoothnesses)].slice(0, 6).map((s) => ( + + + {s} + + ))} + {[...new Set(smoothnesses)].length > 6 && +{[...new Set(smoothnesses)].length - 6}} + )} + {colorMode === "tracktype" && (<> + grade1 + grade2 + grade3 + grade4 + grade5 + )} + {colorMode === "cycleway" && cycleways.length > 0 && (<> + {[...new Set(cycleways)].slice(0, 6).map((c) => ( + + + {c} + + ))} + {[...new Set(cycleways)].length > 6 && +{[...new Set(cycleways)].length - 6}} + )} + {colorMode === "bikeroute" && (<> + icn + ncn + rcn + lcn + none + )}
([]); const [highways, setHighways] = useState([]); const [maxspeeds, setMaxspeeds] = useState([]); + const [smoothnesses, setSmoothnesses] = useState([]); + const [tracktypes, setTracktypes] = useState([]); + const [cycleways, setCycleways] = useState([]); + const [bikeroutes, setBikeroutes] = useState([]); const [colorMode, setColorMode] = useState("plain"); const [noGoDrawing, setNoGoDrawing] = useState(false); const toggleNoGoDraw = useCallback(() => setNoGoDrawing((v) => !v), []); @@ -458,6 +462,34 @@ export function PlannerMap({ yjs, onRouteRequest, highlightPosition, highlighted setMaxspeeds([]); } + const smoothnessesJson = yjs.routeData.get("smoothnesses") as string | undefined; + if (smoothnessesJson) { + try { setSmoothnesses(JSON.parse(smoothnessesJson)); } catch { setSmoothnesses([]); } + } else { + setSmoothnesses([]); + } + + const tracktypesJson = yjs.routeData.get("tracktypes") as string | undefined; + if (tracktypesJson) { + try { setTracktypes(JSON.parse(tracktypesJson)); } catch { setTracktypes([]); } + } else { + setTracktypes([]); + } + + const cyclewaysJson = yjs.routeData.get("cycleways") as string | undefined; + if (cyclewaysJson) { + try { setCycleways(JSON.parse(cyclewaysJson)); } catch { setCycleways([]); } + } else { + setCycleways([]); + } + + const bikeroutesJson = yjs.routeData.get("bikeroutes") as string | undefined; + if (bikeroutesJson) { + try { setBikeroutes(JSON.parse(bikeroutesJson)); } catch { setBikeroutes([]); } + } else { + setBikeroutes([]); + } + if (modeVal) setColorMode(modeVal); }; @@ -740,6 +772,10 @@ export function PlannerMap({ yjs, onRouteRequest, highlightPosition, highlighted surfaces={surfaces} highways={highways} maxspeeds={maxspeeds} + smoothnesses={smoothnesses} + tracktypes={tracktypes} + cycleways={cycleways} + bikeroutes={bikeroutes} /> []): Enric const allSurfaces: string[] = []; const allHighways: string[] = []; const allMaxspeeds: string[] = []; + const allSmoothnesses: string[] = []; + const allTracktypes: string[] = []; + const allCycleways: string[] = []; + const allBikeroutes: string[] = []; const segmentBoundaries: number[] = []; let totalLength = 0; let totalAscend = 0; @@ -123,6 +131,10 @@ export function mergeGeoJsonSegments(segments: Record[]): Enric allSurfaces.push(wayTagData.surfaces.get(j) ?? wayTagData.surfaces.get(j - 1) ?? "unknown"); allHighways.push(wayTagData.highways.get(j) ?? wayTagData.highways.get(j - 1) ?? "unknown"); allMaxspeeds.push(wayTagData.maxspeeds.get(j) ?? wayTagData.maxspeeds.get(j - 1) ?? "unknown"); + allSmoothnesses.push(wayTagData.smoothnesses.get(j) ?? wayTagData.smoothnesses.get(j - 1) ?? "unknown"); + allTracktypes.push(wayTagData.tracktypes.get(j) ?? wayTagData.tracktypes.get(j - 1) ?? "unknown"); + allCycleways.push(wayTagData.cycleways.get(j) ?? wayTagData.cycleways.get(j - 1) ?? "unknown"); + allBikeroutes.push(wayTagData.bikeroutes.get(j) ?? wayTagData.bikeroutes.get(j - 1) ?? "none"); } // Accumulate stats @@ -157,6 +169,10 @@ export function mergeGeoJsonSegments(segments: Record[]): Enric surfaces: allSurfaces, highways: allHighways, maxspeeds: allMaxspeeds, + smoothnesses: allSmoothnesses, + tracktypes: allTracktypes, + cycleways: allCycleways, + bikeroutes: allBikeroutes, totalLength, totalAscend, totalTime, @@ -168,6 +184,10 @@ interface WayTagData { surfaces: Map; highways: Map; maxspeeds: Map; + smoothnesses: Map; + tracktypes: Map; + cycleways: Map; + bikeroutes: Map; } /** @@ -179,12 +199,16 @@ function extractWayTagData(properties: Record): WayTagData { const surfaces = new Map(); const highways = new Map(); const maxspeeds = new Map(); + const smoothnesses = new Map(); + const tracktypes = new Map(); + const cycleways = new Map(); + const bikeroutes = new Map(); const messages = properties.messages as string[][] | undefined; - if (!messages || messages.length < 2) return { surfaces, highways, maxspeeds }; + if (!messages || messages.length < 2) return { surfaces, highways, maxspeeds, smoothnesses, tracktypes, cycleways, bikeroutes }; const headers = messages[0]!; const wayTagsIdx = headers.indexOf("WayTags"); - if (wayTagsIdx === -1) return { surfaces, highways, maxspeeds }; + if (wayTagsIdx === -1) return { surfaces, highways, maxspeeds, smoothnesses, tracktypes, cycleways, bikeroutes }; for (let i = 1; i < messages.length; i++) { const row = messages[i]!; @@ -209,8 +233,39 @@ function extractWayTagData(properties: Record): WayTagData { speed = plain ? plain[1]! : "unknown"; } maxspeeds.set(i - 1, speed); + + // Extract smoothness + const smoothnessMatch = tags.match(/smoothness=(\S+)/); + smoothnesses.set(i - 1, smoothnessMatch ? smoothnessMatch[1]! : "unknown"); + + // Extract tracktype + const tracktypeMatch = tags.match(/tracktype=(\S+)/); + tracktypes.set(i - 1, tracktypeMatch ? tracktypeMatch[1]! : "unknown"); + + // Extract cycleway with direction handling + let cycleway: string | null = null; + if (!reversedirection) { + const right = tags.match(/cycleway:right=(\S+)/); + if (right) cycleway = right[1]!; + } else { + const left = tags.match(/cycleway:left=(\S+)/); + if (left) cycleway = left[1]!; + } + if (!cycleway) { + const bare = tags.match(/cycleway=(\S+)/); + cycleway = bare ? bare[1]! : "unknown"; + } + cycleways.set(i - 1, cycleway); + + // Extract bicycle route network (priority: icn > ncn > rcn > lcn) + let bikeroute = "none"; + if (/route_bicycle_icn=yes/.test(tags)) bikeroute = "icn"; + else if (/route_bicycle_ncn=yes/.test(tags)) bikeroute = "ncn"; + else if (/route_bicycle_rcn=yes/.test(tags)) bikeroute = "rcn"; + else if (/route_bicycle_lcn=yes/.test(tags)) bikeroute = "lcn"; + bikeroutes.set(i - 1, bikeroute); } - return { surfaces, highways, maxspeeds }; + return { surfaces, highways, maxspeeds, smoothnesses, tracktypes, cycleways, bikeroutes }; } /** diff --git a/apps/planner/app/lib/use-routing.ts b/apps/planner/app/lib/use-routing.ts index 9ef6108..61ad504 100644 --- a/apps/planner/app/lib/use-routing.ts +++ b/apps/planner/app/lib/use-routing.ts @@ -132,6 +132,18 @@ export function useRouting(yjs: YjsState | null) { if (enriched.maxspeeds?.length) { yjs.routeData.set("maxspeeds", JSON.stringify(enriched.maxspeeds)); } + if (enriched.smoothnesses?.length) { + yjs.routeData.set("smoothnesses", JSON.stringify(enriched.smoothnesses)); + } + if (enriched.tracktypes?.length) { + yjs.routeData.set("tracktypes", JSON.stringify(enriched.tracktypes)); + } + if (enriched.cycleways?.length) { + yjs.routeData.set("cycleways", JSON.stringify(enriched.cycleways)); + } + if (enriched.bikeroutes?.length) { + yjs.routeData.set("bikeroutes", JSON.stringify(enriched.bikeroutes)); + } }); } catch { setRouteError("failed"); diff --git a/docker/brouter/Dockerfile b/docker/brouter/Dockerfile index 24d1164..13a2cc1 100644 --- a/docker/brouter/Dockerfile +++ b/docker/brouter/Dockerfile @@ -20,13 +20,14 @@ RUN addgroup --system app && adduser --system --ingroup app app \ RUN cp -r profiles2/* /data/profiles/ 2>/dev/null || true \ && mv brouter-*-all.jar brouter.jar -# Patch profiles to include maxspeed in WayTags output (for speed limit visualization) +# Patch profiles to include extra tags in WayTags output for visualization # Each tag needs its own assign statement to appear in tiledesc WayTags RUN for f in /data/profiles/*.brf; do \ - if grep -q "dummyUsage" "$f" && ! grep -q "maxspeed" "$f"; then \ - sed -i '/assign dummyUsage/a assign dummyUsage2 = maxspeed=' "$f"; \ - elif ! grep -q "maxspeed" "$f" && grep -q "context:node" "$f"; then \ - sed -i '/---context:node/i assign dummyUsage2 = maxspeed=' "$f"; \ + if grep -q "dummyUsage" "$f"; then \ + grep -q "maxspeed" "$f" || sed -i '/assign dummyUsage/a assign dummyUsage2 = maxspeed=' "$f"; \ + grep -q "tracktype" "$f" || sed -i '/assign dummyUsage/a assign dummyUsage3 = tracktype=' "$f"; \ + elif grep -q "context:node" "$f"; then \ + sed -i '/---context:node/i assign dummyUsage2 = maxspeed=\nassign dummyUsage3 = tracktype=' "$f"; \ fi; \ done diff --git a/e2e/fixtures/brouter-mock.ts b/e2e/fixtures/brouter-mock.ts index 8849ab1..864712e 100644 --- a/e2e/fixtures/brouter-mock.ts +++ b/e2e/fixtures/brouter-mock.ts @@ -44,6 +44,22 @@ const MOCK_ROUTE_3WP = { "30", "30", "unknown", "unknown", "unknown", "unknown", "50", "50", "30", "30", ], + smoothnesses: [ + "good", "good", "intermediate", "intermediate", "bad", + "bad", "good", "good", "excellent", "excellent", + ], + tracktypes: [ + "unknown", "unknown", "unknown", "unknown", "grade3", + "grade3", "unknown", "unknown", "unknown", "unknown", + ], + cycleways: [ + "lane", "lane", "track", "track", "unknown", + "unknown", "shared_lane", "shared_lane", "lane", "lane", + ], + bikeroutes: [ + "rcn", "rcn", "lcn", "lcn", "none", + "none", "ncn", "ncn", "rcn", "rcn", + ], }; /** @@ -83,6 +99,18 @@ const MOCK_ROUTE_2WP = { maxspeeds: [ "50", "50", "unknown", "unknown", "30", "30", ], + smoothnesses: [ + "good", "good", "intermediate", "bad", "good", "good", + ], + tracktypes: [ + "unknown", "unknown", "grade2", "grade4", "unknown", "unknown", + ], + cycleways: [ + "lane", "lane", "track", "no", "shared_lane", "shared_lane", + ], + bikeroutes: [ + "ncn", "ncn", "rcn", "none", "lcn", "lcn", + ], }; /** diff --git a/packages/i18n/src/locales/de.ts b/packages/i18n/src/locales/de.ts index d823b69..c7110c0 100644 --- a/packages/i18n/src/locales/de.ts +++ b/packages/i18n/src/locales/de.ts @@ -104,6 +104,10 @@ export default { grade: "Steigung", highway: "Straßentyp", maxspeed: "Tempolimit", + smoothness: "Fahrbahnqualität", + tracktype: "Wegtyp", + cycleway: "Radweg", + bikeroute: "Radroute", surfaceLegend: "Farbe nach Straßenbelag", surfaceUnavailable: "Untergrunddaten für dieses Profil nicht verfügbar", }, @@ -115,6 +119,10 @@ export default { grade: "Steigungsprofil", highway: "Straßentypenprofil", maxspeed: "Tempolimitprofil", + smoothness: "Fahrbahnqualitätsprofil", + tracktype: "Wegtypenprofil", + cycleway: "Radwegprofil", + bikeroute: "Radroutenprofil", low: "Tief", high: "Hoch", }, diff --git a/packages/i18n/src/locales/en.ts b/packages/i18n/src/locales/en.ts index dc3546d..a97bbd1 100644 --- a/packages/i18n/src/locales/en.ts +++ b/packages/i18n/src/locales/en.ts @@ -104,6 +104,10 @@ export default { grade: "Grade", highway: "Road Type", maxspeed: "Speed Limit", + smoothness: "Smoothness", + tracktype: "Track Type", + cycleway: "Cycleway", + bikeroute: "Bike Route", surfaceLegend: "Color by road surface type", surfaceUnavailable: "Surface data not available for this profile", }, @@ -115,6 +119,10 @@ export default { grade: "Grade Profile", highway: "Road Type Profile", maxspeed: "Speed Limit Profile", + smoothness: "Smoothness Profile", + tracktype: "Track Type Profile", + cycleway: "Cycleway Profile", + bikeroute: "Bike Route Profile", low: "Low", high: "High", },