feat: add experimental landmark-based voice guidance
Some checks failed
TrafficCue CI / check (push) Failing after 1m24s
TrafficCue CI / build (push) Successful in 48s

This commit is contained in:
Cfp
2025-06-25 14:02:00 +02:00
parent 029c2dc410
commit dea6fb741b
2 changed files with 88 additions and 2 deletions

View File

@ -0,0 +1,83 @@
import { OVERPASS_SERVER } from "../hosts";
import type { OverpassResult } from "../Overpass";
export async function generateVoiceGuidance(maneuver: Maneuver, shape: WorldLocation[]): Promise<string> {
const landmarks = await findNearbyLandmarks(shape[maneuver.begin_shape_index]);
if(landmarks.length == 0) {
return maneuver.verbal_pre_transition_instruction;
}
console.log("Original instruction:", maneuver.verbal_pre_transition_instruction);
return `Hinter ${landmarks[0].tags.name}, ${typeToName(maneuver.type)} auf ${(maneuver.street_names || []).join(", ")}.`;
}
function typeToName(type: number) {
switch (type) {
case 4:
return "Ziel erreicht";
case 5:
return "Ziel auf der rechten Seite erreicht";
case 6:
return "Ziel auf der linken Seite erreicht";
case 8:
return "Weiterfahren";
case 9:
return "Leicht rechts abbiegen";
case 10:
return "Rechts abbiegen";
case 11:
return "Scharf rechts abbiegen";
case 12:
case 13:
return "Umdrehen";
case 14:
return "Scharf links abbiegen";
case 15:
return "Links abbiegen";
case 16:
return "Leicht links abbiegen";
case 20:
return "Rechts abfahren";
case 21:
return "Links abfahren";
case 22:
return "Geradeaus weiterfahren";
case 23:
return "Rechts halten";
case 24:
return "Links halten";
case 25:
return "Einordnen";
case 26:
case 27:
return "Kreisverkehr";
case 37:
return "Rechts einordnen";
case 38:
return "Links einordnen";
default:
return "Unbekannt";
}
}
export async function findNearbyLandmarks(location: WorldLocation) {
const radius = 100;
const lat = location.lat;
const lon = location.lon;
const res = await fetch(OVERPASS_SERVER, {
method: "POST",
body: `[out:json];
(
node(around:${radius}, ${lat}, ${lon})["tourism"="artwork"]["name"];
way(around:${radius}, ${lat}, ${lon})["tourism"="artwork"]["name"];
node(around:${radius}, ${lat}, ${lon})["shop"]["name"];
way(around:${radius}, ${lat}, ${lon})["shop"]["name"];
);
out center tags;`,
}).then((res) => res.json() as Promise<OverpassResult>);
// Sort by distance to the location
return res.elements.sort((a, b) => {
const distA = Math.sqrt(Math.pow(a.lat! - lat, 2) + Math.pow(a.lon! - lon, 2));
const distB = Math.sqrt(Math.pow(b.lat! - lat, 2) + Math.pow(b.lon! - lon, 2));
return distA - distB;
});
}

View File

@ -3,6 +3,7 @@ import { map } from "$lib/components/lnv/map.svelte";
import say from "./TTS"; import say from "./TTS";
import type { ValhallaRequest } from "./ValhallaRequest"; import type { ValhallaRequest } from "./ValhallaRequest";
import type { LngLatBoundsLike } from "maplibre-gl"; import type { LngLatBoundsLike } from "maplibre-gl";
import { generateVoiceGuidance } from "./VoiceGuidance";
export const routing = $state({ export const routing = $state({
geojson: { geojson: {
@ -192,11 +193,13 @@ async function tickRoute() {
); );
if (distanceToEnd <= verbalDistance) { if (distanceToEnd <= verbalDistance) {
hasAnnouncedPreInstruction = true; hasAnnouncedPreInstruction = true;
const instruction = await generateVoiceGuidance(currentManeuver, polyline);
console.log( console.log(
"[Verbal instruction] ", "[Verbal instruction] ",
currentManeuver.verbal_pre_transition_instruction, // currentManeuver.verbal_pre_transition_instruction,
instruction
); );
say(currentManeuver.verbal_pre_transition_instruction); say(instruction);
} }
} }