diff --git a/.gitignore b/.gitignore index 7d67f13..e337bbc 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,5 @@ __pycache__/ src/data/announcements/* !src/data/announcements/.gitkeep config.json +src/legacy/ +main_legacy.py \ No newline at end of file diff --git a/bruno/Gleiswechsel-API/.gitignore b/bruno/Gleiswechsel-API/.gitignore deleted file mode 100644 index e19311f..0000000 --- a/bruno/Gleiswechsel-API/.gitignore +++ /dev/null @@ -1,9 +0,0 @@ -# Secrets -.env* - -# Dependencies -node_modules - -# OS files -.DS_Store -Thumbs.db \ No newline at end of file diff --git a/bruno/Gleiswechsel-API/Geocode München HBF.yml b/bruno/Gleiswechsel-API/Geocode München HBF.yml deleted file mode 100644 index 865c048..0000000 --- a/bruno/Gleiswechsel-API/Geocode München HBF.yml +++ /dev/null @@ -1,29 +0,0 @@ -info: - name: Geocode München HBF - type: http - seq: 1 - -http: - method: GET - url: https://api.transitous.org/api/v1/geocode?text=München Hbf - headers: - - name: User-Agent - value: Gleiswechsel-Discord-Bot - params: - - name: text - value: München Hbf - type: query - auth: inherit - -runtime: - scripts: - - type: after-response - code: |- - const data = res.getBody(); - bru.setEnvVar("stop_id", data[0].id); - -settings: - encodeUrl: true - timeout: 0 - followRedirects: true - maxRedirects: 5 diff --git a/bruno/Gleiswechsel-API/Stoptimes for Station.yml b/bruno/Gleiswechsel-API/Stoptimes for Station.yml deleted file mode 100644 index 29a2ef7..0000000 --- a/bruno/Gleiswechsel-API/Stoptimes for Station.yml +++ /dev/null @@ -1,32 +0,0 @@ -info: - name: Stoptimes for Station - type: http - seq: 3 - -http: - method: GET - url: https://api.transitous.org/api/v1/stoptimes?stopId={{stop_id}}&n=10 - headers: - - name: User-Agent - value: Gleiswechsel-Discord-Bot - params: - - name: stopId - value: "{{stop_id}}" - type: query - - name: n - value: "10" - type: query - auth: inherit - -runtime: - scripts: - - type: after-response - code: |- - const data = res.getBody(); - bru.setEnvVar("trip_id", data.stopTimes[0].tripId); - -settings: - encodeUrl: true - timeout: 0 - followRedirects: true - maxRedirects: 5 diff --git a/bruno/Gleiswechsel-API/Trip Details.yml b/bruno/Gleiswechsel-API/Trip Details.yml deleted file mode 100644 index 1d45b12..0000000 --- a/bruno/Gleiswechsel-API/Trip Details.yml +++ /dev/null @@ -1,22 +0,0 @@ -info: - name: Trip Details - type: http - seq: 4 - -http: - method: GET - url: https://api.transitous.org/api/v2/trip?tripId={{trip_id}} - headers: - - name: User-Agent - value: Gleiswechsel-Discord-Bot - params: - - name: tripId - value: "{{trip_id}}" - type: query - auth: inherit - -settings: - encodeUrl: true - timeout: 0 - followRedirects: true - maxRedirects: 5 diff --git a/bruno/Gleiswechsel-API/environments/Transitous API.yml b/bruno/Gleiswechsel-API/environments/Transitous API.yml deleted file mode 100644 index 0662ac7..0000000 --- a/bruno/Gleiswechsel-API/environments/Transitous API.yml +++ /dev/null @@ -1,6 +0,0 @@ -name: Transitous API -variables: - - name: stop_id - value: at-Railway-Current-Reference-Data-2026_de:09162:100:11:11 - - name: trip_id - value: 20260815_11:52_de-DELFI_3338623109 diff --git a/bruno/Gleiswechsel-API/opencollection.yml b/bruno/Gleiswechsel-API/opencollection.yml deleted file mode 100644 index 43d1edd..0000000 --- a/bruno/Gleiswechsel-API/opencollection.yml +++ /dev/null @@ -1,10 +0,0 @@ -opencollection: 1.0.0 - -info: - name: Gleiswechsel-API -bundled: false -extensions: - bruno: - ignore: - - node_modules - - .git diff --git a/main.py b/main.py index 82acaf6..2de3078 100644 --- a/main.py +++ b/main.py @@ -30,5 +30,5 @@ TODO - text announcements - voice announcements - improved error handling (retry connection) -- Footer Notice Slogangs +- multi language support ''' \ No newline at end of file diff --git a/src/api/transitous.py b/src/api/transitous.py index 813b791..6abe4a0 100644 --- a/src/api/transitous.py +++ b/src/api/transitous.py @@ -1,6 +1,6 @@ import requests import random -from datetime import datetime +import json from src.utils import logger, config, get_train_name, convert_iso_string @@ -73,10 +73,15 @@ def get_random_connection(stop_id: str) -> str: logger("Couldn't find any connection", "fatal") return None - trip_id = random.choice(trip_id) - from_station = stop_times[0]["place"]["name"] + trip_id = random.choice(trip_ids) + for trip in stop_times: + if trip.get("tripId") == trip_id: + from_station = trip.get("place").get("name") + break + + print(from_station) return { - "trip_id": random.choice(trip_ids), + "trip_id": trip_id, "from_station": from_station } @@ -85,7 +90,7 @@ def get_trip_details(trip_id: str, from_station: str) -> dict: try: response = requests.get(req, headers=headers) - response.raise_for_status + response.raise_for_status() data = response.json() except requests.RequestException as e: logger(f"An error occured while trying to get the route details: {e}", "fatal") @@ -120,8 +125,8 @@ def get_trip_details(trip_id: str, from_station: str) -> dict: trip_details["stops"][trip_from] = departure for stop in legs["intermediateStops"]: - arrival = convert_iso_string(stop["arrival"]) - trip_details["stops"][stop["name"]] = arrival + stop_arrival = convert_iso_string(stop["arrival"]) + trip_details["stops"][stop["name"]] = stop_arrival if stop.get("name") == from_station: departure_time = stop["departure"] trip_details["departure"] = convert_iso_string(departure_time) diff --git a/src/commands.py b/src/commands.py deleted file mode 100644 index 67298c3..0000000 --- a/src/commands.py +++ /dev/null @@ -1,12 +0,0 @@ -import discord -from discord.ext import commands -from src.embeds import build_info_embed, build_error_embed - -def setup_commands(bot: discord.Bot): - @bot.slash_command(description="Informationen über die aktuelle Fahrt") - async def info(ctx): - embed = build_info_embed() - if embed is None: - await build_error_embed("Noch keine Verbindung gesetzt.") - return - await ctx.respond(embed=embed) diff --git a/src/config.py b/src/config.py deleted file mode 100644 index 45d45c1..0000000 --- a/src/config.py +++ /dev/null @@ -1,4 +0,0 @@ -import json - -with open("config.json", "r") as file: - config = json.load(file) \ No newline at end of file diff --git a/src/data/emojis.py b/src/data/emojis.py index 11bfe5f..22fafcc 100644 --- a/src/data/emojis.py +++ b/src/data/emojis.py @@ -3,5 +3,7 @@ emoji_list = { "BUS": "🚎", "TRAM": "🚈", "REGIONAL_RAIL": "🚊", - "HIGHSPEED_RAIL": "🚅" + "HIGHSPEED_RAIL": "🚅", + "METRO": "🚇", + "SUBWAY": "🚇" } \ No newline at end of file diff --git a/src/dc/embeds.py b/src/dc/embeds.py index 8b1973f..6ffa2ee 100644 --- a/src/dc/embeds.py +++ b/src/dc/embeds.py @@ -45,4 +45,6 @@ def build_info_embed() -> discord.Embed: embed.set_author(name=agency) embed.set_thumbnail(url=metadata["logo"]) + color = trip.get("color", "keine farbe :(") + print(color) return embed \ No newline at end of file diff --git a/src/embeds.py b/src/embeds.py deleted file mode 100644 index 2097669..0000000 --- a/src/embeds.py +++ /dev/null @@ -1,109 +0,0 @@ -import random -import discord -from datetime import datetime, timedelta -import src.handlers as handlers -from src.config import config -from src.utils import operator_metadata, format_via_list, resolve_operator, logger - -def format_timestamp(timestr): - parsed_time = datetime.strptime(timestr, "%H:%M") - now = datetime.now() - final_datetime = datetime.now().replace( - hour=parsed_time.hour, - minute=parsed_time.minute, - second=0, - microsecond=0 - ) - - if final_datetime <= now: - final_datetime += timedelta(days=1) - - return discord.utils.format_dt(final_datetime, style="t") - -def format_iso_timestamp(isostr): - parsed_time = discord.utils.parse_time(isostr) - return discord.utils.format_dt(parsed_time, style="t") - -def build_embed_footer(operator_slogans): - dbf = config.get("dbf", "https://dbf.finalrewind.org") - footer_notice = f"Daten großzügig bereitgestellt von {dbf} • Typ: {handlers.train_type}" - footer_text = ( - f"{random.choice(operator_slogans)} • {footer_notice}" - if operator_slogans else - footer_notice - ) - icon = f"{dbf}/static/icons/icon-96x96.png" - return { - "text": footer_text, - "icon": icon - } - -def build_info_embed() -> discord.Embed | None: - conn = handlers.current - info = handlers.train_info - - if handlers.current is None and handlers.train_info is None: - return None - - current_operator = resolve_operator(info["operators"]) - arrival = format_iso_timestamp(info["arrival"]) - departure = format_timestamp(conn['departure']) - - operator_infos = operator_metadata(current_operator) - - embed = discord.Embed( - title=handlers.train_name, - description=f"Abfahrt von {conn['station']} um {departure}. Ankunft um {arrival}", - color=operator_infos["color"] - ) - - if len(conn['via']) > 0: - embed.add_field(name="Über", value=format_via_list(conn['via']), inline=False) - - if operator_infos.get("unknown"): - logger(f"Metadaten für {current_operator} konten in operators.py nicht gefunden werden") - - embed.set_author(name=current_operator) - embed.set_thumbnail(url=operator_infos["logo"]) - - route_lines = [] - for stop in conn['route']: - stop_name = stop["name"] - if stop_name == conn['station']: - route_lines.append(f"• **{stop_name}**") - else: - route_lines.append(f"• {stop_name}") - - embed.add_field(name="Route", value="\n".join(route_lines)) - - footer = build_embed_footer(operator_infos.get("slogan")) - embed.set_footer(text=footer["text"], icon_url=footer["icon"]) - - return embed - -def build_announcement_embed(msg): - current_operator = resolve_operator(handlers.train_info["operators"]) - operator_infos = operator_metadata(current_operator) - - embed = discord.Embed( - title = "Informationen zu Ihrer Fahrt", - description=msg, - color=operator_infos["color"] - ) - - embed.set_author(name=current_operator) - embed.set_thumbnail(url=operator_infos["logo"]) - - footer = build_embed_footer(operator_infos.get("slogan")) - embed.set_footer(text=footer["text"], icon_url=footer["icon"]) - - return embed - -def build_error_embed(errormsg) -> discord.Embed: - embed = discord.Embed( - title="Ein Fehler ist aufgetreten!", - description=errormsg, - color=discord.Colour.red() - ) - - return embed \ No newline at end of file diff --git a/src/handlers.py b/src/handlers.py deleted file mode 100644 index 57a058e..0000000 --- a/src/handlers.py +++ /dev/null @@ -1,154 +0,0 @@ -import discord -from datetime import datetime, timedelta -import asyncio -import random -from pathlib import Path - -from src.config import config -from src.utils import random_connection, get_train_info, get_channel_formatting, logger -from src.embeds import build_announcement_embed, build_info_embed - -current = None -_scheduled_task: asyncio.Task | None = None - -async def announcer(bot, announcement): - voice_channel = bot.get_channel(config["vc"]) - if len(voice_channel.members) > 0: - match announcement: - case "ende": - destination = current['destination'] - embed = build_announcement_embed( - f'Sehr geehrte Fahrgäste,\nIn wenigen Minuten erreichen wir {destination}. Dieser Zug endet dort.\n\nWir wünschen Ihnen eine angenehme Weiterreise.\n\nVielen Dank für Ihr Vertrauen und auf Wiedersehen.') - if config["voice_announcements"][0]["enabled"]: - await voice_announcer(bot, destination, voice_channel) - case "umstieg": - embed = build_info_embed() - case _: - embed = None - if embed is None: - logger(f"Unbekanntes Announcement: {announcement}") - else: - await voice_channel.send(embed=embed) - else: - logger(f"Announcement {announcement} wird geskipped, keiner da") - return - -async def voice_announcer(bot: discord.Bot, destination, voice_channel): - voice_announcement_config = config["voice_announcements"][0] - voice_stations = voice_announcement_config["stations"] - - if destination in voice_stations: - announcement_for = destination - else: - if voice_stations.get("general", "") == "": - return - announcement_for = "general" - - if voice_stations.values() == list: - sound_file = random.choice(voice_stations.get(announcement_for)) - else: - sound_file = voice_stations.get(announcement_for) - - sound_path = f"src/data/announcements/{sound_file}" - if Path(sound_path).is_file() is False: - logger(f"Konnte Datei {sound_path} nicht finden", "error") - return - - logger(f"VC wird betreten, spiele {sound_path}") - vc = await voice_channel.connect(timeout=15, reconnect=True) - audio_source = discord.FFmpegPCMAudio(sound_path) - - if not vc.is_playing(): - def after_playing(error): - if error: - logger(f"Player error: {error}", "error") - bot.loop.create_task(vc.disconnect()) - logger("VC wird verlassen") - - vc.play(audio_source, after=after_playing) - -async def rename_vc(bot: discord.Bot, from_scheduler: bool = False): - global current, train_name, train_info, train_type, _scheduled_task - - guild = bot.get_guild(int(config["server"])) - if guild is None: - logger(f"Es konnte kein Server mit der ID {config['server']} gefunden werden! Ist der Bot ein Member?", "fatal") - return False - - channel = guild.get_channel(int(config["vc"])) - if not isinstance(channel, discord.VoiceChannel): - logger(f"Es konnte kein VC mit der ID {config['vc']} auf dem Server gefunden werden", "fatal") - return False - - if not from_scheduler and _scheduled_task and not _scheduled_task.done(): - _scheduled_task.cancel() - - attempt = 0 - while True: - if attempt == 20: - logger("Zu viele Fehlversuche. Füge einen anderen Bahnhof hinzu.", "fatal") - return "Es konnte kein Zug gefunden werden." - - attempt += 1 - current = random_connection() - if current == None: - return None - - parts = current['train'].split() - train_type = parts[0] - - if parts[1].isdigit(): - train = current['train'] - train_ID = parts[1] - else: - train = parts[1] - train_ID = current['train_number'] - - station = current['station'] - train_info = get_train_info(station=station, train_ID=train_ID, train_type=train_type) - if train_info and train_info.get('operators') and train_info.get('arrival'): - break - - logger(f"Versuch {attempt}: Fehler bei {current['train']} von {station}, versuche neue Verbindung...") - - train_name = f"{train} nach {current['destination']} von {current['station']}" - logger(f"Vorbereitung auf {train_name} (typ: {train_type})") - arrival = datetime.fromisoformat((train_info["arrival"])) - formatting = get_channel_formatting(train_type) - - - print("-----------------------------------------") - logger(f"Umstieg: {train_name}") - logger(f"Betreiber: {''.join(train_info['operators'])}") - logger(f"Train-Type: {train_type}") - logger(f"Wenn der Name nicht geändert wird bin ich im Cooldown") - await channel.edit(name=f"{formatting}{train_name}") - await channel.set_status(f"Ankunft um {arrival.strftime('%H:%M')}") - logger(f"Name geändert!") - - if config.get("announcements", True): - await announcer(bot, "umstieg") - - _scheduled_task = asyncio.create_task(_schedule_next_umstieg(bot, arrival)) - - return True - -async def _schedule_next_umstieg(bot, arrival): - announcement = config.get("announcements", True) - announcement_countdown = random.randrange(180, 300) # letzte station announcement ist meistens 3-5min vor ankunft - wait_seconds = (arrival - datetime.now()).total_seconds() - if wait_seconds > 0: - remaining = str(timedelta(seconds=wait_seconds)) - logger(f"Nächster Umstieg in {remaining.split('.')[0]} ({arrival.strftime('%H:%M:%S')} Uhr)") - - if announcement and wait_seconds > announcement_countdown: - wait_until_end_announcement = wait_seconds - announcement_countdown - await asyncio.sleep(wait_until_end_announcement) - await announcer(bot, "ende") - await asyncio.sleep(announcement_countdown) - - else: - await asyncio.sleep(wait_seconds) - - logger("Zug angekommen, wähle neue Verbindung...") - await rename_vc(bot, from_scheduler=True) \ No newline at end of file diff --git a/src/utils.py b/src/utils.py index 13f2832..8c82ed4 100644 --- a/src/utils.py +++ b/src/utils.py @@ -1,6 +1,6 @@ import json import os -from datetime import datetime +from datetime import datetime, timedelta import src.data.operators as operators from src.data.emojis import emoji_list @@ -17,8 +17,11 @@ def logger(msg, log_type="info") -> str: os._exit(1) def convert_iso_string(isostring) -> str: - datetime_isostring = datetime.fromisoformat(isostring) - return datetime_isostring.strftime('%H:%M') + dt = datetime.fromisoformat(isostring.replace('Z', '+00:00')) + if dt.second >= 30: + dt += timedelta(minutes=1) + + return dt.strftime('%H:%M') def channel_formatting(mode: str) -> str: formatting = config.get("formatting", "") diff --git a/wtf.json b/wtf.json new file mode 100644 index 0000000..089f239 --- /dev/null +++ b/wtf.json @@ -0,0 +1,827 @@ +{ + "duration": 3240, + "startTime": "2026-08-15T20:16:00Z", + "endTime": "2026-08-15T21:10:00Z", + "transfers": 0, + "id": "CqEBCgIxMhIiMjAyNjA4MTVfMjI6MTZfZGUtREVMRklfMzMwNTA4MDkwNhofZGUtREVMRklfZGU6MDIwMDA6MTAwNTY6OjEwMDAxOSH0ona/CsZKQClzRSkhWB0kQDIfZGUtREVMRklfZGU6MDIwMDA6ODAwMDU6OjgwMDA1NDk1e6AVGMZKQEGc/1cdObojQEiAkoPUBlCwqoPUBloDQlVTYAE=", + "legs": [ + { + "mode": "BUS", + "from": { + "name": "Osterbrookplatz", + "stopId": "de-DELFI_de:02000:10056::100019", + "importance": 0.00038864786620251834, + "lat": 53.547202999999996, + "lon": 10.057312999999999, + "level": 0.0, + "tz": "Europe/Berlin", + "departure": "2026-08-15T20:16:00Z", + "scheduledDeparture": "2026-08-15T20:16:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 100017", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + "to": { + "name": "Teufelsbr\u00fcck (F\u00e4hre)", + "stopId": "de-DELFI_de:02000:80005::800054", + "importance": 0.001101788366213441, + "lat": 53.54761, + "lon": 9.863717000000001, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:10:00Z", + "scheduledArrival": "2026-08-15T21:08:00Z", + "scheduledTrack": "5", + "track": "5", + "description": "VHH 800019", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + "duration": 3240, + "startTime": "2026-08-15T20:16:00Z", + "endTime": "2026-08-15T21:10:00Z", + "scheduledStartTime": "2026-08-15T20:16:00Z", + "scheduledEndTime": "2026-08-15T21:08:00Z", + "realTime": true, + "scheduled": true, + "interlineWithPreviousLeg": false, + "headsign": "Teufelsbr\u00fcck (F\u00e4hre)", + "tripFrom": { + "name": "Osterbrookplatz", + "stopId": "de-DELFI_de:02000:10056::100019", + "importance": 0.00038864786620251834, + "lat": 53.547202999999996, + "lon": 10.057312999999999, + "level": 0.0, + "tz": "Europe/Berlin", + "departure": "2026-08-15T20:16:00Z", + "scheduledDeparture": "2026-08-15T20:16:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 100017", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + "tripTo": { + "name": "Teufelsbr\u00fcck (F\u00e4hre)", + "stopId": "de-DELFI_de:02000:80005::800054", + "importance": 0.001101788366213441, + "lat": 53.54761, + "lon": 9.863717000000001, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:10:00Z", + "scheduledArrival": "2026-08-15T21:08:00Z", + "scheduledTrack": "5", + "track": "5", + "description": "VHH 800019", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + "routeId": "de-DELFI_de:hvv:12:_3", + "routeUrl": "", + "directionId": "1", + "routeColor": "ca3d2a", + "routeTextColor": "ffffff", + "routeType": 3, + "agencyName": "Hochbahn Bus", + "agencyUrl": "", + "agencyFareUrl": "", + "agencyId": "15140", + "tripId": "20260815_22:16_de-DELFI_3305080906", + "routeShortName": "12", + "routeLongName": "", + "tripShortName": "1200086", + "displayName": "12", + "cancelled": false, + "source": "de_DELFI.gtfs.zip/stop_times.txt:33330623:33330654", + "intermediateStops": [ + { + "name": "Schadesweg", + "stopId": "de-DELFI_de:02000:10042::100197", + "importance": 0.00038864786620251834, + "lat": 53.547714, + "lon": 10.05187, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:17:00Z", + "departure": "2026-08-15T20:17:00Z", + "scheduledArrival": "2026-08-15T20:17:00Z", + "scheduledDeparture": "2026-08-15T20:17:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 100196", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Luisenweg", + "stopId": "de-DELFI_de:02000:10041::100224", + "importance": 0.00038864786620251834, + "lat": 53.548298, + "lon": 10.046362999999998, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:18:00Z", + "departure": "2026-08-15T20:18:00Z", + "scheduledArrival": "2026-08-15T20:18:00Z", + "scheduledDeparture": "2026-08-15T20:18:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 100195", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Ausschl\u00e4ger Weg (LBV)", + "stopId": "de-DELFI_de:02000:10040::100181", + "importance": 0.001163465902209282, + "lat": 53.547276, + "lon": 10.037443, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:22:00Z", + "departure": "2026-08-15T20:22:00Z", + "scheduledArrival": "2026-08-15T20:20:00Z", + "scheduledDeparture": "2026-08-15T20:20:00Z", + "scheduledTrack": "1", + "track": "1", + "description": "HHA-B 100181", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "S\u00fcderstra\u00dfe", + "stopId": "de-DELFI_de:02000:10039::100172", + "importance": 0.0010683928849175572, + "lat": 53.545147, + "lon": 10.029789000000001, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:22:00Z", + "departure": "2026-08-15T20:22:00Z", + "scheduledArrival": "2026-08-15T20:21:00Z", + "scheduledDeparture": "2026-08-15T20:21:00Z", + "scheduledTrack": "3", + "track": "3", + "description": "HHA-B 100164", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "S Hammerbrook (S\u00fcd)", + "stopId": "de-DELFI_de:02000:10038::100207", + "importance": 0.0006706778658553958, + "lat": 53.54553200000001, + "lon": 10.024811999999999, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:24:00Z", + "departure": "2026-08-15T20:24:00Z", + "scheduledArrival": "2026-08-15T20:22:00Z", + "scheduledDeparture": "2026-08-15T20:22:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "SBAHNS 3921901", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "S Hammerbrook (Nord)", + "stopId": "de-DELFI_de:02000:10060::100030", + "importance": 0.00039489471237175167, + "lat": 53.547832, + "lon": 10.02298, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:25:00Z", + "departure": "2026-08-15T20:25:00Z", + "scheduledArrival": "2026-08-15T20:23:00Z", + "scheduledDeparture": "2026-08-15T20:23:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 100029", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Albertstra\u00dfe", + "stopId": "de-DELFI_000011005802", + "importance": 0.00039489471237175167, + "lat": 53.54898, + "lon": 10.019054, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:26:00Z", + "departure": "2026-08-15T20:26:00Z", + "scheduledArrival": "2026-08-15T20:24:00Z", + "scheduledDeparture": "2026-08-15T20:24:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 100026", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Repsoldstra\u00dfe", + "stopId": "de-DELFI_000011005702", + "importance": 0.00039489471237175167, + "lat": 53.54862, + "lon": 10.013898, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:28:00Z", + "departure": "2026-08-15T20:28:00Z", + "scheduledArrival": "2026-08-15T20:26:00Z", + "scheduledDeparture": "2026-08-15T20:26:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 100020", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "U Steinstra\u00dfe (Deichtorplatz)", + "stopId": "de-DELFI_de:02000:10024::100050", + "importance": 0.0013284930028021336, + "lat": 53.548954, + "lon": 10.006442000000002, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:30:00Z", + "departure": "2026-08-15T20:30:00Z", + "scheduledArrival": "2026-08-15T20:28:00Z", + "scheduledDeparture": "2026-08-15T20:28:00Z", + "scheduledTrack": "3", + "track": "3", + "description": "HHA-B 100033", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "HBF/Spitalerstra\u00dfe", + "stopId": "de-DELFI_de:02000:10011::100133", + "importance": 0.2917599081993103, + "lat": 53.552642999999996, + "lon": 10.005606000000002, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:32:00Z", + "departure": "2026-08-15T20:32:00Z", + "scheduledArrival": "2026-08-15T20:30:00Z", + "scheduledDeparture": "2026-08-15T20:30:00Z", + "scheduledTrack": "1", + "track": "1", + "description": "HHA-B 100133", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "HIGHSPEED_RAIL", + "LONG_DISTANCE", + "COACH", + "NIGHT_RAIL", + "REGIONAL_RAIL", + "SUBURBAN", + "SUBWAY", + "BUS" + ] + }, + { + "name": "Kunsthalle", + "stopId": "de-DELFI_000011103302", + "importance": 0.0003962125920224935, + "lat": 53.555546, + "lon": 10.001356999999999, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:35:00Z", + "departure": "2026-08-15T20:35:00Z", + "scheduledArrival": "2026-08-15T20:33:00Z", + "scheduledDeparture": "2026-08-15T20:33:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 100213", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "U Stephansplatz", + "stopId": "de-DELFI_de:02000:11013:1:110071", + "importance": 0.0023285001516342163, + "lat": 53.558370000000004, + "lon": 9.98852, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:38:00Z", + "departure": "2026-08-15T20:38:00Z", + "scheduledArrival": "2026-08-15T20:36:00Z", + "scheduledDeparture": "2026-08-15T20:36:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 110071", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Johannes-Brahms-Platz", + "stopId": "de-DELFI_de:02000:11009:1:110056", + "importance": 0.0004172989574726671, + "lat": 53.555110000000006, + "lon": 9.978620999999999, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:40:00Z", + "departure": "2026-08-15T20:40:00Z", + "scheduledArrival": "2026-08-15T20:38:00Z", + "scheduledDeparture": "2026-08-15T20:38:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 110056", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Handwerkskammer", + "stopId": "de-DELFI_de:02000:11008::110054", + "importance": 0.0004172989574726671, + "lat": 53.55314, + "lon": 9.976393, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:41:00Z", + "departure": "2026-08-15T20:41:00Z", + "scheduledArrival": "2026-08-15T20:39:00Z", + "scheduledDeparture": "2026-08-15T20:39:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 110053", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Museum f\u00fcr Hamburgische Geschichte", + "stopId": "de-DELFI_de:02000:11007::110052", + "importance": 0.0004172989574726671, + "lat": 53.55146799999999, + "lon": 9.974794000000001, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:42:00Z", + "departure": "2026-08-15T20:42:00Z", + "scheduledArrival": "2026-08-15T20:40:00Z", + "scheduledDeparture": "2026-08-15T20:40:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 110051", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "U St. Pauli", + "stopId": "de-DELFI_de:02000:80017:3:800037", + "importance": 0.0012117273872718215, + "lat": 53.550117, + "lon": 9.969799000000002, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:45:00Z", + "departure": "2026-08-15T20:45:00Z", + "scheduledArrival": "2026-08-15T20:43:00Z", + "scheduledDeparture": "2026-08-15T20:43:00Z", + "scheduledTrack": "3", + "track": "3", + "description": "HHA-B 800037", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Davidstra\u00dfe", + "stopId": "de-DELFI_de:02000:80018:2:800039", + "importance": 0.001252002315595746, + "lat": 53.549583, + "lon": 9.961643, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:48:00Z", + "departure": "2026-08-15T20:48:00Z", + "scheduledArrival": "2026-08-15T20:46:00Z", + "scheduledDeparture": "2026-08-15T20:46:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 800033", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "S Reeperbahn", + "stopId": "de-DELFI_de:02000:80019:2:800041", + "importance": 0.0014315921580418944, + "lat": 53.549746999999996, + "lon": 9.954717, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:52:00Z", + "departure": "2026-08-15T20:52:00Z", + "scheduledArrival": "2026-08-15T20:50:00Z", + "scheduledDeparture": "2026-08-15T20:50:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 800031", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Bl\u00fccherstra\u00dfe", + "stopId": "de-DELFI_de:02000:80107::800356", + "importance": 0.0009377760579809546, + "lat": 53.549186999999996, + "lon": 9.949956, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:53:00Z", + "departure": "2026-08-15T20:53:00Z", + "scheduledArrival": "2026-08-15T20:51:00Z", + "scheduledDeparture": "2026-08-15T20:51:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 800356", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Gro\u00dfe Bergstra\u00dfe", + "stopId": "de-DELFI_de:02000:80023:2:800323", + "importance": 0.0009272329043596983, + "lat": 53.551525, + "lon": 9.946254999999999, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:55:00Z", + "departure": "2026-08-15T20:55:00Z", + "scheduledArrival": "2026-08-15T20:53:00Z", + "scheduledDeparture": "2026-08-15T20:53:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 800323", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Goethestra\u00dfe", + "stopId": "de-DELFI_de:02000:80024::800082", + "importance": 0.0004593530611600727, + "lat": 53.55199399999999, + "lon": 9.940056, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:56:00Z", + "departure": "2026-08-15T20:56:00Z", + "scheduledArrival": "2026-08-15T20:54:00Z", + "scheduledDeparture": "2026-08-15T20:54:00Z", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Bf. Altona", + "stopId": "de-DELFI_de:02000:80026::800091", + "importance": 0.00639944477006793, + "lat": 53.551548, + "lon": 9.934171999999998, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:58:00Z", + "departure": "2026-08-15T20:58:00Z", + "scheduledArrival": "2026-08-15T20:56:00Z", + "scheduledDeparture": "2026-08-15T20:56:00Z", + "scheduledTrack": "5", + "track": "5", + "description": "VHH 800096", + "vertexType": "TRANSIT", + "pickupType": "NOT_ALLOWED", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Bf. Altona", + "stopId": "de-DELFI_de:02000:80026::800094", + "importance": 0.00639944477006793, + "lat": 53.55177, + "lon": 9.934406000000001, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T20:59:00Z", + "departure": "2026-08-15T20:59:00Z", + "scheduledArrival": "2026-08-15T20:57:00Z", + "scheduledDeparture": "2026-08-15T20:57:00Z", + "scheduledTrack": "8", + "track": "8", + "description": "Z 69275700", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NOT_ALLOWED", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Rathaus Altona", + "stopId": "de-DELFI_de:02000:80015::800044", + "importance": 0.0019747898913919926, + "lat": 53.547134, + "lon": 9.936131, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:01:00Z", + "departure": "2026-08-15T21:01:00Z", + "scheduledArrival": "2026-08-15T20:59:00Z", + "scheduledDeparture": "2026-08-15T20:59:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 800205", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Susettestra\u00dfe", + "stopId": "de-DELFI_de:02000:80098::800329", + "importance": 0.00032002496300265193, + "lat": 53.546482, + "lon": 9.924938, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:03:00Z", + "departure": "2026-08-15T21:03:00Z", + "scheduledArrival": "2026-08-15T21:01:00Z", + "scheduledDeparture": "2026-08-15T21:01:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 800328", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Hohenzollernring (S\u00fcd)", + "stopId": "de-DELFI_de:02000:80010::800047", + "importance": 0.00032002496300265193, + "lat": 53.546079999999996, + "lon": 9.915613, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:04:00Z", + "departure": "2026-08-15T21:04:00Z", + "scheduledArrival": "2026-08-15T21:02:00Z", + "scheduledDeparture": "2026-08-15T21:02:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 800026", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Liebermannstra\u00dfe", + "stopId": "de-DELFI_de:02000:80009::800048", + "importance": 0.00032002496300265193, + "lat": 53.54596, + "lon": 9.899587000000002, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:06:00Z", + "departure": "2026-08-15T21:06:00Z", + "scheduledArrival": "2026-08-15T21:04:00Z", + "scheduledDeparture": "2026-08-15T21:04:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "HHA-B 800025", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Halbmondsweg", + "stopId": "de-DELFI_de:02000:80008::800050", + "importance": 0.0005810476723127067, + "lat": 53.546510000000005, + "lon": 9.887433000000001, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:07:00Z", + "departure": "2026-08-15T21:07:00Z", + "scheduledArrival": "2026-08-15T21:05:00Z", + "scheduledDeparture": "2026-08-15T21:05:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 800023", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Zedernweg", + "stopId": "de-DELFI_de:02000:80007::800051", + "importance": 0.0005810476723127067, + "lat": 53.54658, + "lon": 9.88216, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:08:00Z", + "departure": "2026-08-15T21:08:00Z", + "scheduledArrival": "2026-08-15T21:06:00Z", + "scheduledDeparture": "2026-08-15T21:06:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 800022", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + }, + { + "name": "Holztwiete", + "stopId": "de-DELFI_de:02000:89006::800110", + "importance": 0.0005810476723127067, + "lat": 53.547146, + "lon": 9.874039000000002, + "level": 0.0, + "tz": "Europe/Berlin", + "arrival": "2026-08-15T21:09:00Z", + "departure": "2026-08-15T21:09:00Z", + "scheduledArrival": "2026-08-15T21:07:00Z", + "scheduledDeparture": "2026-08-15T21:07:00Z", + "scheduledTrack": "2", + "track": "2", + "description": "VHH 800021", + "vertexType": "TRANSIT", + "pickupType": "NORMAL", + "dropoffType": "NORMAL", + "cancelled": false, + "modes": [ + "BUS" + ] + } + ], + "legGeometry": { + "points": "cfgceBsczdREx@q@tN{@|R_Dbt@q@xNgB~^e@nIM`B_@`DWlDMzBIxCBpCF`E@vEMpFsAtVsCfi@}@jOYpFaDnk@_@zGANCp@WtEa@pH]tJ]xKuArH[rEcA^{@`Ai@zAUjBApBPjBd@|At@dAIhF\\nGeG`iAuBzb@kJxmBUtEeAdTAPq@bOYfGWxHQdFMdG[~RYlZGvRCvFmAhG?tE?rBJfHJ|H\\pPRlNpBhNJpN\\~LNpDf@rL|@hOn@|KRjCh@lHxCp]PnB~Cx[TbClBtQPdBp@zF~@pHdB|LdDnUlAtIpIrh@lJxi@xBlMDZxArIjEdWrB|L|BbMpWjxAxEbWzKxl@|EvWdJxf@dC`N|Gx^bLtm@jAtNdClMj@zCtAfH~BxL~AjIhGz\\PdAlCxNpFvSfA|Fv@fEjCrNrCjOpB~Kf@jC|E~WhA`GtAfHzAlIbDbQfAxIfDrXVtBtA`LjAbGvAtD_BdE}AvCu@fAuAzAsLjIeBlAkYhSsN`KuF|DqA~@wB~AmDjCwG|EgIpFqBxAc_@xWuHnF}a@bZ_DzBqN`KeD~BiHhFgAv@cExCu@j@sHnF_R|MuWtRmGpEgChBwVnQmG`FyBfBaGtEza@d~BrEbW|C~PxFn[pAnG_ObKkChBaFjD}F~DuIbGaBhAuDfClBpKjFhZ`C|M|Kpn@rI|e@bKvk@~@hF^tB~F`\\t@zD\\vBh@zCnC~NtJnh@RlApDtRt@dF~@jHd@dGPvGDhJDfHLbINzHPvHr@jr@BfQGrYM|POfV]fg@Y|a@OxSGdJIvTCdBMzDSnEWxD{@bJsGrCgLImG_DsGcA}HuAkBYeEq@cT_EwLsAqJa@cK@_BHoDf@{Er@u@HkEp@_JzB{Br@aOnEuOxEoJ~CmNrEcA\\w@RmGrAQFuFnAaGvAwPlE_IrCsE~BeAh@_Az@iB~A}EzEyBlBwIfK}KhNsHhKmEpFeV`[qA`BuE|GoDlFcG~IiEdHkCdFk`@~v@qFlMqBvT}KtVgIhQgEdJw@nB{@tB{@vBgCfGsDjJuBbGeIbTkFjPkCtI{DxNyDz[a@`CeAdFeGx[sIhe@gM`s@{D|VcA`GwIxm@{Fvf@m@rFqEla@wCbV[pCaB|MeAxIi@pFi@dGm@xHkIffA}Cpa@sIhmAqB|X_@dFGl@K`BWtJ?hEGlJ?pD?pGZtOLrFt@tWlAz\\nF|dBdBhe@~@tQl@~I|@rJ^pC~A~LlB`M^~BbAlFrCzMxBbLtCtNlEzR`CdKdCtJvB~H`CrHrDxKbEnLvFzNjHhPzk@joApItPlAbCbBrD|AbDdGjIn@dAlDhFvAfB`@f@|GvH~LfJNJfNhK|aAvqApXp]nA|A|JjL~@fAtBbCbM|KxF`FvEtEhGdGp@p@jQfQlWdW|OhNtAjAhJbInYpUpUzPv@f@pGjExIfLpDlHvAvB~BfDdBxAfEn@xE|CdBgOdAmJjAiO~@iLNiBr@{Lh@oLJ_CTuQxCotAtAow@H_LBqDPmPLaIJ{GNsCj@mXFiB_@mQbB{SfD__@|Kq_AvL}aAzJux@zDgLTkBtA_KjAgJDe@nCkUvCwTdBsMlCqQnF__@nCgP^_TzBsPXkCPmDLyDA_MQgCWmAyAdO_AvIiJ~y@{Fj`@cClRmF~b@iC~SUlBgArIsAtKSlB@rO{Jtx@wL|aA}Kp_AgD~^cBzSyDnOoAtQ_@`KKpDKxHGxHOjO?vBIjUE|I_@p`@c@|Rm@|T}Ab^k@zKq@`MQlBcClVsEx_@_@fDeAhKoAjOgBvY{AfU_A`KmAvKm@nFwAnLmAx[KzCKhDE`DMrHA~B^ba@TfYFfF?\\HxILxJJ~JVlWN|Sp@fi@^xRNlHr@zSFbB~JdwBHhBd@rLzKnbCHdBl@fL^fLf@``@@x@?fA@nN]vm@KtQEzLChFCnCChIIdJEnHKbMA~AuAbb@E|@KnDY~G{Cnk@{Ah^QtHS`Is@lYCja@?vALbFXrMTxHp@d\\HlCPbHTdJPpGtBpd@DhBHdCt@bZ\\rM`@fL`AvRjAtR`If~@~AvPPfBT|BxEvg@B`@|AtPMdMPfF\\dEn@rIrB`VtAxPs@jDcA|E{@nDmN`o@aCbCsAr@uA`@sBHgJr@qEM{SL}K`@gZ`@wC_A_Nr@u@Ce@GYI]O]YOg@Sc@U_@W[[S[M[I]?[B]JYPYTUXQ\\Q`@Md@Kf@Gf@Ej@Ch@?j@@T@TDh@Hf@?v@Cn@Cr@Ej@MjBwDzf@{BdZKjAY~D}Ftx@WhEeCn`@s@lOAV_@~PUdTIxJOnRi@vy@SbVu@zh@o@`Ug@pFUjCe@lGi@rHc@d@m@r@R~AHpBCbC_JtnAcCbZ}Cj_@a@zEcAtG_AdG`F|G~AjBjBjBpD~CbDbCnIzE|A~@nPvJ_@|EGxB@`b@P|IZhFZ|DJrDGhSAxCEfb@?r@Qlb@E|NO|@Sp@Y^a@RaGOm@[Si@Oi@I{@DkU?yIB{GHqT?gBJkXBiNHsB`@{Ax@gBvGeMb@eAhBoFPe_@DoAp@_HdRfFrGpArKdA|Rh@vA?jGK`EGtEI|dBM~WD`C?n@?~IXdEJfJL~J?jJ@r@?t\\@zCZ|ARfAb@dAbAx@tAp@hBl@zC`@|L?nTCjIaAjXYpJNpOuF~sAsAn\\k@xUCzIA~LBpHFzEJ|FPfFj@|K`@lHxFhhANrCl@`MNpGEnHOzFwBze@o@tPYtLChIF~OJdHX~NNhHNxYK`Q@nDIhOA~F?hC@pCT`^BzC^lWhCn^pAhRvBtXtA~Tj@bPf@xQV`Rf@nd@b@xOfA~TNzCPfCt@~KdCbXx@lKn@vKl@dPf@dTF`NHpNQ|WAz@KjIYtLSjGYtIe@dMe@rLOjEq@jTg@jT[dQWjNAdACxBmArr@o@x]E|DQtL?lNAvBMzj@KjbAHvKJxFRxIh@nPfAdXTpFdAhWv@nRFjAFpBJdCv@dSlAhYp@tP^vKp@~[n@tXh@hPj@nOnAnU`AfPnCrc@|@vOhBbZdAlPPpCn@bKl@lL^pKRfJD~M?rJEvIM`MEnDKhJCl@QfJc@dJ_@dF]|Da@bEc@tDk@jE}AzJ}D~UyA|Ky@rH_@`G_A`No@zMIdBItBm@dN{Ab_@]|HE|@IhBK|Bq@dOQtDy@tQMzDk@nNu@`Ra@zJkB~X_D|j@m@jK{Bte@UhFm@zL{ElfAmG`uAGxAmApXm@dSKjJ_@v`@@pQDdGGt\\F~JR`PV`LN`Dl@lMt@fK`BxOvCdVVxGFlGBzFYpk@Q|LGhEYfRMbIKhIa@zc@o@lu@EnDO|QQ`RmAhwACtKEnO\\rPRlK@Xx@zT^~JT`O@~@D|KQxMWhIS`F]pGO|A]|Dk@lGa@vEc@|Ee@lI[lGa@t[i@x]InG_Alr@QrKSfQI~XOrb@GnIOxH[xHm@bIi@zFy@lHyB~OiAzIo@xE}AdM_AzIq@tHc@tGQxCk@bKq@vKWxEGlAGfA{@~Oc@fJeAfWiAj^sBlv@}Adl@QlI[|G_@bFy@vHcB|KkHhb@uB~NgBnOcBbScDza@M`BIlAe@hIi@bNUhKMnMB|NNfOXvN`@bNX`H\\hHVjEVlDlAvLnDn[^lDj@bFh@jEdAhInB|MfBjL", + "precision": 6, + "length": 972 + }, + "bikesAllowed": false, + "wheelchairAccessible": "NOT_ACCESSIBLE", + "reservation": "NONE", + "alternatives": [] + } + ] +} \ No newline at end of file