aboutsummaryrefslogtreecommitdiffstats
path: root/src/lib
diff options
context:
space:
mode:
authorLibravatarLarge Libravatar memdmp <memdmpestrogenzone>2025-07-21 22:53:11 +0200
committerLibravatarLarge Libravatar memdmp <memdmpestrogenzone>2025-07-21 22:53:11 +0200
commita723e56b4ce392d2b11d28f2745279aa825a2ee1 (patch)
treedc69ea161c9b52e84c3969fbb9562b780f74586b /src/lib
downloadfahrplan-a723e56b4ce392d2b11d28f2745279aa825a2ee1.tar.gz
fahrplan-a723e56b4ce392d2b11d28f2745279aa825a2ee1.tar.bz2
fahrplan-a723e56b4ce392d2b11d28f2745279aa825a2ee1.tar.lz
fahrplan-a723e56b4ce392d2b11d28f2745279aa825a2ee1.zip

feat: initial commit

Diffstat (limited to 'src/lib')
-rw-r--r--src/lib/Page.svelte32
-rw-r--r--src/lib/Timetable.svelte424
-rw-r--r--src/lib/Titlebar.svelte55
-rw-r--r--src/lib/aliases.ts36
-rw-r--r--src/lib/assets/LineGlyph.svelte193
-rw-r--r--src/lib/assets/LineGlyphSrc.svg233
-rw-r--r--src/lib/assets/Pictogram.svelte11
-rw-r--r--src/lib/index.ts51
-rw-r--r--src/lib/motis-api.ts57
-rw-r--r--src/lib/motis-types.ts496
10 files changed, 1588 insertions, 0 deletions
diff --git a/src/lib/Page.svelte b/src/lib/Page.svelte
new file mode 100644
index 0000000..92cd92a
--- /dev/null
+++ b/src/lib/Page.svelte
@@ -0,0 +1,32 @@
+<script lang="ts">
+ import { S } from '$lib';
+ import type { Snippet } from 'svelte';
+
+ let {
+ children,
+ sidepanel,
+ title,
+ description = 'No Description Specified',
+ class: classes,
+ }: {
+ children: Snippet;
+ sidepanel?: Snippet;
+ title: string;
+ description?: string;
+ class?: string;
+ } = $props();
+</script>
+
+<svelte:head>
+ <title>{title}</title>
+ <meta name="description" content={description} />
+</svelte:head>
+
+<div class="{S.window} {S.shadow_large} relative max-w-[100%]">
+ {#if sidepanel}
+ <div class={S.window_sidepanel}>
+ {@render sidepanel()}
+ </div>
+ {/if}
+ {@render children()}
+</div>
diff --git a/src/lib/Timetable.svelte b/src/lib/Timetable.svelte
new file mode 100644
index 0000000..4a17ca7
--- /dev/null
+++ b/src/lib/Timetable.svelte
@@ -0,0 +1,424 @@
+<script lang="ts">
+ import { browser, dev } from '$app/environment';
+ import { page } from '$app/state';
+ import { S } from '$lib';
+ import { operators } from './aliases';
+ import LineGlyph from './assets/LineGlyph.svelte';
+ import Pictogram from './assets/Pictogram.svelte';
+ import { Mode, type StoptimesResponse } from './motis-types';
+ import { m } from './paraglide/messages';
+
+ let relativeSecondPrecision = $derived(
+ ['seconds', 'second', 'sec', 'secs', 's', '2'].includes(
+ page.url.searchParams.get('relative')!
+ )
+ );
+ let isRelativeTime = $derived(
+ relativeSecondPrecision ||
+ ['1', 'true'].includes(page.url.searchParams.get('relative')!)
+ );
+ /** only if isRelativeTime is true */
+ let now = $state(0);
+ const updateNow = () => {
+ now = Date.now();
+ setTimeout(updateNow, 33);
+ };
+ $effect(() => (isRelativeTime ? updateNow() : void 0));
+
+ let {
+ stopTimes,
+ isArrivals = false,
+ placeName = '',
+ placeId,
+ isResultsPage = true,
+ setSearch = (q) => void 0,
+ }: {
+ stopTimes: null | StoptimesResponse;
+ isArrivals?: boolean;
+ placeName?: string;
+ isResultsPage?: boolean;
+ placeId: string | null;
+ setSearch: (query: string) => void;
+ } = $props();
+ const timeRelative = (ms: number, relativeTo = now) => {
+ const _totalMillis = ms - relativeTo;
+ const totalMillis = Math.abs(_totalMillis);
+ const totalSeconds = totalMillis / 1000;
+ const totalMinutes = totalSeconds / 60;
+ const totalHours = totalMinutes / 60;
+ const hours = Math.floor(totalHours);
+ const minutes = Math.floor(totalMinutes) - hours * 60;
+ const seconds = Math.floor(totalSeconds) - hours * 60 * 60 - minutes * 60;
+ const results = [] as string[];
+ if (hours) results.push(`${hours} ${hours !== 1 ? m.hours() : m.hour()}`);
+ if (hours || minutes)
+ results.push(`${minutes} ${minutes !== 1 ? m.minutes() : m.minute()}`);
+ if (seconds && relativeSecondPrecision)
+ results.push(`${seconds} ${seconds !== 1 ? m.seconds() : m.second()}`);
+ if (results.length > 1) {
+ const [r, l] = [results.pop()!, results.pop()!];
+ results.push(
+ m.timeJoiner({
+ l,
+ r,
+ })
+ );
+ }
+ const relTime =
+ results.length === 1
+ ? results[0]
+ : results.length === 0
+ ? ''
+ : results.reduce((pv, cv) => m.timeJoiner2({ l: pv, r: cv }));
+ if (relTime === '')
+ return m.timeImmediate({
+ isPastTense: _totalMillis < 0 ? 'true' : 'false',
+ });
+ if (_totalMillis < 0)
+ return m.timeInPast({
+ relTime,
+ });
+ else
+ return m.timeInFuture({
+ relTime,
+ });
+ };
+ const localTime = async (
+ time: Date,
+ forceAbsolute = false,
+ relativeTo = now
+ ) => {
+ const offset = parseFloat(
+ browser
+ ? // Browsers like Librewolf love patching time to be a fixed timezone, breaking us.
+ (localStorage.getItem(
+ isRelativeTime && !forceAbsolute
+ ? 'relative-offset-time'
+ : 'offset-time'
+ ) ?? '0')
+ : ((await import('$env/dynamic/private')).env[
+ isRelativeTime && !forceAbsolute
+ ? 'PRIVATE_RELATIVE_TIME_OFFSET'
+ : 'PRIVATE_SERVER_TIMEZONE'
+ ] ?? '0')
+ );
+ if (isRelativeTime && !forceAbsolute) {
+ return timeRelative(time.getTime() - offset * 60 * 60 * 1000, relativeTo);
+ } else {
+ const hours = time.getHours() + Math.floor(offset);
+ const minutes = time.getMinutes() + Math.floor((offset % 1) * 60);
+ const seconds =
+ time.getSeconds() + Math.floor(((offset % 1) * 60) % 1) * 60;
+ return (
+ hours.toString().padStart(2, '0') +
+ ':' +
+ minutes.toString().padStart(2, '0') +
+ (seconds !== 0 ? `:${seconds.toString().padStart(2, '0')}` : '')
+ );
+ }
+ };
+</script>
+
+{#snippet renderLocalTime(
+ time: Date,
+ forceAbsolute = false
+)}{#await localTime(time, forceAbsolute, now)}{isRelativeTime && !forceAbsolute
+ ? timeRelative(time.getTime(), now)
+ : time.getUTCHours() +
+ ':' +
+ time.getUTCMinutes() +
+ (time.getUTCSeconds() !== 0 ? `:${time.getUTCSeconds()}` : '') +
+ ' UTC'}{:then t}{t}{/await}{/snippet}
+
+{#if stopTimes}
+ {#each stopTimes.stopTimes
+ // garbage data
+ .filter((v) => v.agencyUrl !== 'http://www.rta.ae') as departure}
+ {@const expectedTime =
+ new Date(
+ (isArrivals
+ ? departure.place.scheduledArrival
+ : departure.place.scheduledDeparture) ?? '0'
+ ).getTime() /
+ 1000 /
+ 60}
+ {@const receivedTime =
+ new Date(
+ (isArrivals ? departure.place.arrival : departure.place.departure) ??
+ '0'
+ ).getTime() /
+ 1000 /
+ 60}
+ {@const delayMinutes = receivedTime - expectedTime}
+ {@const avoidGlyph = departure.routeShortName.startsWith('FlixTrain')}
+ {@const routeShortName = (() => {
+ let n = departure.routeShortName;
+ if (n.startsWith('EC ')) n = n.replace('EC ', 'EC');
+ if (departure.mode === 'TRAM' && !isNaN(parseInt(n))) n = 'T ' + n;
+ if (departure.mode === 'BUS' && !isNaN(parseInt(n))) n = 'B ' + n;
+ if (
+ departure.routeShortName === 'European Sleeper' &&
+ departure.agencyName === 'Eu Sleeper'
+ )
+ n = 'EN'; // TODO: validate these are real euronights
+ if (n === '?') n = '';
+ if (n.startsWith('FlixTrain ')) n = n.substring(10);
+ // Note: may also catch ECs/ICs/EXTs operated by DB
+ if (
+ departure.agencyId === '12681' &&
+ departure.agencyName === 'DB Fernverkehr AG' &&
+ departure.mode === 'HIGHSPEED_RAIL' &&
+ departure.source.startsWith('de_DELFI.gtfs.zip/') &&
+ !isNaN(parseInt(n))
+ )
+ n = `ICE ${n}`;
+ return n;
+ })()}
+ {@const pictogram = (() => {
+ switch (true) {
+ case departure.mode === Mode.Bike:
+ return 'Velo_l';
+ case departure.mode === Mode.ODM:
+ case departure.mode === Mode.Rental:
+ return 'Taxi_l';
+ case departure.mode === Mode.Car:
+ case departure.mode === Mode.CarDropoff:
+ case departure.mode === Mode.CarParking:
+ return 'Auto_l';
+
+ // Transit //
+ case departure.mode === Mode.Airplane:
+ return 'Abflug_l';
+ case departure.mode === Mode.LongDistanceRail:
+ case departure.mode === Mode.RegionalFastRail:
+ case departure.mode === Mode.RegionalRail:
+ case departure.mode === Mode.Metro:
+ case departure.mode === Mode.HighspeedRail:
+ return 'Zug_l';
+ case departure.mode === Mode.NightRail:
+ return 'Schlafwagen';
+ case departure.mode === Mode.Subway:
+ return 'Metro_l_' + (m.lang_short() === 'en' ? 'de' : m.lang_short());
+ case departure.mode === Mode.Bus:
+ return 'Bus_l';
+ case departure.mode === Mode.Coach:
+ return 'Fernbus_l';
+ case departure.mode === Mode.Tram:
+ case departure.mode === Mode.CableTram:
+ return 'Tram_l';
+ case departure.mode === Mode.Funicular:
+ return 'Zahnradbahn_l';
+ case departure.mode === Mode.AerialLift:
+ // return 'Gondelbahn_l';
+ return 'Luftseilbahn_l';
+ case departure.mode === Mode.Ferry:
+ return 'Schiff_l';
+ case departure.mode === Mode.Other:
+ default:
+ return 'Licht';
+ }
+ })()}
+ {@const notices = (() => {
+ let notices = [] as [pictogram: string[], content: string][];
+ if (departure.cancelled)
+ notices.push([
+ ['Cancellation', 'Attention'],
+ m.brief_north_otter_cherish(),
+ ]);
+ if (delayMinutes < -0.5) {
+ notices.push([
+ ['Hint'],
+ m.vehicle_is_early({
+ minutes: -delayMinutes,
+ arrival: isArrivals.toString(),
+ }),
+ ]);
+ } else if (delayMinutes >= 1) {
+ notices.push([
+ delayMinutes >= 5
+ ? ['Delay', 'Attention']
+ : delayMinutes >= 3
+ ? ['Delay', 'Hint']
+ : ['Hint'],
+ m.next_long_lark_bask({
+ minutes: delayMinutes.toFixed(0),
+ }),
+ ]);
+ }
+ return notices;
+ })()}
+ {@const situationIsBad = notices.find((v) => v[0].includes('Attention'))}
+ <div
+ class={{
+ 'p-4 pr-3 sm:pr-4 md:p-6 md:pr-6 rounded-xl': true,
+ 'bg-[#28282C]': !situationIsBad,
+ 'bg-[#452525]': situationIsBad,
+ }}
+ data-data={dev ? JSON.stringify(departure, null, 2) : undefined}
+ >
+ <div class="flex gap-1 md:items-center md:flex-row flex-col flex-wrap">
+ <div class="pictoline flex gap-1 md:items-center flex-r">
+ {#if pictogram}
+ <Pictogram which={pictogram} />
+ {/if}
+ {#if ([Mode.NightRail || Mode.HighspeedRail || Mode.LongDistanceRail || Mode.RegionalFastRail || Mode.RegionalRail].includes(departure.mode) || (departure.mode === 'BUS' && routeShortName.startsWith('EV')) || (departure.mode === 'METRO' && departure.routeShortName.startsWith('S'))) && !avoidGlyph}
+ <LineGlyph currentColor="#fff" kind={routeShortName} />
+ {:else}
+ <span
+ class="ml-1 -mr-0.5 md:mt-0.5 font-sbb-typo text-nowrap font-bold"
+ >
+ {routeShortName}
+ </span>
+ {/if}
+ <span class="ml-1 -mr-0.5 md:mt-0.5 font-sbb-typo">
+ <!-- {isArrivals ? m.from() : m.to()} -->
+ {m.to()}
+ <span class="font-semibold">{departure.headsign}</span>
+ </span>
+ </div>
+ <div class="flex-1"></div>
+ {#if departure.place.scheduledTrack && departure.place.track}
+ <span
+ class={{
+ 'ml-1 mt-0.5 font-[SBB,Inter,system-ui,sans-serif]': true,
+ 'text-red-400':
+ departure.place.scheduledTrack !== departure.place.track,
+ }}
+ >
+ {#if departure.place.name !== placeName}{`${
+ departure.place.name === placeName + ', Bahnhof'
+ ? placeName + ', Busbahnhof'
+ : departure.place.name
+ }, `}
+ {/if}<span class="font-semibold"
+ >{m.station_location({
+ track: departure.place.track,
+ mode: departure.mode,
+ })}</span
+ >
+ </span>
+ {:else if departure.place.name !== placeName}{departure.place.name ===
+ placeName + ', Bahnhof'
+ ? placeName + ', Busbahnhof'
+ : departure.place.name}
+ {/if}
+ </div>
+ <div class="flex gap-1 items-center">
+ {departure.cancelled
+ ? m.antsy_weird_cowfish_wish() + ' '
+ : ''}{isRelativeTime && Math.abs(expectedTime - receivedTime) < 1
+ ? isArrivals
+ ? m.hour_tidy_hawk_explore()
+ : m.free_knotty_ray_soar()
+ : isArrivals
+ ? m.polite_lucky_angelfish_amaze()
+ : m.home_flaky_jurgen_ascend()}
+ {#if Math.abs(expectedTime - receivedTime) < 1}
+ <span class="font-bold">
+ {@render renderLocalTime(new Date(receivedTime * 60 * 1000))}
+ </span>
+ {:else}
+ <span class="line-through">
+ {@render renderLocalTime(new Date(expectedTime * 60 * 1000), true)}
+ </span>
+ <span class="font-bold">
+ {@render renderLocalTime(new Date(receivedTime * 60 * 1000), true)}
+ </span>
+ {#if isRelativeTime}
+ <span class="opacity">
+ ({@render renderLocalTime(new Date(receivedTime * 60 * 1000))})
+ </span>
+ {/if}
+ {/if}
+ </div>
+ {#if notices.length !== 0}
+ <div class="notices pt-2 flex flex-col gap-1">
+ {#each notices as notice}
+ <div class="flex items-center gap-2">
+ {#each notice[0] as pictogram}<Pictogram
+ which={pictogram}
+ />{/each}
+ <span class="ml-0.5">
+ {notice[1]}
+ </span>
+ </div>
+ {/each}
+ </div>
+ {/if}
+ <!-- <pre>{JSON.stringify(departure, null, 2)}</pre> -->
+ {#if departure.agencyName}
+ <small class="-mb-1 mt-2 block opacity-70"
+ >{m.operated_by({
+ operator: operators.has(departure.agencyName)
+ ? operators.get(departure.agencyName)!
+ : departure.agencyName,
+ })}{#if departure.agencyName === 'DB Fernverkehr AG'}
+ {' '}
+ <b>·</b>
+ {m.line_number_accuracy()}{/if}</small
+ >
+ {/if}
+ </div>
+ {/each}
+{:else}
+ <div class="flex items-center justify-center">
+ <div class="results">
+ {#if (placeName || placeId) && isResultsPage}
+ <h2 class="text-2xl opacity-90">No results</h2>
+ <p>
+ No results have been found for the station <b
+ >{placeName ?? placeId}</b
+ >.<br />
+ Please try again.
+ </p>
+ {:else if placeId}
+ <h2 class="text-2xl opacity-90">No results</h2>
+ <p>
+ No results have been found for the station <b
+ >{placeName ?? placeId}</b
+ >.<br />
+ Please try again.
+ </p>
+ {:else}
+ <h2 class="text-2xl opacity-90">No Station</h2>
+ <p class="pb-1">
+ Please input a station in the search field above and select a search
+ result.
+ </p>
+ <p class="py-1">
+ Examples:
+ <span class="pt-2 flex flex-wrap gap-2">
+ <button
+ class="{S.button('secondary').replace(
+ 'not-disabled:bg-[#0000]',
+ 'not-disabled:bg-[#2E2E3299]'
+ )} flex-1 text-nowrap p-3"
+ onclick={() => setSearch('Zürich HB')}>Zürich HB</button
+ >
+ <button
+ class="{S.button('secondary').replace(
+ 'not-disabled:bg-[#0000]',
+ 'not-disabled:bg-[#2E2E3299]'
+ )} flex-1 text-nowrap p-3"
+ onclick={() => setSearch('Berlin Hbf')}>Berlin Hbf</button
+ >
+ <button
+ class="{S.button('secondary').replace(
+ 'not-disabled:bg-[#0000]',
+ 'not-disabled:bg-[#2E2E3299]'
+ )} flex-1 text-nowrap p-3"
+ onclick={() => setSearch('Hamburg Hbf')}>Hamburg Hbf</button
+ >
+ <button
+ class="{S.button('secondary').replace(
+ 'not-disabled:bg-[#0000]',
+ 'not-disabled:bg-[#2E2E3299]'
+ )} flex-1 text-nowrap p-3"
+ onclick={() => setSearch('Bern')}>Bern</button
+ >
+ </span>
+ </p>
+ {/if}
+ </div>
+ </div>
+{/if}
diff --git a/src/lib/Titlebar.svelte b/src/lib/Titlebar.svelte
new file mode 100644
index 0000000..35cfd0d
--- /dev/null
+++ b/src/lib/Titlebar.svelte
@@ -0,0 +1,55 @@
+<script lang="ts">
+ import { S } from '$lib';
+ import type { Snippet } from 'svelte';
+ let { children }: { children?: Snippet } = $props();
+</script>
+
+<div class="rounded-tr-xl {S.window_topbar}">
+ <!-- <div class="flex gap-2 w-max">
+ <button
+ onclick={() => history.back()}
+ onkeypress={(e) =>
+ e.key === 'Return' || e.key === 'Enter'
+ ? e.currentTarget.click()
+ : void 0}
+ aria-label="Back"
+ class={S.button('secondary')}
+ ><svg
+ height="16px"
+ viewBox="0 0 16 16"
+ width="16px"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="m 12 2 c 0 -0.265625 -0.105469 -0.519531 -0.292969 -0.707031 c -0.390625 -0.390625 -1.023437 -0.390625 -1.414062 0 l -6 6 c -0.1875 0.1875 -0.292969 0.441406 -0.292969 0.707031 s 0.105469 0.519531 0.292969 0.707031 l 6 6 c 0.390625 0.390625 1.023437 0.390625 1.414062 0 c 0.1875 -0.1875 0.292969 -0.441406 0.292969 -0.707031 s -0.105469 -0.519531 -0.292969 -0.707031 l -5.292969 -5.292969 l 5.292969 -5.292969 c 0.1875 -0.1875 0.292969 -0.441406 0.292969 -0.707031 z m 0 0"
+ fill="currentColor"
+ />
+ </svg></button
+ >
+ <button
+ onclick={() => history.forward()}
+ onkeypress={(e) =>
+ e.key === 'Return' || e.key === 'Enter'
+ ? e.currentTarget.click()
+ : void 0}
+ aria-label="Forward"
+ class={S.button('secondary')}
+ ><svg
+ height="16px"
+ viewBox="0 0 16 16"
+ width="16px"
+ xmlns="http://www.w3.org/2000/svg"
+ >
+ <path
+ d="m 4 2 c 0 -0.265625 0.105469 -0.519531 0.292969 -0.707031 c 0.390625 -0.390625 1.023437 -0.390625 1.414062 0 l 6 6 c 0.1875 0.1875 0.292969 0.441406 0.292969 0.707031 s -0.105469 0.519531 -0.292969 0.707031 l -6 6 c -0.390625 0.390625 -1.023437 0.390625 -1.414062 0 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 s 0.105469 -0.519531 0.292969 -0.707031 l 5.292969 -5.292969 l -5.292969 -5.292969 c -0.1875 -0.1875 -0.292969 -0.441406 -0.292969 -0.707031 z m 0 0"
+ fill="currentColor"
+ />
+ </svg></button
+ >
+ </div> -->
+ <div
+ class="flex-1 rounded-tl-xl min-h-8 flex items-center justify-center gap-2"
+ >
+ {@render children?.()}
+ </div>
+</div>
diff --git a/src/lib/aliases.ts b/src/lib/aliases.ts
new file mode 100644
index 0000000..4fc08ec
--- /dev/null
+++ b/src/lib/aliases.ts
@@ -0,0 +1,36 @@
+import { m } from './paraglide/messages';
+
+export const placeNameMap = new Map<string, string>();
+for (const [v1, v2] of [
+ ['Freiburg(Brsg)', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg(Breisgau)', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg(Breisgau) Hbf', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg (Breisgau)', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg (Breisgau) Hbf', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg im Breisgau', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg im Breisgau Hbf', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg im Breisgau Hauptbahnhof', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg (D)', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg (D), Busbahnhof', 'Freiburg(Breisgau) Hbf'],
+ ['Freiburg Hauptbahnhof', 'Freiburg(Breisgau) Hbf'],
+ ['S+U Berlin Hauptbahnhof', 'Berlin Hbf'],
+ ['Berlin Hauptbahnhof', 'Berlin Hbf'],
+])
+ placeNameMap.set(v1.toLowerCase(), v2);
+export const operators = new Map<string, string>();
+operators.set('Schweizerische Bundesbahnen SBB', m.operator_sbb());
+operators.set('SBB', m.operator_sbb());
+
+operators.set('SZU', m.operator_szu());
+operators.set('Sihltal-Zürich-Uetliberg-Bahn', m.operator_szu());
+
+operators.set('Verkehrsbetriebe Zürich', m.operator_vbz()); // buses
+operators.set('Verkehrsbetriebe Zürich INFO+', m.operator_vbz()); // trams
+
+operators.set('BLS AG (bls)', m.operator_bls());
+
+operators.set('Städtische Verkehrsbetriebe Bern', m.operator_bernmobil());
+
+operators.set('Regionalverkehr Bern-Solothurn', m.operator_rbs());
+
+operators.set('Verkehrsbetriebe Glattal', m.operator_vbg());
diff --git a/src/lib/assets/LineGlyph.svelte b/src/lib/assets/LineGlyph.svelte
new file mode 100644
index 0000000..f5df523
--- /dev/null
+++ b/src/lib/assets/LineGlyph.svelte
@@ -0,0 +1,193 @@
+<script lang="ts">
+ import { dev } from '$app/environment';
+
+ const fernverkehrIconMap = {
+ IR: 'M 2.43946,1.11125 H 3.51261 L 2.18493,4.18041 H 1.10728 Z m 4.43442,2.16058 c 0.097,-3.3e-4 0.18477,-0.0576 0.2241,-0.14631 L 7.96899,1.11125 H 4.2291 L 2.90142,4.18041 H 3.96981 L 4.97523,1.88118 H 6.56088 L 6.24311,2.6154 4.8604,2.6104 5.97615,4.18043 H 7.19244 L 6.36588,3.27185 Z',
+ IC: 'M 2.4406,1.11203 H 3.51401 L 2.18633,4.1812 H 1.10869 Z m 1.78011,0 H 8.02939 L 7.63516,2.02352 H 4.91525 L 4.37497,3.26971 H 7.09621 L 6.70198,4.1812 H 2.89304 Z',
+ EC: 'm 2.1507979,4.1626896 a 0.238125,0.238125 0 0 0 0.094721,0.017727 H 5.5890583 L 4.5100875,3.2525229 H 2.4299333 l 0.4741334,-0.4699 h 2.0984104 l 0.4743979,-0.4699 H 3.3779354 L 3.8401625,1.8370021 h 2.098675 L 6.6558583,1.11125 H 3.3485667 A 0.43127083,0.43127083 0 0 0 3.1231417,1.164696 L 3.0644047,1.212321 2.9929667,1.271852 1.1911542,3.0860999 a 0.21166667,0.21166667 0 0 0 -0.083344,0.1664229 0.16933333,0.16933333 0 0 0 0.065617,0.1367896 l 0.1180041,0.1010708 0.6757459,0.5413375 0.1068917,0.089165 c 0.020373,0.02196 0.047625,0.036513 0.076994,0.041804 m 4.0433625,0.00582 0.088371,0.011906 H 9.44245 L 10.367169,3.2525229 H 6.5315042 L 7.9245354,1.8370021 H 11.291623 L 12.003087,1.11125 H 7.2255062 l -1.920875,1.9629437 q -0.054504,0.050006 -0.097631,0.1098021 a 0.14816667,0.14816667 0 0 0 -0.015081,0.074348 0.17197917,0.17197917 0 0 0 0.04736,0.125148 l 0.089429,0.071438 0.6402917,0.5529791 0.082815,0.077258 q 0.044979,0.033338 0.094456,0.059531 z',
+ TGV: 'M 6.1364817,1.762125 A 1.8520833,1.8520833 0 0 0 5.9634437,1.669785 1.8520833,1.8520833 0 0 0 5.7247897,1.588558 1.5610417,1.5610417 0 0 0 5.3085997,1.54252 q -0.543718,0 -0.889264,0.3603625 -0.346075,0.3603625 -0.346075,0.9186334 0,0.434975 0.245269,0.681302 0.245268,0.2460625 0.683418,0.2460625 c 0.0799,0.0037 0.160073,-0.00688 0.236538,-0.030692 l 0.170656,-0.8133288 h -0.648229 l 0.100542,-0.4833937 h 1.239837 l -0.350308,1.6748125 q -0.08784,0.030427 -0.157692,0.050271 -0.07038,0.019844 -0.236537,0.052917 a 2.38125,2.38125 0 0 1 -0.442384,0.032808 q -0.723106,0 -1.101989,-0.3537479 -0.378884,-0.3537479 -0.378884,-1.0218209 0,-0.8262937 0.50165,-1.3123333 0.50165,-0.4855104 1.351492,-0.4855104 0.26035,-0.00132 0.516731,0.043921 a 3.4395833,3.4395833 0 0 1 0.486304,0.127529 z M 1.2125854,1.1115146 H 3.5737267 L 3.4731857,1.5951729 H 2.60985 l -0.5564187,2.58445 H 1.4141979 L 1.9703521,1.5954375 H 1.1075458 Z m 6.1198123,0 h -0.613304 l 0.402961,3.0681083 h 0.731572 l 1.708415,-3.0681083 h -0.665956 l -1.278996,2.3868062 z',
+ ICE: 'M 4.9887187,1.7227021 A 1.1641667,1.1641667 0 0 0 4.6950312,1.5909396 1.3758333,1.3758333 0 0 0 4.3053,1.5425208 q -0.5167312,0 -0.8453438,0.3386667 -0.3286125,0.3381375 -0.3286125,0.8744479 0,0.4397375 0.2452688,0.7164917 a 0.809625,0.809625 0 0 0 0.635,0.2770187 c 0.1008062,5.292e-4 0.2010833,-0.0127 0.2981854,-0.039687 0.117475,-0.037042 0.2307167,-0.087048 0.3373438,-0.149225 l -0.096573,0.5889625 q -0.061383,0.013229 -0.1706562,0.03519 -0.1098021,0.02196 -0.2169583,0.034925 a 2.38125,2.38125 0 0 1 -0.2868084,0.013229 q -0.6482291,0 -1.0162646,-0.3823229 -0.3680354,-0.382323 -0.3680354,-1.063625 0,-0.7871354 0.4815417,-1.2573 Q 3.4554583,1.0591271 4.270375,1.0591271 a 2.4870833,2.4870833 0 0 1 0.4489979,0.032808 q 0.1730375,0.033073 0.2365375,0.052917 0.0635,0.019844 0.1860021,0.0635 z M 1.7515417,1.1117792 H 2.3910396 L 1.7470437,4.1798875 H 1.1075458 Z m 5.6816625,0 H 5.6327146 L 4.9887187,4.1798875 h 1.82245 L 6.9162083,3.6962292 H 5.7070625 L 5.8869792,2.8347458 H 6.9598646 L 7.0564375,2.3513521 H 5.9875208 L 6.1452125,1.5954375 h 1.1959167 z',
+ // RJ: 'M 6.093,15.802 H 3.703 L 6.133,4.2 h 5.152 q 1.329,0 2.07,0.273 0.741,0.273 1.194,1.001 0.456,0.729 0.456,1.765 0,1.48 -0.887,2.442 -0.885,0.961 -2.683,1.19 0.46,0.412 0.863,1.085 0.8,1.361 1.78,3.846 H 11.514 Q 11.206,14.821 10.304,12.739 9.812,11.615 9.259,11.228 8.919,10.998 8.072,10.998 H 7.098 Z m 1.37,-6.545 h 1.266 q 1.923,0 2.552,-0.23 0.63,-0.228 0.985,-0.72 0.357,-0.49 0.356,-1.028 0,-0.635 -0.514,-0.95 -0.317,-0.19 -1.37,-0.19 H 8.112 Z M 22.375,4.2 h 2.342 l -1.464,7.028 q -0.585,2.793 -1.535,3.783 -0.95,0.99 -2.92,0.989 -1.67,0 -2.501,-0.768 -0.831,-0.767 -0.831,-2.09 0,-0.276 0.04,-0.616 l 2.207,-0.238 a 4.4,4.4 0 0 0 -0.047,0.578 q 0,0.555 0.316,0.847 0.317,0.293 0.974,0.293 0.918,0 1.29,-0.539 0.276,-0.411 0.673,-2.318 z',
+ // RJX: '',
+ // RX: '',
+ // NJ: '',
+ // OGV: '',
+ // PE: '',
+ IRE: '',
+ BEX: '',
+ GEX: '',
+ EN: '',
+ EXT: '',
+ CNL: '',
+ ICN: '',
+ };
+ let {
+ kind: routeShortName,
+ type: _type,
+ line: _line,
+ currentColor: _currentColor,
+ nightIsFilled = true,
+ }: {
+ /** i.e. RE69, TER, etc... */
+ kind: string;
+ type?: keyof typeof fernverkehrIconMap | string | null;
+ line?: string;
+ nightIsFilled?: boolean;
+ } & {
+ currentColor?: string;
+ } = $props();
+
+ let shortNumberIndex = $derived(
+ routeShortName.split('').findIndex((char) => char.match(/[0-9]/))
+ );
+ let line = $derived(
+ _line ??
+ (_line === undefined
+ ? shortNumberIndex !== -1
+ ? routeShortName.substring(shortNumberIndex).trim()
+ : ''
+ : _line)
+ );
+ let lineType = $derived(
+ _type ??
+ (_type === undefined
+ ? shortNumberIndex !== -1
+ ? routeShortName.substring(0, shortNumberIndex).trim()
+ : routeShortName
+ : _type)
+ );
+ let isRE = $derived(
+ typeof routeShortName === 'string' &&
+ routeShortName.toUpperCase().startsWith('RE') &&
+ routeShortName.charAt(2).toUpperCase() !== 'N'
+ ),
+ isFernverkehr = $derived(
+ (lineType ?? '').toUpperCase() in fernverkehrIconMap
+ ),
+ isNightRendering =
+ routeShortName.toUpperCase().startsWith('SN') ||
+ routeShortName.toUpperCase().startsWith('REN') ||
+ routeShortName.startsWith('IRN');
+ let currentColor = $derived(
+ _currentColor ?? (isRE ? '#eb0000' : 'currentColor')
+ );
+</script>
+
+<!-- svelte-ignore attribute_illegal_colon -->
+<svg
+ width="15.61mm"
+ height="5.2919998mm"
+ viewBox="0 0 15.61 5.2919998"
+ version="1.1"
+ id="svg1"
+ data-kind={routeShortName}
+ data-type={lineType}
+ data-line={line}
+ data-debug={dev
+ ? JSON.stringify({
+ shortNumberIndex,
+ line,
+ type: lineType,
+ kind: routeShortName,
+ _line: _line ?? [`undef, ${_line}`],
+ _type: _type ?? [`undef, ${_type}`],
+ })
+ : undefined}
+>
+ <defs id="defs1">
+ <clipPath id="a">
+ <path fill="#fff" d="M0 0h59v20H0z" id="path3" />
+ </clipPath>
+ </defs>
+ {#if isFernverkehr}
+ <g id="layer4" data-inkscape-label="Fernverkehr" style="display:inline">
+ <path
+ fill="#eb0000"
+ fill-rule="evenodd"
+ d="M 0.52917,0 A 0.52916667,0.52916667 0 0 0 0,0.52916 V 4.7625 A 0.52916667,0.52916667 0 0 0 0.52917,5.29166 H 15.08125 A 0.52916667,0.52916667 0 0 0 15.61042,4.7625 V 0.52916 A 0.52916667,0.52916667 0 0 0 15.08125,0 Z"
+ clip-rule="evenodd"
+ id="path1"
+ style="display:inline;stroke-width:0.264583"
+ data-inkscape-label="Background"
+ />
+ <g
+ data-inkscape-label="Glyphs"
+ data-inkscape-groupmode="layer"
+ id="layer1"
+ >
+ {#if lineType && lineType.toUpperCase() in fernverkehrIconMap}
+ <path
+ fill="#ffffff"
+ d={fernverkehrIconMap[
+ lineType.toUpperCase() as keyof typeof fernverkehrIconMap
+ ]}
+ id="path2"
+ style="stroke-width:0.264583"
+ data-sodipodi-nodetypes="ccccccccccccccccccc"
+ data-inkscape-label="IR Glyph"
+ />
+ {/if}
+ </g>
+ {#if line !== undefined && line !== ''}
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333px;line-height:0;font-family:SBB;-inkscape-font-specification:'SBB Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;direction:ltr;text-anchor:start;fill:#ffffff;stroke-width:0.0264583"
+ x="9.5329876"
+ y="4.1801271"
+ id="text1-9"
+ data-inkscape-label="Number"
+ ><tspan
+ id="tspan1-8"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.28625px;line-height:0;font-family:SBB;-inkscape-font-specification:'SBB, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;text-anchor:start;fill:#ffffff;stroke-width:0.0264583"
+ x="9.5329876"
+ y="4.1801271"
+ data-sodipodi-role="line">{line}</tspan
+ ></text
+ >
+ {/if}
+ </g>
+ {:else}
+ <g
+ clip-path="url(#a)"
+ id="g2"
+ transform="scale(0.26458333)"
+ data-inkscape-label="Regioverkehr"
+ >
+ <title id="title1">A</title>
+ <path
+ stroke="#000000"
+ d="M 2,0.5 H 57 A 1.5,1.5 0 0 1 58.5,2 V 18 A 1.5,1.5 0 0 1 57,19.5 H 2 A 1.5,1.5 0 0 1 0.5,18 V 2 A 1.5,1.5 0 0 1 2,0.5 Z"
+ id="path1-8"
+ style="display:inline;fill:{nightIsFilled && isNightRendering
+ ? '#000'
+ : 'none'};stroke:{isNightRendering
+ ? nightIsFilled
+ ? '#000'
+ : '#FFDE15'
+ : currentColor};stroke-opacity:1"
+ data-inkscape-label="Background"
+ />
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.32594px;line-height:0;font-family:SBB;-inkscape-font-specification:'SBB, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:{isNightRendering
+ ? '#FFDE15'
+ : currentColor};fill-opacity:1;stroke:{/*isNightRendering
+ ? nightIsFilled
+ '#FFDE15'
+ : currentColor*/ 'none'};stroke-width:0.0264583;stroke-opacity:1"
+ x="0.80801982"
+ y="4.2193737"
+ id="text1"
+ data-inkscape-label="Kind"
+ transform="scale(3.7795276)"
+ ><tspan
+ data-sodipodi-role="line"
+ id="tspan1"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.32594px;font-family:SBB;-inkscape-font-specification:'SBB, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.0264583"
+ x="0.80801982"
+ y="4.2193737">{routeShortName}</tspan
+ ></text
+ >
+ </g>
+ {/if}
+</svg>
diff --git a/src/lib/assets/LineGlyphSrc.svg b/src/lib/assets/LineGlyphSrc.svg
new file mode 100644
index 0000000..8d7bc59
--- /dev/null
+++ b/src/lib/assets/LineGlyphSrc.svg
@@ -0,0 +1,233 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ width="15.61mm"
+ height="5.2919998mm"
+ viewBox="0 0 15.61 5.2919998"
+ version="1.1"
+ id="svg1"
+ inkscape:version="1.4 (e7c3feb100, 2024-10-09)"
+ sodipodi:docname="LineGlyphSrc.svg"
+ xml:space="preserve"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
+ id="namedview1"
+ pagecolor="#505050"
+ bordercolor="#ffffff"
+ borderopacity="1"
+ inkscape:showpageshadow="0"
+ inkscape:pageopacity="0"
+ inkscape:pagecheckerboard="1"
+ inkscape:deskcolor="#505050"
+ inkscape:document-units="mm"
+ inkscape:zoom="16.635742"
+ inkscape:cx="20.077253"
+ inkscape:cy="1.5929557"
+ inkscape:window-width="1920"
+ inkscape:window-height="1014"
+ inkscape:window-x="0"
+ inkscape:window-y="0"
+ inkscape:window-maximized="1"
+ inkscape:current-layer="layer1" /><defs
+ id="defs1"><clipPath
+ id="a"><path
+ fill="#fff"
+ d="M0 0h59v20H0z"
+ id="path3" /></clipPath><clipPath
+ id="a-6"><path
+ fill="#fff"
+ d="M0 0h59v20H0z"
+ id="path3-8" /></clipPath></defs><g
+ inkscape:groupmode="layer"
+ id="layer4"
+ inkscape:label="Fernverkehr"
+ style="display:inline"
+ transform="scale(0.99997309,1)"><path
+ fill="#eb0000"
+ fill-rule="evenodd"
+ d="M 0.52917,0 A 0.52916667,0.52916667 0 0 0 0,0.52916 V 4.7625 A 0.52916667,0.52916667 0 0 0 0.52917,5.29166 H 15.08125 A 0.52916667,0.52916667 0 0 0 15.61042,4.7625 V 0.52916 A 0.52916667,0.52916667 0 0 0 15.08125,0 Z"
+ clip-rule="evenodd"
+ id="path1"
+ style="display:inline;stroke-width:0.264583"
+ inkscape:label="Background" /><g
+ inkscape:label="Glyphs"
+ inkscape:groupmode="layer"
+ id="layer1"
+ style="display:inline"><path
+ fill="#ffffff"
+ d="M 2.43946,1.11125 H 3.51261 L 2.18493,4.18041 H 1.10728 Z m 4.43442,2.16058 c 0.097,-3.3e-4 0.18477,-0.0576 0.2241,-0.14631 L 7.96899,1.11125 H 4.2291 L 2.90142,4.18041 H 3.96981 L 4.97523,1.88118 H 6.56088 L 6.24311,2.6154 4.8604,2.6104 5.97615,4.18043 H 7.19244 L 6.36588,3.27185 Z"
+ id="path2"
+ style="display:none;stroke-width:0.264583"
+ sodipodi:nodetypes="ccccccccccccccccccc"
+ inkscape:label="IR Glyph" /><path
+ fill="#ffffff"
+ d="M 2.4406,1.11203 H 3.51401 L 2.18633,4.1812 H 1.10869 Z m 1.78011,0 H 8.02939 L 7.63516,2.02352 H 4.91525 L 4.37497,3.26971 H 7.09621 L 6.70198,4.1812 H 2.89304 Z"
+ id="path2-7"
+ style="display:none;stroke-width:0.264583"
+ sodipodi:nodetypes="cccccccccccccc"
+ inkscape:label="IC Glyph" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="m 2.1507979,4.1626896 a 0.238125,0.238125 0 0 0 0.094721,0.017727 H 5.5890583 L 4.5100875,3.2525229 H 2.4299333 l 0.4741334,-0.4699 h 2.0984104 l 0.4743979,-0.4699 H 3.3779354 L 3.8401625,1.8370021 h 2.098675 L 6.6558583,1.11125 H 3.3485667 A 0.43127083,0.43127083 0 0 0 3.1231417,1.164696 L 3.0644047,1.212321 2.9929667,1.271852 1.1911542,3.0860999 a 0.21166667,0.21166667 0 0 0 -0.083344,0.1664229 0.16933333,0.16933333 0 0 0 0.065617,0.1367896 l 0.1180041,0.1010708 0.6757459,0.5413375 0.1068917,0.089165 c 0.020373,0.02196 0.047625,0.036513 0.076994,0.041804 m 4.0433625,0.00582 0.088371,0.011906 H 9.44245 L 10.367169,3.2525229 H 6.5315042 L 7.9245354,1.8370021 H 11.291623 L 12.003087,1.11125 H 7.2255062 l -1.920875,1.9629437 q -0.054504,0.050006 -0.097631,0.1098021 a 0.14816667,0.14816667 0 0 0 -0.015081,0.074348 0.17197917,0.17197917 0 0 0 0.04736,0.125148 l 0.089429,0.071438 0.6402917,0.5529791 0.082815,0.077258 q 0.044979,0.033338 0.094456,0.059531 z"
+ clip-rule="evenodd"
+ id="path2-8"
+ style="display:inline;stroke-width:0.264583"
+ inkscape:label="EC Glyph" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="M 6.1364817,1.762125 A 1.8520833,1.8520833 0 0 0 5.9634437,1.669785 1.8520833,1.8520833 0 0 0 5.7247897,1.588558 1.5610417,1.5610417 0 0 0 5.3085997,1.54252 q -0.543718,0 -0.889264,0.3603625 -0.346075,0.3603625 -0.346075,0.9186334 0,0.434975 0.245269,0.681302 0.245268,0.2460625 0.683418,0.2460625 c 0.0799,0.0037 0.160073,-0.00688 0.236538,-0.030692 l 0.170656,-0.8133288 h -0.648229 l 0.100542,-0.4833937 h 1.239837 l -0.350308,1.6748125 q -0.08784,0.030427 -0.157692,0.050271 -0.07038,0.019844 -0.236537,0.052917 a 2.38125,2.38125 0 0 1 -0.442384,0.032808 q -0.723106,0 -1.101989,-0.3537479 -0.378884,-0.3537479 -0.378884,-1.0218209 0,-0.8262937 0.50165,-1.3123333 0.50165,-0.4855104 1.351492,-0.4855104 0.26035,-0.00132 0.516731,0.043921 a 3.4395833,3.4395833 0 0 1 0.486304,0.127529 z M 1.2125854,1.1115146 H 3.5737267 L 3.4731857,1.5951729 H 2.60985 l -0.5564187,2.58445 H 1.4141979 L 1.9703521,1.5954375 H 1.1075458 Z m 6.1198123,0 h -0.613304 l 0.402961,3.0681083 h 0.731572 l 1.708415,-3.0681083 h -0.665956 l -1.278996,2.3868062 z"
+ clip-rule="evenodd"
+ id="path2-81"
+ style="display:none;stroke-width:0.264583"
+ inkscape:label="TGV Glyph" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="M 4.9887187,1.7227021 A 1.1641667,1.1641667 0 0 0 4.6950312,1.5909396 1.3758333,1.3758333 0 0 0 4.3053,1.5425208 q -0.5167312,0 -0.8453438,0.3386667 -0.3286125,0.3381375 -0.3286125,0.8744479 0,0.4397375 0.2452688,0.7164917 a 0.809625,0.809625 0 0 0 0.635,0.2770187 c 0.1008062,5.292e-4 0.2010833,-0.0127 0.2981854,-0.039687 0.117475,-0.037042 0.2307167,-0.087048 0.3373438,-0.149225 l -0.096573,0.5889625 q -0.061383,0.013229 -0.1706562,0.03519 -0.1098021,0.02196 -0.2169583,0.034925 a 2.38125,2.38125 0 0 1 -0.2868084,0.013229 q -0.6482291,0 -1.0162646,-0.3823229 -0.3680354,-0.382323 -0.3680354,-1.063625 0,-0.7871354 0.4815417,-1.2573 Q 3.4554583,1.0591271 4.270375,1.0591271 a 2.4870833,2.4870833 0 0 1 0.4489979,0.032808 q 0.1730375,0.033073 0.2365375,0.052917 0.0635,0.019844 0.1860021,0.0635 z M 1.7515417,1.1117792 H 2.3910396 L 1.7470437,4.1798875 H 1.1075458 Z m 5.6816625,0 H 5.6327146 L 4.9887187,4.1798875 h 1.82245 L 6.9162083,3.6962292 H 5.7070625 L 5.8869792,2.8347458 H 6.9598646 L 7.0564375,2.3513521 H 5.9875208 L 6.1452125,1.5954375 h 1.1959167 z"
+ clip-rule="evenodd"
+ id="path2-73"
+ style="display:none;stroke-width:0.264583"
+ inkscape:label="ICE Glyph" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="m 14.415,11.156 a 0.27,0.27 0 0 1 0.225,-0.15 q 0.15,0 0.15,0.282 v 0.037 l -0.9,4.55 q -0.075,0.206 -0.243,0.206 a 0.165,0.165 0 0 1 -0.188,-0.188 l -0.018,-0.188 q -0.056,-0.507 -0.488,-0.507 h -0.112 l -0.094,0.019 -0.112,0.037 -0.45,0.188 a 7,7 0 0 1 -2.829,0.564 q -2.322,0 -3.746,-1.39 Q 4.186,13.224 4.186,10.95 a 6.66,6.66 0 0 1 2.042,-4.87 6.62,6.62 0 0 1 4.852,-2.048 6.25,6.25 0 0 1 2.735,0.64 l 0.73,0.337 q 0.165,0.063 0.338,0.076 0.375,0 0.637,-0.49 L 15.707,4.22 15.782,4.145 15.914,4.107 a 0.17,0.17 0 0 1 0.168,0.15 l 0.019,0.038 V 4.37 l -1.05,4.512 -0.037,0.244 -0.093,0.15 A 0.4,0.4 0 0 1 14.752,9.352 L 14.658,9.314 14.602,9.239 V 8.581 A 4.6,4.6 0 0 0 13.665,5.667 q -0.936,-1.203 -2.304,-1.203 -1.854,0 -3.222,2.2 -1.368,2.199 -1.368,5.17 0,3.758 2.623,3.759 1.854,0 2.885,-1.185 a 16,16 0 0 0 1.63,-2.18 l 0.168,-0.338 0.244,-0.546 0.037,-0.056 0.02,-0.056 z m 3.597,1.297 q -0.152,0.65 -0.169,1.316 c -0.01,0.39 0.098,0.773 0.31,1.1 a 0.9,0.9 0 0 0 0.758,0.46 l 0.206,0.02 h 0.056 q 0.188,0.056 0.188,0.206 a 0.6,0.6 0 0 1 -0.094,0.188 l -0.131,0.056 H 15.07 a 0.165,0.165 0 0 1 -0.187,-0.187 l 0.019,-0.095 a 0.44,0.44 0 0 1 0.262,-0.132 l 0.169,-0.037 a 2.4,2.4 0 0 0 1.405,-0.874 q 0.543,-0.686 0.805,-1.964 L 18.949,5.667 18.724,5.329 A 1.42,1.42 0 0 0 17.656,4.652 h -0.075 q -0.337,0 -0.337,-0.225 0,-0.226 0.412,-0.226 h 3.297 l 4.403,8.027 1.086,-5.076 0.056,-0.282 q 0.042,-0.195 0.057,-0.394 0.018,-0.226 0.018,-0.414 0,-1.165 -1.03,-1.39 L 25.318,4.633 25.206,4.577 25.15,4.427 q 0,-0.226 0.206,-0.226 h 3.915 a 0.16,0.16 0 0 1 0.169,0.094 l 0.018,0.094 a 0.226,0.226 0 0 1 -0.206,0.244 l -0.168,0.038 c -0.37,0.018 -0.72,0.164 -0.993,0.414 A 2.7,2.7 0 0 0 27.323,6.081 L 27.173,6.438 27.042,6.833 25,16.025 24.98,16.044 v 0.037 a 0.3,0.3 0 0 1 -0.205,0.113 0.2,0.2 0 0 1 -0.169,-0.075 7,7 0 0 1 -0.3,-0.526 L 19.323,6.307 Z m 9.61,3.159 q 0,0.187 0.263,0.187 h 8.917 l 1.537,-4.135 0.075,-0.245 q 0,-0.169 -0.188,-0.169 l -0.169,0.075 -0.112,0.207 -0.056,0.132 a 6.8,6.8 0 0 1 -2.183,2.585 q -1.49,1.1 -2.8,1.1 -0.956,0 -0.956,-0.79 0.015,-0.279 0.056,-0.555 0.056,-0.423 0.113,-0.667 l 1.61,-7.557 q 0.225,-1.11 0.994,-1.109 h 0.187 l 0.225,-0.019 q 0.337,0 0.337,-0.225 V 4.389 q 0,-0.188 -0.15,-0.188 H 30.49 q -0.43,0 -0.431,0.226 0,0.188 0.356,0.263 h 0.056 q 0.712,0.17 0.712,0.808 0,0.075 -0.028,0.31 -0.028,0.236 -0.103,0.705 l -1.555,7.425 q -0.28,1.222 -1.274,1.354 l -0.356,0.056 a 0.247,0.247 0 0 0 -0.244,0.263"
+ clip-rule="evenodd"
+ id="path2-650"
+ inkscape:label="CNL Glyph"
+ style="display:inline;fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ d="M 3.669,15.8 6.104,4.132 h 8.651 L 14.35,6.082 H 8.102 l -0.557,2.65 h 6.04 l -0.405,1.95 H 7.139 L 6.406,13.85 h 6.79 l -0.414,1.95 z m 13.009,0 h -3.104 l 5.15,-5.905 -2.898,-5.763 h 2.508 l 1.138,2.189 q 0.056,0.12 0.78,1.616 0.048,0.08 0.103,0.214 A 82,82 0 0 1 21.987,6.21 l 1.838,-2.078 h 3.017 l -5.34,6.145 2.873,5.523 h -2.707 l -0.875,-1.759 q -0.685,-1.392 -0.907,-1.998 -0.31,0.47 -1.6,1.942 z m 14.204,0 h -2.404 l 2.037,-9.718 h -3.422 l 0.406,-1.95 h 9.193 l -0.406,1.95 H 32.91 Z"
+ id="path2-02"
+ inkscape:label="EXT Glyph"
+ style="fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="m 8.287,15.8 h 5.288 l 3.428,-3.508 H 9.138 l 1.77,-1.774 h 7.933 L 20.656,8.74 h -7.933 l 1.748,-1.799 h 7.935 L 25.116,4.2 H 12.588 a 1.4,1.4 0 0 0 -0.807,0.2 l -0.246,0.182 -0.27,0.223 -6.834,6.858 c -0.15,0.176 -0.237,0.398 -0.246,0.63 a 0.7,0.7 0 0 0 0.203,0.517 l 0.426,0.382 2.576,2.048 0.381,0.335 0.316,0.158 q 0.051,0.003 0.099,0.024 l 0.075,0.032 z M 33.115,12.675 V 4.2 H 27.268 L 15.726,15.8 h 5.358 L 29.552,7.301 V 15.8 h 5.849 L 46.963,4.2 h -5.4 z"
+ clip-rule="evenodd"
+ id="path2-5"
+ style="fill:none"
+ transform="scale(0.26458333)"
+ inkscape:label="EN Glyph" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="m 9.856,9.605 h 5.173 l -1.041,4.977 a 9,9 0 0 1 -2.244,1.005 9.1,9.1 0 0 1 -2.708,0.411 q -2.145,0 -3.289,-0.973 -1.56,-1.329 -1.561,-3.853 a 8.1,8.1 0 0 1 0.67,-3.244 6.6,6.6 0 0 1 2.287,-2.896 5.96,5.96 0 0 1 3.486,-1.03 q 2.003,0 3.213,0.935 1.21,0.934 1.605,2.722 L 13.231,7.912 A 2.8,2.8 0 0 0 12.289,6.432 2.6,2.6 0 0 0 10.652,5.934 3.805,3.805 0 0 0 7.032,8.394 6.8,6.8 0 0 0 6.488,11.14 q 0,1.464 0.663,2.164 0.663,0.7 1.94,0.7 a 6,6 0 0 0 1.57,-0.217 q 0.72,-0.187 1.387,-0.518 l 0.355,-1.725 h -2.95 z m 5.602,6.196 2.413,-11.6 h 8.572 l -0.402,1.938 h -6.19 l -0.552,2.635 h 5.985 l -0.402,1.939 h -5.986 l -0.725,3.149 h 6.727 l -0.41,1.939 z m 9.096,0 h 3.076 l 1.593,-1.805 q 0.841,-0.924 1.585,-1.93 0.22,0.6 0.898,1.986 l 0.868,1.749 h 2.681 L 32.408,10.309 37.7,4.201 H 34.71 L 32.89,6.266 A 82,82 0 0 0 31.274,8.196 2,2 0 0 0 31.171,7.983 L 30.398,6.377 29.27,4.2 h -2.484 l 2.87,5.728 z"
+ clip-rule="evenodd"
+ id="path2-45"
+ inkscape:label="GEX Glyph"
+ style="display:inline;fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="M 4.186,15.8 6.606,4.2 h 3.952 q 0.9,-0.02 1.798,0.08 c 0.508,0.058 0.999,0.22 1.443,0.474 0.383,0.225 0.694,0.554 0.899,0.95 0.207,0.406 0.313,0.857 0.307,1.313 a 2.7,2.7 0 0 1 -0.536,1.673 3.27,3.27 0 0 1 -1.656,1.073 2.6,2.6 0 0 1 1.392,0.874 c 0.327,0.4 0.504,0.903 0.5,1.42 a 3.66,3.66 0 0 1 -0.59,1.983 3.33,3.33 0 0 1 -1.633,1.349 Q 11.441,15.8 9.635,15.8 Z M 8.003,8.884 H 9.816 A 6.3,6.3 0 0 0 11.575,8.71 1.47,1.47 0 0 0 12.647,7.294 C 12.657,7.028 12.567,6.768 12.395,6.566 A 1.26,1.26 0 0 0 11.678,6.186 10,10 0 0 0 10.424,6.139 H 8.578 Z m 1.246,5.049 H 6.954 l 0.63,-3.03 h 2.84 q 0.993,0 1.42,0.375 c 0.277,0.237 0.433,0.587 0.425,0.953 a 1.56,1.56 0 0 1 -0.296,0.902 1.64,1.64 0 0 1 -0.788,0.614 q -0.493,0.186 -1.936,0.186 M 15.34,15.8 17.753,4.2 h 8.572 l -0.402,1.939 h -6.19 L 19.18,8.774 h 5.986 l -0.402,1.938 h -5.986 l -0.725,3.15 h 6.726 l -0.41,1.938 z m 9.096,0 h 3.076 l 1.593,-1.804 q 0.841,-0.924 1.584,-1.93 0.221,0.6 0.9,1.985 l 0.867,1.749 h 2.681 L 32.29,10.309 37.582,4.2 H 34.593 L 32.771,6.265 A 82,82 0 0 0 31.155,8.195 2,2 0 0 0 31.052,7.982 L 30.279,6.376 29.153,4.2 h -2.484 l 2.87,5.729 z"
+ clip-rule="evenodd"
+ id="path2-4"
+ inkscape:label="BEX Glyph"
+ style="display:inline;fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ d="M 4.28,16 6.707,4.483 H 9.116 L 6.69,16 Z m 4.659,0 2.409,-11.517 h 2.722 c 2.129,0 4.587,-0.033 4.587,2.723 0,1.98 -1.485,3.036 -3.366,3.069 v 0.032 c 0.627,0.034 1.023,0.43 1.172,1.057 L 17.568,16 H 15.143 L 14.582,13.294 C 14.318,12.074 14.384,11.215 13.13,11.215 H 12.255 L 11.25,16 Z M 13.311,6.298 12.668,9.4 h 0.841 c 1.238,0 2.74,-0.38 2.74,-1.897 0,-1.205 -1.04,-1.205 -1.98,-1.205 z M 19.19,16 21.616,4.483 h 6.782 l -0.347,1.815 h -4.504 l -0.594,2.838 h 4.026 l -0.363,1.815 h -4.043 l -0.676,3.234 h 4.554 L 26.055,16 Z"
+ id="path2-87"
+ inkscape:label="IRE Glyph"
+ style="fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="m 7.002,9.378 h 0.745 q 1.913,0 2.554,-0.24 A 2.1,2.1 0 0 0 11.31,8.369 q 0.369,-0.528 0.368,-1.16 0,-0.426 -0.188,-0.697 A 1.07,1.07 0 0 0 10.962,6.115 Q 10.622,5.991 9.444,5.991 H 7.714 Z M 5.664,15.801 H 3.246 L 5.705,4.06 h 4.772 q 1.273,0 2.014,0.3 0.741,0.3 1.173,0.993 0.432,0.693 0.433,1.654 0,0.89 -0.345,1.73 -0.345,0.84 -0.844,1.345 a 3.6,3.6 0 0 1 -1.077,0.765 q -0.578,0.26 -1.554,0.396 -0.57,0.08 -2.13,0.08 H 6.601 Z M 13.602,15.8 16.052,4.061 h 8.705 l -0.408,1.962 h -6.286 l -0.56,2.667 h 6.077 l -0.408,1.962 h -6.078 l -0.737,3.187 h 6.83 l -0.416,1.962 z"
+ clip-rule="evenodd"
+ id="path2-805"
+ inkscape:label="PE Glyph"
+ style="fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ d="m 4.116,11.252 q 0,-1.028 0.308,-2.168 0.404,-1.525 1.227,-2.65 A 5.96,5.96 0 0 1 7.739,4.663 q 1.258,-0.657 2.863,-0.657 2.152,0 3.473,1.337 1.329,1.336 1.329,3.544 a 7.8,7.8 0 0 1 -0.862,3.551 q -0.863,1.716 -2.342,2.642 -1.479,0.926 -3.346,0.926 -1.621,0 -2.72,-0.736 -1.1,-0.735 -1.56,-1.82 A 5.6,5.6 0 0 1 4.116,11.252 m 2.357,-0.048 q 0,1.195 0.728,2.002 0.728,0.807 1.914,0.806 0.965,0 1.851,-0.633 0.894,-0.64 1.472,-1.93 0.585,-1.297 0.585,-2.523 0,-1.368 -0.736,-2.144 Q 11.552,6 10.412,6 8.665,6 7.566,7.63 A 6.3,6.3 0 0 0 6.474,11.205 M 21.942,9.606 h 5.189 l -1.044,4.976 q -0.918,0.593 -2.254,1.004 a 9.1,9.1 0 0 1 -2.714,0.412 q -2.152,0 -3.298,-0.973 -1.566,-1.329 -1.566,-3.852 0,-1.693 0.672,-3.244 0.807,-1.867 2.294,-2.895 1.487,-1.028 3.496,-1.028 2.009,0 3.22,0.933 1.218,0.934 1.613,2.721 l -2.222,0.254 q -0.294,-0.98 -0.95,-1.48 -0.648,-0.498 -1.637,-0.498 -1.155,0 -2.12,0.601 -0.965,0.6 -1.51,1.86 -0.547,1.257 -0.547,2.744 0,1.464 0.665,2.167 0.664,0.696 1.946,0.696 0.767,0 1.574,-0.213 a 7.3,7.3 0 0 0 1.392,-0.522 l 0.356,-1.725 H 21.539 Z M 33.408,15.8 H 30.861 L 28.781,4.204 h 2.38 l 1.472,8.772 4.801,-8.772 H 39.8 Z"
+ id="path2-91"
+ inkscape:label="OGV Glyph"
+ style="fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="m 12.966,15.78 h -2.23 L 7.594,7.964 5.96,15.78 H 3.73 L 6.158,4.148 h 2.238 l 3.15,7.784 1.626,-7.784 h 2.222 z M 22.435,4.148 h 2.349 l -1.468,7.046 q -0.588,2.8 -1.54,3.793 -0.951,0.99 -2.927,0.991 -1.675,0 -2.508,-0.77 -0.833,-0.768 -0.833,-2.094 0,-0.277 0.04,-0.619 l 2.214,-0.238 a 4.4,4.4 0 0 0 -0.048,0.58 q 0,0.555 0.317,0.848 0.318,0.294 0.976,0.294 0.921,0 1.293,-0.54 0.278,-0.413 0.675,-2.325 z"
+ clip-rule="evenodd"
+ id="path2-9"
+ inkscape:label="NJ Glyph"
+ style="fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ d="M 9.204,4.2 4.186,15.8 H 8.42 l 1.905,-4.609 1.68,-4.091 h 8.962 l -1.21,2.788 h -7.438 a 66,66 0 0 0 1.557,1.596 238,238 0 0 0 2.879,2.799 q 0.739,0.707 1.613,1.517 h 10.777 l 1.926,-1.484 2.129,-1.64 1.389,3.124 h 6.228 L 38.891,12.63 37.166,9.753 45.612,4.2 h -6.43 L 35.597,6.875 34.297,4.2 h -6.071 l 3.204,5.575 -8.447,5.575 q -0.627,-0.495 -1.49,-1.202 -0.863,-0.709 -2.229,-1.765 h 0.874 q 0.739,0 1.199,-0.011 a 9,9 0 0 0 0.66,-0.034 1.7,1.7 0 0 0 0.673,-0.157 c 0.24,-0.232 0.424,-0.517 0.537,-0.832 L 26.232,4.2 Z"
+ id="path2-0"
+ inkscape:label="RX Glyph"
+ style="display:inline;fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="M 6.093,15.802 H 3.703 L 6.133,4.2 h 5.152 q 1.329,0 2.07,0.273 0.741,0.273 1.194,1.001 0.456,0.729 0.456,1.765 0,1.48 -0.887,2.442 -0.885,0.961 -2.683,1.19 0.46,0.412 0.863,1.085 0.8,1.361 1.78,3.846 H 11.514 Q 11.206,14.821 10.304,12.739 9.812,11.615 9.259,11.228 8.919,10.998 8.072,10.998 H 7.098 Z m 1.37,-6.545 h 1.266 q 1.923,0 2.552,-0.23 0.63,-0.228 0.985,-0.72 0.357,-0.49 0.356,-1.028 0,-0.635 -0.514,-0.95 -0.317,-0.19 -1.37,-0.19 H 8.112 Z M 22.375,4.2 h 2.342 l -1.464,7.028 q -0.585,2.793 -1.535,3.783 -0.95,0.99 -2.92,0.989 -1.67,0 -2.501,-0.768 -0.831,-0.767 -0.831,-2.09 0,-0.276 0.04,-0.616 l 2.207,-0.238 a 4.4,4.4 0 0 0 -0.047,0.578 q 0,0.555 0.316,0.847 0.317,0.293 0.974,0.293 0.918,0 1.29,-0.539 0.276,-0.411 0.673,-2.318 z m 4.538,11.602 H 23.826 L 28.946,9.93 26.066,4.2 h 2.493 l 1.132,2.176 q 0.055,0.12 0.775,1.607 0.048,0.08 0.103,0.214 A 82,82 0 0 1 32.192,6.266 L 34.02,4.2 h 3 l -5.311,6.11 2.857,5.492 h -2.691 l -0.87,-1.749 q -0.681,-1.384 -0.903,-1.986 -0.309,0.466 -1.59,1.93 z"
+ clip-rule="evenodd"
+ id="path2-80"
+ inkscape:label="RJX Glyph"
+ style="display:inline;fill:none"
+ transform="scale(0.26458333)" /><path
+ fill="#ffffff"
+ fill-rule="evenodd"
+ d="M 6.093,15.802 H 3.703 L 6.133,4.2 h 5.152 q 1.329,0 2.07,0.273 0.741,0.273 1.194,1.001 0.456,0.729 0.456,1.765 0,1.48 -0.887,2.442 -0.885,0.961 -2.683,1.19 0.46,0.412 0.863,1.085 0.8,1.361 1.78,3.846 H 11.514 Q 11.206,14.821 10.304,12.739 9.812,11.615 9.259,11.228 8.919,10.998 8.072,10.998 H 7.098 Z m 1.37,-6.545 h 1.266 q 1.923,0 2.552,-0.23 0.63,-0.228 0.985,-0.72 0.357,-0.49 0.356,-1.028 0,-0.635 -0.514,-0.95 -0.317,-0.19 -1.37,-0.19 H 8.112 Z M 22.375,4.2 h 2.342 l -1.464,7.028 q -0.585,2.793 -1.535,3.783 -0.95,0.99 -2.92,0.989 -1.67,0 -2.501,-0.768 -0.831,-0.767 -0.831,-2.09 0,-0.276 0.04,-0.616 l 2.207,-0.238 a 4.4,4.4 0 0 0 -0.047,0.578 q 0,0.555 0.316,0.847 0.317,0.293 0.974,0.293 0.918,0 1.29,-0.539 0.276,-0.411 0.673,-2.318 z"
+ clip-rule="evenodd"
+ id="path2-65"
+ inkscape:label="RJ Glyph"
+ style="display:inline;fill:none"
+ transform="scale(0.26458333)" /></g><text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.23333px;line-height:0;font-family:SBB;-inkscape-font-specification:'SBB Bold';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:none;fill:#ffffff;stroke-width:0.0264583"
+ x="9.5329876"
+ y="4.1801271"
+ id="text1-9"
+ inkscape:label="Number"><tspan
+ id="tspan1-8"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.28625px;line-height:0;font-family:SBB;-inkscape-font-specification:'SBB, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;text-anchor:start;fill:#ffffff;stroke-width:0.0264583"
+ x="0"
+ y="0"
+ sodipodi:role="line">37</tspan></text></g><g
+ clip-path="url(#a)"
+ id="g2"
+ transform="scale(0.26458333)"
+ inkscape:label="Regioverkehr"
+ style="display:none"><title
+ id="title1">A</title><path
+ stroke="#000000"
+ d="M 2,0.5 H 57 A 1.5,1.5 0 0 1 58.5,2 V 18 A 1.5,1.5 0 0 1 57,19.5 H 2 A 1.5,1.5 0 0 1 0.5,18 V 2 A 1.5,1.5 0 0 1 2,0.5 Z"
+ id="path1-8"
+ style="display:inline;fill:none;stroke:#00e1ff;stroke-opacity:1"
+ inkscape:label="Background" /><text
+ xml:space="preserve"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.32594px;line-height:0;font-family:SBB;-inkscape-font-specification:'SBB, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;direction:ltr;text-anchor:start;display:inline;fill:#00e1ff;fill-opacity:1;stroke:#00e1ff;stroke-width:0.0264583;stroke-opacity:1"
+ x="0.80801982"
+ y="4.2193737"
+ id="text1"
+ inkscape:label="Kind"
+ transform="scale(3.7795276)"><tspan
+ sodipodi:role="line"
+ id="tspan1"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:4.32594px;font-family:SBB;-inkscape-font-specification:'SBB, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-east-asian:normal;stroke-width:0.0264583"
+ x="0.80801982"
+ y="4.2193737">RE37</tspan></text></g><g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="References"
+ style="display:none"><path
+ d="m 193.67641,104.14923 v 3.0599 h -0.60907 v -2.00289 h -0.71755 v -0.44371 c 0.12276,-7.2e-4 0.24499,-0.0162 0.36407,-0.046 0.0934,-0.0264 0.18071,-0.0704 0.2577,-0.12964 0.11254,-0.0864 0.1883,-0.23231 0.22728,-0.43762 z m 0.87207,0.96917 c -0.001,-0.22429 0.0632,-0.44403 0.18547,-0.63209 0.18927,-0.27341 0.47361,-0.41011 0.85302,-0.41011 0.2921,0 0.52784,0.0746 0.70723,0.22384 0.18917,0.15271 0.29638,0.38483 0.28998,0.62786 0.004,0.14073 -0.0362,0.27916 -0.11377,0.39661 -0.0728,0.1089 -0.18183,0.18855 -0.30771,0.22489 0.15611,0.0326 0.29475,0.12224 0.38894,0.25136 0.0979,0.13705 0.1479,0.30241 0.14287,0.47095 0.008,0.28545 -0.11509,0.55885 -0.33337,0.74295 -0.20602,0.17851 -0.46619,0.26776 -0.78052,0.26776 -0.34043,0 -0.61049,-0.0983 -0.81016,-0.29501 -0.19949,-0.19685 -0.29924,-0.46266 -0.29924,-0.79745 v -0.0397 h 0.58605 c 0,0.18821 0.0445,0.33364 0.13362,0.4363 0.0963,0.10583 0.23495,0.16219 0.37756,0.15372 0.16263,0 0.28777,-0.0481 0.37544,-0.14446 0.0799,-0.0886 0.12303,-0.20479 0.12092,-0.32438 0.006,-0.13659 -0.051,-0.26837 -0.15452,-0.35772 -0.0847,-0.0727 -0.21537,-0.10901 -0.39211,-0.10901 -0.0494,0 -0.0988,0.003 -0.14817,0.008 v -0.44979 c 0.0363,0.003 0.0712,0.004 0.10451,0.004 0.35031,0 0.52546,-0.13044 0.52546,-0.39132 0.004,-0.1047 -0.0378,-0.20594 -0.11456,-0.27728 -0.0833,-0.073 -0.19172,-0.11098 -0.30242,-0.10584 -0.12511,-0.007 -0.24689,0.042 -0.33179,0.13415 -0.0778,0.0891 -0.1195,0.21951 -0.12515,0.39132 z"
+ style="display:none;fill:#ffffff;stroke-width:0.264583"
+ id="path1-3"
+ sodipodi:nodetypes="ccccccccccccscccccccscscccccccsccscccccc"
+ inkscape:label="IR13 Number" /><path
+ d="m 192.16493,105.11918 c -0.001,-0.22432 0.0634,-0.44406 0.18573,-0.63209 0.18927,-0.27341 0.47361,-0.41011 0.85302,-0.41011 0.2921,0 0.52784,0.0746 0.70723,0.22384 0.18917,0.15271 0.29638,0.38483 0.28998,0.62786 0.004,0.14073 -0.0362,0.27917 -0.11377,0.39662 -0.0728,0.1089 -0.18183,0.18855 -0.30771,0.22489 0.15611,0.0326 0.29475,0.12224 0.38894,0.25136 0.0979,0.13705 0.1479,0.30241 0.14287,0.47095 0.008,0.28545 -0.11509,0.55885 -0.33337,0.74295 -0.20602,0.17851 -0.46619,0.26776 -0.78052,0.26776 -0.34043,0 -0.61048,-0.0983 -0.81015,-0.29501 -0.1995,-0.19685 -0.29925,-0.46266 -0.29925,-0.79745 v -0.0397 h 0.58605 c 0,0.18821 0.0445,0.33364 0.13335,0.4363 0.0963,0.10583 0.23522,0.16219 0.37756,0.15372 0.16263,0 0.28787,-0.0481 0.37571,-0.14446 0.0796,-0.0886 0.12303,-0.20479 0.12092,-0.32438 0.006,-0.13659 -0.051,-0.26837 -0.15452,-0.35772 -0.0847,-0.0727 -0.21537,-0.10901 -0.39211,-0.10901 -0.0494,0 -0.0988,0.003 -0.14817,0.008 v -0.44979 c 0.0363,0.003 0.0712,0.004 0.10451,0.004 0.35031,0 0.52546,-0.13053 0.52546,-0.39159 0.004,-0.10461 -0.0379,-0.20574 -0.11456,-0.27702 -0.0833,-0.073 -0.19172,-0.11098 -0.30242,-0.10584 -0.12511,-0.007 -0.24689,0.042 -0.33179,0.13415 -0.0778,0.0891 -0.1195,0.21951 -0.12515,0.39132 z"
+ style="display:none;fill:#ffffff;stroke-width:0.264583"
+ id="path1-37"
+ inkscape:label="IC3 Number" /><path
+ fill="#000000"
+ d="M 12.894,4.224 V 6.4 H 9.316 v 9.604 H 6.746 V 6.399 H 3.186 V 4.224 Z m 10.119,0 V 6.4 h -6.268 v 2.514 h 5.744 v 2.022 h -5.744 v 2.893 h 6.38 v 2.175 h -8.95 V 4.224 Z m 1.804,0 h 5.704 q 1.692,0 2.595,0.532 0.749,0.435 1.144,1.16 0.395,0.725 0.395,1.636 0,1.03 -0.5,1.796 -0.492,0.765 -1.378,1.096 0.782,0.194 1.225,0.87 0.282,0.428 0.379,0.894 0.096,0.467 0.25,2.135 0.112,1.225 0.346,1.523 l 0.113,0.137 H 32.407 A 2,2 0 0 1 32.205,15.367 Q 32.149,14.98 32.052,13.577 31.956,12.329 31.44,11.854 30.932,11.371 29.7,11.37 h -2.312 v 4.633 h -2.57 z m 2.57,2.022 v 3.24 h 2.458 q 1.184,0 1.668,-0.34 0.612,-0.427 0.612,-1.288 0,-0.838 -0.524,-1.225 Q 31.077,6.246 29.95,6.246 Z"
+ id="path2-6"
+ style="display:none;fill:#00e1ff;fill-opacity:1"
+ transform="scale(0.26458333)"
+ inkscape:label="TER Text" /></g></svg>
diff --git a/src/lib/assets/Pictogram.svelte b/src/lib/assets/Pictogram.svelte
new file mode 100644
index 0000000..fe97de6
--- /dev/null
+++ b/src/lib/assets/Pictogram.svelte
@@ -0,0 +1,11 @@
+<script lang="ts">
+ import { base } from '$app/paths';
+
+ let { which }: { which: string } = $props();
+</script>
+
+<img
+ src="{base}/pictograms/{which}.svg"
+ alt={which}
+ style="height: 5.2919998mm;width: 5.2919998mm;"
+/>
diff --git a/src/lib/index.ts b/src/lib/index.ts
new file mode 100644
index 0000000..a961354
--- /dev/null
+++ b/src/lib/index.ts
@@ -0,0 +1,51 @@
+// place files you want to import through the `$lib` alias in this folder.
+export type ButtonColours = {
+ fg: string;
+ fgDisabled: string;
+ bg: string;
+ bgActive: string;
+ bgHover: string;
+};
+export const S = {
+ card_bg: 'bg-[#ffffff]/20' as const,
+ shadow_large: 'shadow-[0_5px_25px_-3px_#00000077]' as const,
+ window:
+ 'outline-[1.5px] outline-[#313135] rounded-xl flex flex-row items-stretch justify-stretch max-h-full' as const,
+ window_sidepanel: 'bg-[#28282C] rounded-l-xl p-2 pt-0 min-w-48' as const,
+ window_content: 'bg-[#1D1D20] rounded-r-xl p-2 pt-0 flex-1' as const,
+ window_content_fullscreen:
+ 'bg-[#1D1D20] rounded-xl p-2 pt-0 flex-1 max-w-[100%]' as const,
+ window_topbar:
+ 'box-border min-h-[3.5rem] px-1 py-2 font-bold flex items-center justify-center gap-2 sticky top-0 bg-inherit z-50' as const,
+ button(colours: 'primary' | 'secondary' | ButtonColours) {
+ switch (colours) {
+ case 'primary':
+ colours = {
+ fg: '#dedede',
+ fgDisabled: '#2e3436',
+ bg: '#3584E4',
+ bgActive: '#2A6AB8',
+ bgHover: '#4990E7',
+ };
+ break;
+ case 'secondary':
+ colours = {
+ fg: '#dedede',
+ fgDisabled: '#2e3436',
+ bg: '#0000',
+ bgActive: '#dedede22',
+ bgHover: '#dedede11',
+ };
+ break;
+ }
+ colours = colours as ButtonColours;
+ return `transition-all disabled:grayscale disabled:text-[${colours.fgDisabled}] not-disabled:text-[${colours.fg}] not-disabled:bg-[${colours.bg}] not-disabled:hover:bg-[${colours.bgHover}] not-disabled:active:bg-[${colours.bgActive}] flex items-center justify-center p-2 rounded-xl outline-2 outline-[#0000] not-disabled:focus:outline-[#4C82C999]`;
+ },
+ input:
+ 'transition-all bg-[#2E2E32] not-disabled:not-focus:not-active:hover:bg-[#dedede11] p-2 outline-2 outline-[#0000] not-disabled:focus:outline-[#4C82C999]' as const,
+ heading: [
+ //
+ 'text-2xl font-bold',
+ 'text-lg',
+ ] as const,
+};
diff --git a/src/lib/motis-api.ts b/src/lib/motis-api.ts
new file mode 100644
index 0000000..1d43eea
--- /dev/null
+++ b/src/lib/motis-api.ts
@@ -0,0 +1,57 @@
+import type { StoptimesResponse } from './motis-types';
+export class MotisAPI {
+ backend = 'https://api.transitous.org';
+ fetch: typeof fetch = (url, init) =>
+ fetch(
+ `${this.backend}${this.backend.endsWith('/') ? '' : '/'}${
+ typeof url === 'string'
+ ? url.startsWith('/')
+ ? url.substring(1)
+ : url
+ : url instanceof URL
+ ? url.pathname + url.search
+ : url
+ }`,
+ init
+ );
+ async getStopTimes(
+ id: string,
+ abortSignal?: AbortSignal,
+ arrivals = false,
+ limit = 128,
+ time: Date | undefined = arrivals
+ ? new Date(
+ // '2025-07-19T02:03:11Z'
+ Date.now() - 1000 * 60
+ )
+ : undefined
+ ) {
+ const res = await this.fetch(
+ `/api/v1/stoptimes?stopId=${encodeURIComponent(
+ id
+ )}&n=${encodeURIComponent(limit.toString())}&arriveBy=${
+ arrivals ? 'true' : 'false'
+ }&withScheduledSkippedStops=${
+ localStorage.getItem('with-scheduled-skipped-stops') ?? true
+ }&radius=${localStorage.getItem('radius') ?? 350}${
+ time ? `&time=${time.toISOString()}` : ''
+ }`,
+ {
+ signal: abortSignal,
+ }
+ );
+ if (res.status !== 200)
+ throw new Error(
+ `Stop Times: Expected 200 OK, got ${res.status} ${
+ res.statusText
+ } (${await res
+ .text()
+ .catch((e) => `Could not get response body: ${e}`)})`
+ );
+ const json = (await res.json()) as StoptimesResponse;
+ // TODO: validate
+ return json;
+ }
+}
+export const motis = new MotisAPI();
+export default motis;
diff --git a/src/lib/motis-types.ts b/src/lib/motis-types.ts
new file mode 100644
index 0000000..2f05229
--- /dev/null
+++ b/src/lib/motis-types.ts
@@ -0,0 +1,496 @@
+export type Area = {
+ /** Name of the area */
+ name: string;
+ /** {@link https://wiki.openstreetmap.org/wiki/Key:admin_level OpenStreetMap `admin_level`} of the area */
+ adminLevel: number;
+ /** Whether this area was matched by the input text */
+ matched: boolean;
+ /** Set for the first area after the `default` area that distinguishes areas if the match is ambiguous regarding (`default` area + place name / street [+ house number]). */
+ unique: boolean;
+ /** Whether this area should be displayed as default area (area with admin level closest 7) */
+ default: boolean;
+};
+export type Match = {
+ /** location type */
+ type: 'ADDRESS' | 'PLACE' | 'STOP';
+ /** list of non-overlapping tokens that were matched */
+ tokens: number[][];
+ /** name of the location (transit stop / PoI / address) */
+ name: string;
+ /** unique id of the location */
+ id: string;
+ /** latitude */
+ lat: number;
+ /** longitude */
+ lon: number;
+ /** level according to OpenStreetMap (at the moment only for public transport) */
+ level?: number;
+ /** street name */
+ street?: string;
+ /** house number */
+ houseNumber?: string;
+ /** zip code */
+ zip?: string;
+ /** list of areas */
+ areas: Area[];
+};
+export enum Mode {
+ // street //
+ Walk = 'WALK',
+ Bike = 'BIKE',
+ /** Experimental. Expect unannounced breaking changes (without version bumps) for all parameters and returned structs. */
+ Rental = 'RENTAL',
+ Car = 'CAR',
+ /** Experimental. Expect unannounced breaking changes (without version bumps) for all parameters and returned structs. */
+ CarParking = 'CAR_PARKING',
+ /** Experimental. Expect unannounced breaking changes (without version bumps) for all parameters and returned structs. */
+ CarDropoff = 'CAR_DROPOFF',
+ /** on-demand taxis from the Prima+ÖV Project */
+ ODM = 'ODM',
+ /** flexible transports */
+ Flex = 'FLEX',
+
+ // transit //
+
+ /** translates to `RAIL,TRAM,BUS,FERRY,AIRPLANE,COACH,CABLE_CAR,FUNICULAR,AREAL_LIFT,OTHER` */
+ Transit = 'TRANSIT',
+ /** trams */
+ Tram = 'TRAM',
+ /** subway trains */
+ Subway = 'SUBWAY',
+ /** ferries */
+ Ferry = 'FERRY',
+ /** airline flights */
+ Airplane = 'AIRPLANE',
+ /** metro trains */
+ Metro = 'METRO',
+ /** short distance buses (does not include `COACH`) */
+ Bus = 'BUS',
+ /** long distance buses (does not include `BUS`) */
+ Coach = 'COACH',
+ /** translates to `HIGHSPEED_RAIL,LONG_DISTANCE,NIGHT_RAIL,REGIONAL_RAIL,REGIONAL_FAST_RAIL,METRO,SUBWAY` */
+ Rail = 'RAIL',
+ /** long distance high speed trains (e.g. TGV, ICE) */
+ HighspeedRail = 'HIGHSPEED_RAIL',
+ /** long distance inter city trains */
+ LongDistanceRail = 'LONG_DISTANCE',
+ /** long distance night trains */
+ NightRail = 'NIGHT_RAIL',
+ /** regional express routes that skip low traffic stops to be faster */
+ RegionalFastRail = 'REGIONAL_FAST_RAIL',
+ /** regional train */
+ RegionalRail = 'REGIONAL_RAIL',
+ /** Cable tram. Used for street-level rail cars where the cable runs beneath the vehicle (e.g., cable car in San Francisco). */
+ CableTram = 'CABLE_CAR',
+ /** Funicular. Any rail system designed for steep inclines. */
+ Funicular = 'FUNICULAR',
+ /** Aerial lift, suspended cable car (e.g., gondola lift, aerial tramway). Cable transport where cabins, cars, gondolas or open chairs are suspended by means of one or more cables. */
+ AerialLift = 'AREAL_LIFT',
+ Other = 'OTHER',
+}
+
+/**
+ * - `NORMAL` - latitude / longitude coordinate or address
+ * - `BIKESHARE` - bike sharing station
+ * - `TRANSIT` - transit stop
+ */
+export type VertexType = 'NORMAL' | 'BIKESHARE' | 'TRANSIT';
+/**
+ * - `NORMAL` - entry/exit is possible normally
+ * - `NOT_ALLOWED` - entry/exit is not allowed
+ */
+export type PickupDropoffType = 'NORMAL' | 'NOT_ALLOWED';
+
+/** Cause of this alert. */
+export type AlertCause =
+ | 'UNKNOWN_CAUSE'
+ | 'OTHER_CAUSE'
+ | 'TECHNICAL_PROBLEM'
+ | 'STRIKE'
+ | 'DEMONSTRATION'
+ | 'ACCIDENT'
+ | 'HOLIDAY'
+ | 'WEATHER'
+ | 'MAINTENANCE'
+ | 'CONSTRUCTION'
+ | 'POLICE_ACTIVITY'
+ | 'MEDICAL_EMERGENCY';
+/** The effect of this problem on the affected entity. */
+export type AlertEffect =
+ | 'NO_SERVICE'
+ | 'REDUCED_SERVICE'
+ | 'SIGNIFICANT_DELAYS'
+ | 'DETOUR'
+ | 'ADDITIONAL_SERVICE'
+ | 'MODIFIED_SERVICE'
+ | 'OTHER_EFFECT'
+ | 'UNKNOWN_EFFECT'
+ | 'STOP_MOVED'
+ | 'NO_EFFECT'
+ | 'ACCESSIBILITY_ISSUE';
+/** The severity of the alert. */
+export type AlertSeverityLevel =
+ | 'UNKNOWN_SEVERITY'
+ | 'INFO'
+ | 'WARNING'
+ | 'SEVERE';
+/**
+ * A time interval.
+ * The interval is considered active at time t if t is greater than or equal to the start time and less than the end time.
+ */
+export type TimeRange = {
+ /**
+ * If missing, the interval starts at minus infinity.
+ * If a TimeRange is provided, either start or end must be provided - both fields cannot be empty.
+ */
+ start?: string;
+ /**
+ * If missing, the interval ends at plus infinity.
+ * If a TimeRange is provided, either start or end must be provided - both fields cannot be empty.
+ */
+ end?: string;
+};
+/**
+ * An alert, indicating some sort of incident in the public transit network.
+ */
+export type Alert = {
+ /**
+ * Time when the alert should be shown to the user.
+ * If missing, the alert will be shown as long as it appears in the feed.
+ * If multiple ranges are given, the alert will be shown during all of them.
+ */
+ communicationPeriod?: Array<TimeRange>;
+
+ /**
+ * Time when the services are affected by the disruption mentioned in the alert.
+ */
+ impactPeriod?: Array<TimeRange>;
+
+ cause?: AlertCause;
+
+ /**
+ * * Description of the cause of the alert that allows for agency-specific language;
+ * more specific than the Cause.
+ *
+ */
+ causeDetail?: string;
+
+ effect?: AlertEffect;
+
+ /**
+ * * Description of the effect of the alert that allows for agency-specific language;
+ * more specific than the Effect.
+ *
+ */
+ effectDetail?: string;
+
+ /**
+ * * The URL which provides additional information about the alert.
+ */
+ url?: string;
+
+ /**
+ * * Header for the alert. This plain-text string will be highlighted, for example in boldface.
+ *
+ */
+ headerText: string;
+
+ /**
+ * * Description for the alert.
+ * This plain-text string will be formatted as the body of the alert (or shown on an explicit "expand" request by the user).
+ * The information in the description should add to the information of the header.
+ *
+ */
+ descriptionText: string;
+
+ /**
+ * * Text containing the alert's header to be used for text-to-speech implementations.
+ * This field is the text-to-speech version of header_text.
+ * It should contain the same information as headerText but formatted such that it can read as text-to-speech
+ * (for example, abbreviations removed, numbers spelled out, etc.)
+ *
+ */
+ ttsHeaderText?: string;
+
+ /**
+ * * Text containing a description for the alert to be used for text-to-speech implementations.
+ * This field is the text-to-speech version of description_text.
+ * It should contain the same information as description_text but formatted such that it can be read as text-to-speech
+ * (for example, abbreviations removed, numbers spelled out, etc.)
+ *
+ */
+ ttsDescriptionText?: string;
+
+ /**
+ * * Severity of the alert.
+ */
+ severityLevel?: AlertSeverityLevel;
+
+ /**
+ * * String containing an URL linking to an image.
+ */
+ imageUrl?: string;
+
+ /**
+ * * IANA media type as to specify the type of image to be displayed. The type must start with "image/"
+ *
+ */
+ imageMediaType?: string;
+
+ /**
+ * * Text describing the appearance of the linked image in the image field
+ * (e.g., in case the image can't be displayed or the user can't see the image for accessibility reasons).
+ * See the HTML spec for alt image text.
+ *
+ */
+ imageAlternativeText?: string;
+};
+
+export type Place = {
+ /**
+ * name of the transit stop / PoI / address
+ */
+ name: string;
+
+ /**
+ * The ID of the stop. This is often something that users don't care about.
+ */
+ stopId?: string;
+
+ /**
+ * latitude
+ */
+ lat: number;
+
+ /**
+ * longitude
+ */
+ lon: number;
+
+ /**
+ * level according to OpenStreetMap
+ */
+ level: number;
+
+ /**
+ * arrival time
+ */
+ arrival?: string;
+
+ /**
+ * departure time
+ */
+ departure?: string;
+
+ /**
+ * scheduled arrival time
+ */
+ scheduledArrival?: string;
+
+ /**
+ * scheduled departure time
+ */
+ scheduledDeparture?: string;
+
+ /**
+ * scheduled track from the static schedule timetable dataset
+ */
+ scheduledTrack?: string;
+
+ /**
+ * The current track/platform information, updated with real-time updates if available.
+ * Can be missing if neither real-time updates nor the schedule timetable contains track information.
+ *
+ */
+ track?: string;
+
+ /**
+ * description of the location that provides more detailed information
+ */
+ description?: string;
+
+ vertexType?: VertexType;
+
+ /**
+ * Type of pickup. It could be disallowed due to schedule, skipped stops or cancellations.
+ */
+ pickupType?: PickupDropoffType;
+
+ /**
+ * Type of dropoff. It could be disallowed due to schedule, skipped stops or cancellations.
+ */
+ dropoffType?: PickupDropoffType;
+
+ /**
+ * Whether this stop is cancelled due to the realtime situation.
+ */
+ cancelled?: boolean;
+
+ /**
+ * Alerts for this stop.
+ */
+ alerts?: Array<Alert>;
+
+ /**
+ * for `FLEX` transports, the flex location area or location group name
+ */
+ flex?: string;
+
+ /**
+ * for `FLEX` transports, the flex location area ID or location group ID
+ */
+ flexId?: string;
+
+ /**
+ * Time that on-demand service becomes available
+ */
+ flexStartPickupDropOffWindow?: string;
+
+ /**
+ * Time that on-demand service ends
+ */
+ flexEndPickupDropOffWindow?: string;
+};
+/**
+ * departure or arrival event at a stop
+ */
+export type StopTime = {
+ /**
+ * information about the stop place and time
+ */
+ place: Place;
+ /**
+ * Transport mode for this leg
+ */
+ mode: Mode;
+ /**
+ * Whether there is real-time data about this leg
+ */
+ realTime: boolean;
+ /**
+ * For transit legs, the headsign of the bus or train being used.
+ * For non-transit legs, null
+ *
+ */
+ headsign: string;
+ agencyId: string;
+ agencyName: string;
+ agencyUrl: string;
+ routeColor?: string;
+ routeTextColor?: string;
+ tripId: string;
+ routeShortName: string;
+ /**
+ * Type of pickup (for departures) or dropoff (for arrivals), may be disallowed either due to schedule, skipped stops or cancellations
+ */
+ pickupDropoffType: PickupDropoffType;
+ /**
+ * Whether the departure/arrival is cancelled due to the realtime situation (either because the stop is skipped or because the entire trip is cancelled).
+ */
+ cancelled: boolean;
+ /**
+ * Whether the entire trip is cancelled due to the realtime situation.
+ */
+ tripCancelled: boolean;
+ /**
+ * Filename and line number where this trip is from
+ */
+ source: string;
+};
+export type StoptimesResponse = {
+ /**
+ * list of stop times
+ */
+ stopTimes: Array<StopTime>;
+ /**
+ * metadata of the requested stop
+ */
+ place: Place;
+ /**
+ * Use the cursor to get the previous page of results. Insert the cursor into the request and post it to get the previous page.
+ * The previous page is a set of stop times BEFORE the first stop time in the result.
+ *
+ */
+ previousPageCursor: string;
+ /**
+ * Use the cursor to get the next page of results. Insert the cursor into the request and post it to get the next page.
+ * The next page is a set of stop times AFTER the last stop time in this result.
+ *
+ */
+ nextPageCursor: string;
+};
+// export type StoptimesData = {
+// query: {
+// /**
+// * Optional. Default is `false`.
+// *
+// * - `arriveBy=true`: the parameters `date` and `time` refer to the arrival time
+// * - `arriveBy=false`: the parameters `date` and `time` refer to the departure time
+// *
+// */
+// arriveBy?: boolean;
+// /**
+// * This parameter will be ignored in case `pageCursor` is set.
+// *
+// * Optional. Default is
+// * - `LATER` for `arriveBy=false`
+// * - `EARLIER` for `arriveBy=true`
+// *
+// * The response will contain the next `n` arrivals / departures
+// * in case `EARLIER` is selected and the previous `n`
+// * arrivals / departures if `LATER` is selected.
+// *
+// */
+// direction?: 'EARLIER' | 'LATER';
+// /**
+// * Optional. Default is `false`.
+// *
+// * If set to `true`, only stations that are phyiscally in the radius are considered.
+// * If set to `false`, additionally to the stations in the radius, equivalences with the same name and children are considered.
+// *
+// */
+// exactRadius?: boolean;
+// /**
+// * Optional. Default is all transit modes.
+// *
+// * Only return arrivals/departures of the given modes.
+// *
+// */
+// mode?: Array<Mode>;
+// /**
+// * the number of events
+// */
+// n: number;
+// /**
+// * Use the cursor to go to the next "page" of stop times.
+// * Copy the cursor from the last response and keep the original request as is.
+// * This will enable you to search for stop times in the next or previous time-window.
+// *
+// */
+// pageCursor?: string;
+// /**
+// * Optional. Radius in meters.
+// *
+// * Default is that only stop times of the parent of the stop itself
+// * and all stops with the same name (+ their child stops) are returned.
+// *
+// * If set, all stops at parent stations and their child stops in the specified radius
+// * are returned.
+// *
+// */
+// radius?: number;
+// /**
+// * stop id of the stop to retrieve departures/arrivals for
+// */
+// stopId: string;
+// /**
+// * Optional. Defaults to the current time.
+// *
+// */
+// time?: string;
+// /**
+// * Optional. Include stoptimes where passengers can not alight/board according to schedule.
+// */
+// withScheduledSkippedStops?: boolean;
+// };
+// };