style: run prettier
All checks were successful
TrafficCue CI / check (push) Successful in 53s
TrafficCue CI / build (push) Successful in 46s

This commit is contained in:
Cfp
2025-06-27 14:34:45 +02:00
parent c861981ec4
commit c16e2e98ec
9 changed files with 114 additions and 71 deletions

View File

@ -9,7 +9,10 @@
} from "svelte-maplibre-gl"; } from "svelte-maplibre-gl";
import { view } from "./sidebar.svelte"; import { view } from "./sidebar.svelte";
import { map, pin } from "./map.svelte"; import { map, pin } from "./map.svelte";
import { decodePolyline, routing } from "$lib/services/navigation/routing.svelte"; import {
decodePolyline,
routing,
} from "$lib/services/navigation/routing.svelte";
import { location } from "./location.svelte"; import { location } from "./location.svelte";
onMount(() => { onMount(() => {
@ -84,18 +87,26 @@
{/each} --> {/each} -->
<Marker <Marker
lnglat={{ lnglat={{
lat: decodePolyline(routing.currentTrip!.legs[0].shape)[routing.currentTripInfo.currentManeuver!.begin_shape_index].lat, lat: decodePolyline(routing.currentTrip!.legs[0].shape)[
lng: decodePolyline(routing.currentTrip!.legs[0].shape)[routing.currentTripInfo.currentManeuver!.begin_shape_index].lon routing.currentTripInfo.currentManeuver!.begin_shape_index
].lat,
lng: decodePolyline(routing.currentTrip!.legs[0].shape)[
routing.currentTripInfo.currentManeuver!.begin_shape_index
].lon,
}} }}
color="lime" color="lime"
/> />
<Marker <Marker
lnglat={{ lnglat={{
lat: decodePolyline(routing.currentTrip!.legs[0].shape)[routing.currentTripInfo.currentManeuver!.end_shape_index].lat, lat: decodePolyline(routing.currentTrip!.legs[0].shape)[
lng: decodePolyline(routing.currentTrip!.legs[0].shape)[routing.currentTripInfo.currentManeuver!.end_shape_index].lon routing.currentTripInfo.currentManeuver!.end_shape_index
].lat,
lng: decodePolyline(routing.currentTrip!.legs[0].shape)[
routing.currentTripInfo.currentManeuver!.end_shape_index
].lon,
}} }}
color="red" color="red"
/> />
{/if} {/if}
<!-- <Hash /> --> <!-- <Hash /> -->
<!-- <GeolocateControl <!-- <GeolocateControl

View File

@ -108,10 +108,15 @@
> >
<div class="p-2 flex gap-2"> <div class="p-2 flex gap-2">
{#if routing.currentTripInfo.currentManeuver?.type === 26 || routing.currentTripInfo.currentManeuver?.type === 27} {#if routing.currentTripInfo.currentManeuver?.type === 26 || routing.currentTripInfo.currentManeuver?.type === 27}
{@const exit = routing.currentTripInfo.currentManeuver?.type === 26 {@const exit =
? routing.currentTripInfo.currentManeuver?.roundabout_exit_count routing.currentTripInfo.currentManeuver?.type === 26
: routing.currentTrip?.legs[0].maneuvers[routing.currentTripInfo.maneuverIdx - 1].roundabout_exit_count} ? routing.currentTripInfo.currentManeuver?.roundabout_exit_count
<span class="border-white border-4 rounded-full text-3xl flex items-center justify-center w-12 h-12"> : routing.currentTrip?.legs[0].maneuvers[
routing.currentTripInfo.maneuverIdx - 1
].roundabout_exit_count}
<span
class="border-white border-4 rounded-full text-3xl flex items-center justify-center w-12 h-12"
>
{exit} {exit}
</span> </span>
{:else} {:else}

View File

@ -65,7 +65,9 @@
<Input bind:value={toLocation} /> <Input bind:value={toLocation} />
</div> </div>
<span> <span>
You can use <strong>current</strong> for your current location, <strong>home</strong> or <strong>work</strong> for saved locations. You can use <strong>current</strong> for your current location,
<strong>home</strong>
or <strong>work</strong> for saved locations.
</span> </span>
</div> </div>
<Button <Button
@ -98,7 +100,7 @@
]); ]);
const res = await fetchRoute(ROUTING_SERVER, req); const res = await fetchRoute(ROUTING_SERVER, req);
routes = [res.trip]; routes = [res.trip];
if(res.alternates) { if (res.alternates) {
for (const alternate of res.alternates) { for (const alternate of res.alternates) {
if (alternate.trip) { if (alternate.trip) {
routes.push(alternate.trip); routes.push(alternate.trip);
@ -122,7 +124,9 @@
{#if i == 0} {#if i == 0}
<StarIcon /> <StarIcon />
{/if} {/if}
{Math.round(route.summary.length)}km - {formatTime(Math.round(route.summary.time))} {Math.round(route.summary.length)}km - {formatTime(
Math.round(route.summary.time),
)}
</Button> </Button>
{/each} {/each}
</div> </div>

View File

@ -13,7 +13,7 @@ class DuckWeb extends WebPlugin implements DuckPlugin {
} }
const Duck = registerPlugin<DuckPlugin>("Duck", { const Duck = registerPlugin<DuckPlugin>("Duck", {
web: new DuckWeb() web: new DuckWeb(),
}); });
export default Duck; export default Duck;

View File

@ -1,12 +1,14 @@
import * as tts from '@diffusionstudio/vits-web'; import * as tts from "@diffusionstudio/vits-web";
import TTSWorker from './TTSWorker.ts?worker'; import TTSWorker from "./TTSWorker.ts?worker";
// const VOICE = "en_US-hfc_female-medium"; // const VOICE = "en_US-hfc_female-medium";
const VOICE = "de_DE-thorsten-medium"; const VOICE = "de_DE-thorsten-medium";
export async function downloadVoice(): Promise<void> { export async function downloadVoice(): Promise<void> {
await tts.download(VOICE, (progress) => { await tts.download(VOICE, (progress) => {
console.log(`Downloading ${progress.url} - ${Math.round(progress.loaded * 100 / progress.total)}%`); console.log(
`Downloading ${progress.url} - ${Math.round((progress.loaded * 100) / progress.total)}%`,
);
}); });
} }
@ -21,22 +23,25 @@ let playing = false;
let generating = 0; let generating = 0;
const worker = new TTSWorker(); const worker = new TTSWorker();
worker.addEventListener('message', (event: MessageEvent<{ type: 'result', audio: Blob, text: string }>) => { worker.addEventListener(
if (event.data.type != 'result') return; "message",
(event: MessageEvent<{ type: "result"; audio: Blob; text: string }>) => {
if (event.data.type != "result") return;
// const audio = new Audio(); // const audio = new Audio();
// audio.src = URL.createObjectURL(event.data.audio); // audio.src = URL.createObjectURL(event.data.audio);
// audio.play(); // audio.play();
// console.log("Audio playing"); // console.log("Audio playing");
// audio.onended = () => { // audio.onended = () => {
// playing = false; // playing = false;
// }; // };
const item = queue.find(item => item.text === event.data.text); const item = queue.find((item) => item.text === event.data.text);
if (item) { if (item) {
item.audio = event.data.audio; // Set the audio blob for the item item.audio = event.data.audio; // Set the audio blob for the item
generating--; generating--;
} }
}); },
);
setInterval(() => { setInterval(() => {
// if(playing) return; // if(playing) return;
@ -73,9 +78,9 @@ setInterval(() => {
generating++; generating++;
console.log("Generating audio for:", item.text); console.log("Generating audio for:", item.text);
worker.postMessage({ worker.postMessage({
type: 'init', type: "init",
text: item.text, text: item.text,
voiceId: VOICE voiceId: VOICE,
}); });
item.audio = undefined; // Reset audio to undefined until generated item.audio = undefined; // Reset audio to undefined until generated
} }
@ -110,26 +115,28 @@ export function queueSpeech(text: string) {
// const audio = new Audio(); // const audio = new Audio();
// audio.src = URL.createObjectURL(wav); // audio.src = URL.createObjectURL(wav);
// audio.play(); // audio.play();
if (queue.some(item => item.text === text)) { if (queue.some((item) => item.text === text)) {
// console.warn("Text already in queue, not adding again:", text); // console.warn("Text already in queue, not adding again:", text);
return; return;
} }
console.log("Queuing text for speech:", text); console.log("Queuing text for speech:", text);
queue.push({ queue.push({
text, text,
shouldPlay: false shouldPlay: false,
}); });
} }
export function speak(text: string) { export function speak(text: string) {
const existingItem = queue.find(item => item.text === text); const existingItem = queue.find((item) => item.text === text);
if (existingItem) { if (existingItem) {
existingItem.shouldPlay = true; existingItem.shouldPlay = true;
} else { } else {
console.warn("Adding new item to play immediately. Consider queuing instead!"); console.warn(
"Adding new item to play immediately. Consider queuing instead!",
);
queue.push({ queue.push({
text, text,
shouldPlay: true shouldPlay: true,
}); });
} }
} }

View File

@ -5,7 +5,7 @@ import Duck from "../DuckPlugin";
export let tts: TextToSpeechPlugin | "web" | null = null; export let tts: TextToSpeechPlugin | "web" | null = null;
export async function initTTS() { export async function initTTS() {
if(Capacitor.isNativePlatform()) { if (Capacitor.isNativePlatform()) {
console.log("Using Capacitor TTS"); console.log("Using Capacitor TTS");
tts = (await import("@capacitor-community/text-to-speech")).TextToSpeech; tts = (await import("@capacitor-community/text-to-speech")).TextToSpeech;
} else { } else {
@ -15,14 +15,14 @@ export async function initTTS() {
} }
export default async function say(text: string) { export default async function say(text: string) {
if(!tts) { if (!tts) {
// alert("TTS not initialized"); // alert("TTS not initialized");
// console.error("TTS not initialized"); // console.error("TTS not initialized");
await initTTS(); await initTTS();
// return; // return;
} }
Duck.duck(); Duck.duck();
if(tts !== "web") { if (tts !== "web") {
try { try {
await tts?.speak({ await tts?.speak({
text: text, text: text,

View File

@ -1,16 +1,18 @@
import * as tts from '@diffusionstudio/vits-web'; import * as tts from "@diffusionstudio/vits-web";
async function main(event: MessageEvent<tts.InferenceConfg & { type: 'init' }>) { async function main(
if (event.data?.type != 'init') return; event: MessageEvent<tts.InferenceConfg & { type: "init" }>,
) {
if (event.data?.type != "init") return;
const start = performance.now(); const start = performance.now();
const blob = await tts.predict({ const blob = await tts.predict({
text: event.data.text, text: event.data.text,
voiceId: event.data.voiceId, voiceId: event.data.voiceId,
}); });
console.log('Time taken:', performance.now() - start + ' ms'); console.log("Time taken:", performance.now() - start + " ms");
self.postMessage({ type: 'result', audio: blob, text: event.data.text }); self.postMessage({ type: "result", audio: blob, text: event.data.text });
} }
self.addEventListener('message', main); self.addEventListener("message", main);

View File

@ -1,20 +1,28 @@
import { OVERPASS_SERVER } from "../hosts"; import { OVERPASS_SERVER } from "../hosts";
import type { OverpassResult } from "../Overpass"; import type { OverpassResult } from "../Overpass";
export async function generateVoiceGuidance(maneuver: Maneuver, shape: WorldLocation[]): Promise<string> { export async function generateVoiceGuidance(
if(maneuver.begin_shape_index == 0) { maneuver: Maneuver,
shape: WorldLocation[],
): Promise<string> {
if (maneuver.begin_shape_index == 0) {
return maneuver.verbal_pre_transition_instruction; return maneuver.verbal_pre_transition_instruction;
} }
if(maneuver.type === 26 || maneuver.type === 27) { if (maneuver.type === 26 || maneuver.type === 27) {
return `Im Kreisverkehr die ${maneuver.roundabout_exit_count}te Ausfahrt nehmen${(maneuver.street_names ?? []).length == 0 ? "." : ` auf ${(maneuver.street_names || []).join(", ")}.`}`; return `Im Kreisverkehr die ${maneuver.roundabout_exit_count}te Ausfahrt nehmen${(maneuver.street_names ?? []).length == 0 ? "." : ` auf ${(maneuver.street_names || []).join(", ")}.`}`;
} }
const landmarks = await findNearbyLandmarks(shape[maneuver.begin_shape_index]); const landmarks = await findNearbyLandmarks(
if(landmarks.length == 0) { shape[maneuver.begin_shape_index],
);
if (landmarks.length == 0) {
return maneuver.verbal_pre_transition_instruction; return maneuver.verbal_pre_transition_instruction;
} }
console.log("Original instruction:", maneuver.verbal_pre_transition_instruction); console.log(
"Original instruction:",
maneuver.verbal_pre_transition_instruction,
);
return `Hinter ${landmarks[0].tags.name}, ${typeToName(maneuver.type)}${(maneuver.street_names ?? []).length == 0 ? "." : ` auf ${(maneuver.street_names || []).join(", ")}.`}`; return `Hinter ${landmarks[0].tags.name}, ${typeToName(maneuver.type)}${(maneuver.street_names ?? []).length == 0 ? "." : ` auf ${(maneuver.street_names || []).join(", ")}.`}`;
} }
@ -72,8 +80,8 @@ export async function findNearbyLandmarks(location: WorldLocation) {
const lat = location.lat; const lat = location.lat;
const lon = location.lon; const lon = location.lon;
const res = await fetch(OVERPASS_SERVER, { const res = await fetch(OVERPASS_SERVER, {
method: "POST", method: "POST",
body: `[out:json]; body: `[out:json];
( (
node(around:${radius}, ${lat}, ${lon})["amenity"="fuel"]["name"]; node(around:${radius}, ${lat}, ${lon})["amenity"="fuel"]["name"];
way(around:${radius}, ${lat}, ${lon})["amenity"="fuel"]["name"]; way(around:${radius}, ${lat}, ${lon})["amenity"="fuel"]["name"];
@ -83,11 +91,15 @@ export async function findNearbyLandmarks(location: WorldLocation) {
way(around:${radius}, ${lat}, ${lon})["shop"]["name"]; way(around:${radius}, ${lat}, ${lon})["shop"]["name"];
); );
out center tags;`, out center tags;`,
}).then((res) => res.json() as Promise<OverpassResult>); }).then((res) => res.json() as Promise<OverpassResult>);
// Sort by distance to the location // Sort by distance to the location
return res.elements.sort((a, b) => { return res.elements.sort((a, b) => {
const distA = Math.sqrt(Math.pow(a.lat! - lat, 2) + Math.pow(a.lon! - lon, 2)); const distA = Math.sqrt(
const distB = Math.sqrt(Math.pow(b.lat! - lat, 2) + Math.pow(b.lon! - lon, 2)); 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; return distA - distB;
}); });
} }

View File

@ -194,11 +194,13 @@ async function tickRoute() {
); );
if (distanceToEnd <= verbalDistance) { if (distanceToEnd <= verbalDistance) {
hasAnnouncedPreInstruction = true; hasAnnouncedPreInstruction = true;
const instruction = USE_LANDMARK_INSTRUCTIONS ? await generateVoiceGuidance(currentManeuver, polyline) : currentManeuver.verbal_pre_transition_instruction; const instruction = USE_LANDMARK_INSTRUCTIONS
? await generateVoiceGuidance(currentManeuver, polyline)
: currentManeuver.verbal_pre_transition_instruction;
console.log( console.log(
"[Verbal instruction] ", "[Verbal instruction] ",
// currentManeuver.verbal_pre_transition_instruction, // currentManeuver.verbal_pre_transition_instruction,
instruction instruction,
); );
say(instruction); say(instruction);
} }
@ -253,7 +255,7 @@ async function tickRoute() {
} }
function verbalPreInstructionDistance(speed: number): number { function verbalPreInstructionDistance(speed: number): number {
return (speed * 2.222) + 37.144; return speed * 2.222 + 37.144;
} }
export function stopNavigation() { export function stopNavigation() {