/** * @type {string | null} */ var fromStation = null /** * @type {string | null} */ var toStation = null /** * @type {Date | null} */ var departureDate = null /** * @type {Date | null} */ var arrivalDate = null var transitKind = { ice: true, ic: true, re: true, rb: true, s: true, bus: true, ferry: true, u: true, tram: true, } var itineraries = null var focusedElement = null /** * @typedef DbJourney * @prop {'journey'} type * @prop {(DbTrip & DbArrDep & {tripId: string})[]} legs * @prop {string} refreshToken * @prop {DbRemark[]} remarks */ /** * @param {{journeys: DbJourney[]}} data */ function onItineraries(data) { var contentDiv = document.getElementById('content-div') if (!contentDiv) { contentDiv = document.createElement('div') document.body.insertBefore(contentDiv, document.querySelector('footer')) contentDiv.classList.add('content') contentDiv.id = 'content-div' } while (contentDiv.childNodes.length > 0) { contentDiv.childNodes[contentDiv.childNodes.length - 1].remove() } for (var i = 0; i < data.journeys.length; i++) { var itineraryDiv = document.createElement('div') contentDiv.appendChild(itineraryDiv) var heading = document.createElement('h4') itineraryDiv.appendChild(heading) heading.textContent = `Itinerary ${i + 1}` var trainsDiv = document.createElement('div') itineraryDiv.appendChild(trainsDiv) trainsDiv.classList.add('itinerary-trains') data.journeys[i].legs.forEach(function (train, idx) { var last = idx === data.journeys[i].legs.length - 1 var trainDiv = document.createElement('div') trainsDiv.appendChild(trainDiv) trainDiv.classList.add('itinerary-train') if (idx === 0) { var departureTimeP = document.createElement('p') trainDiv.appendChild(departureTimeP) departureTimeP.classList.add('sec', 'departure', 'time') var departureTimePre = document.createElement('pre') departureTimeP.appendChild(departureTimePre) var departure = new Date(train.plannedDeparture) departureTimePre.textContent = departure.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) if (train.departureDelay) { departureTimePre.classList.add('original') var departureDelayPre = document.createElement('pre') departureTimeP.append(departureDelayPre) departureDelayPre.append(train.departureDelay > 0 ? '+' : '-', Math.floor(Math.abs(train.departureDelay) / 60).toString()) departureDelayPre.classList.add('delay', train.departureDelay > 0 ? 'late' : 'early') var actualDeparturePre = document.createElement('pre') departureTimeP.append(actualDeparturePre) actualDeparturePre.textContent = new Date(train.departure).toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) actualDeparturePre.classList.add('actual-time', train.departureDelay > 0 ? 'late' : 'early') } var departureHeading = document.createElement('h3') trainDiv.appendChild(departureHeading) departureHeading.classList.add('departure', 'station') if (train.origin.type === 'stop' || train.origin.type === 'station') { var departureLink = document.createElement('a') departureHeading.appendChild(departureLink) departureLink.textContent = train.origin.name departureLink.classList.add('no-custom-a', 'items') var departureUrl = new URL('/view-station.html', window.location.origin) departureUrl.searchParams.set('stationId', train.origin.id) departureLink.href = departureUrl.toString() } else { var departureSpan = document.createElement('span') departureHeading.append(departureSpan) departureSpan.innerText = train.origin.name || train.origin.address } if (train.departurePlatform || train.plannedDeparturePlatform) { var departurePlatformP = document.createElement('p') trainDiv.append(departurePlatformP) departurePlatformP.classList.add('sec', 'departure', 'platform') if (train.departurePlatform && train.departurePlatform != train.plannedDeparturePlatform) { departurePlatformP.classList.add('changed') } departurePlatformP.textContent = `${train.departurePlatform || train.plannedDeparturePlatform}` } } var trainP = document.createElement('p') trainDiv.appendChild(trainP) trainP.classList.add('pri', 'train') if (!train.walking) { var trainLink = document.createElement('a') trainP.appendChild(trainLink) trainLink.innerText = train.line.name trainLink.classList.add('no-custom-a', 'items') if (train.line.product) { if (train.line.productName === 'STB' && train.line.name.startsWith('STB U')) { train.line.product = 'subway' } if (train.line.adminCode === 'vvs020') { // Stuttgart Stadtbahn trainLink.innerText = train.line.name.slice(4) } else if (train.line.adminCode === '800643') { // Stuttgart S-Bahn trainLink.innerText = 'S' + train.line.name.slice(2) } else if (train.line.adminCode === 'kvv021') { // Karlsruhe Tram trainLink.innerText = train.line.name.slice(4) } trainLink.classList.add('product-' + train.line.product) trainLink.classList.add('product-id-' + train.line.id) trainLink.classList.add('product-adminCode-' + train.line.adminCode) if (train.line.operator) { trainLink.classList.add('product-operator-' + train.line.operator.id) } } var trainUrl = new URL('/view-train.html', window.location.origin) trainUrl.searchParams.set('tripId', train.tripId) trainUrl.searchParams.set('startId', train.origin.id) trainUrl.searchParams.set('stopId', train.destination.id) trainLink.href = trainUrl.toString() trainP.appendChild(document.createTextNode(' ')) if (train.line.operator) { var trainCompany = document.createElement('span') trainP.appendChild(trainCompany) trainCompany.textContent = '(' + train.line.operator.name + ')' trainCompany.classList.add('company') } } else { var walkingSpan = document.createElement('span') trainP.append(walkingSpan) walkingSpan.classList.add('walking') walkingSpan.innerText = `Walking (${train.distance} m)` } var arrivalTimeP = document.createElement('p') trainDiv.appendChild(arrivalTimeP) arrivalTimeP.classList.add('sec', 'arrival', 'time') var arrivalTimePre = document.createElement('pre') arrivalTimeP.appendChild(arrivalTimePre) var arrival = new Date(train.plannedArrival) arrivalTimePre.textContent = arrival.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) if (train.arrivalDelay) { arrivalTimePre.classList.add('original') var arrivalDelayPre = document.createElement('pre') arrivalTimeP.append(arrivalDelayPre) arrivalDelayPre.append(train.arrivalDelay > 0 ? '+' : '-', Math.floor(Math.abs(train.arrivalDelay) / 60).toString()) arrivalDelayPre.classList.add('delay', train.arrivalDelay > 0 ? 'late' : 'early') var actualArrivalPre = document.createElement('pre') arrivalTimeP.append(actualArrivalPre) actualArrivalPre.textContent = new Date(train.arrival).toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) actualArrivalPre.classList.add('actual-time', train.arrivalDelay > 0 ? 'late' : 'early') } var arrivalHeading = document.createElement('h3') trainDiv.appendChild(arrivalHeading) arrivalHeading.classList.add('arrival', 'station') if (train.destination.type === 'stop' || train.destination.type === 'station') { var arrivalLink = document.createElement('a') arrivalHeading.appendChild(arrivalLink) arrivalLink.textContent = train.destination.name arrivalLink.classList.add('no-custom-a', 'items') var arrivalUrl = new URL('/view-station.html', window.location.origin) arrivalUrl.searchParams.set('stationId', train.destination.id) arrivalLink.href = arrivalUrl.toString() } else { var arrivalSpan = document.createElement('span') arrivalHeading.append(arrivalSpan) arrivalSpan.innerText = train.destination.name || train.destination.address } if (train.arrivalPlatform || train.plannedArrivalPlatform) { var arrivalPlatformP = document.createElement('p') trainDiv.append(arrivalPlatformP) arrivalPlatformP.classList.add('sec', 'arrival', 'platform') if (train.arrivalPlatform && train.arrivalPlatform != train.plannedArrivalPlatform) { arrivalPlatformP.classList.add('changed') } arrivalPlatformP.textContent = `${train.arrivalPlatform || train.plannedArrivalPlatform}` } if (!last) { var nextTrain = data.journeys[i].legs[idx + 1] var nextDepartureTimeP = document.createElement('p') trainDiv.appendChild(nextDepartureTimeP) nextDepartureTimeP.classList.add('sec', 'next-departure', 'time') var departureTimePre = document.createElement('pre') nextDepartureTimeP.appendChild(departureTimePre) var departure = new Date(nextTrain.plannedDeparture) departureTimePre.textContent = departure.toLocaleTimeString([], { 'hour': '2-digit', 'minute': '2-digit' }) if (nextTrain.departurePlatform || nextTrain.plannedDeparturePlatform) { var departurePlatformP = document.createElement('p') trainDiv.append(departurePlatformP) departurePlatformP.classList.add('sec', 'next-departure', 'platform') if (nextTrain.departurePlatform && nextTrain.departurePlatform != nextTrain.plannedDeparturePlatform) { departurePlatformP.classList.add('changed') } departurePlatformP.textContent = `${nextTrain.departurePlatform || nextTrain.plannedDeparturePlatform}` } } }) } } function lsk() { } function csk() { if (focusedElement == null) { return } focusedElement.click() } window.addEventListener('load', function (e) { var sp = new URL(window.location.href).searchParams fromStation = sp.get('from') var fromJson = JSON.parse(fromStation || 'null') toStation = sp.get('to') var toJson = JSON.parse(toStation || 'null') var departureDateStr = sp.get('departureDate') if (departureDateStr) { departureDate = new Date(departureDateStr) } var arrivalDateStr = sp.get('arrivalDate') if (arrivalDateStr) { arrivalDate = new Date(arrivalDateStr) } var titleH1 = document.querySelector("header > h1") if (!fromJson || !toJson) { titleH1.textContent = 'Find Route' } else { titleH1.textContent = `${fromJson.name || fromJson.address} - ${toJson.name || toJson.address}` } var transitKindJson = JSON.parse(sp.get('transitKind')) if (transitKindJson) { transitKind = transitKindJson } var footer = document.querySelector('footer') if (!fromStation || !toStation || (!departureDate && !arrivalDate)) { // Send to config page var url = new URL(window.location.href) url.pathname = '/config-route.html' window.location.href = url.toString() } else { var contentDiv = document.createElement('div') document.body.insertBefore(contentDiv, footer) contentDiv.classList.add('content') contentDiv.style.display = 'flex' contentDiv.style.flexDirection = 'column' contentDiv.style.alignItems = 'center' contentDiv.style.justifyContent = 'center' var loadingP = document.createElement('p') contentDiv.appendChild(loadingP) loadingP.classList.add('pri') loadingP.textContent = 'Loading data...' var url = new URL('https://v6.db.transport.rest/journeys') if (fromJson.type === 'stop' || fromJson.type === 'station') { url.searchParams.set('from', fromJson.id) } else { if (fromJson.type === 'location') { delete fromJson.id } Object.keys(fromJson).forEach(function (key) { url.searchParams.set(`from.${key}`, fromJson[key]) }) } if (toJson.type === 'stop' || toJson.type === 'station') { url.searchParams.set('to', toJson.id) } else { if (toJson.type === 'location') { delete toJson.id } Object.keys(toJson).forEach(function (key) { url.searchParams.set(`to.${key}`, toJson[key]) }) } if (departureDate) { url.searchParams.set('departure', departureDate.toISOString()) } if (arrivalDate) { url.searchParams.set('arrival', arrivalDate.toISOString()) } url.searchParams.set('results', '20') url.searchParams.set('stopovers', 'true') url.searchParams.set('nationalExpress', transitKind.ice) url.searchParams.set('national', transitKind.ic) url.searchParams.set('regionalExpress', transitKind.re) url.searchParams.set('regional', transitKind.rb) url.searchParams.set('suburban', transitKind.s) url.searchParams.set('bus', transitKind.bus) url.searchParams.set('ferry', transitKind.ferry) url.searchParams.set('subway', transitKind.u) url.searchParams.set('tram', transitKind.tram) if (window.localStorage) { this.localStorage.setItem('recent/route', JSON.stringify({ $addDate: new Date().toISOString(), from: fromJson.name || fromJson.address, to: toJson.name || toJson.address, queryParams: new URL(window.location.href).search, })) } fetch(url.toString()) .then(function (response) { return response.json() }) .then(function (data) { contentDiv.remove() onItineraries(data) itineraries = data function fetchMore(timeoutMs) { console.debug(`Got ${itineraries.journeys.length} journeys, fetching more`) var moreUrl = new URL(url.toString()) moreUrl.searchParams.delete('departure') moreUrl.searchParams.delete('arrival') moreUrl.searchParams.set('laterThan', itineraries.laterRef) fetch(moreUrl.toString()) .then(function (result) { return result.json() }) .then(function (data) { if (data.journeys) { itineraries.journeys = itineraries.journeys.concat(data.journeys) itineraries.laterRef = data.laterRef onItineraries(itineraries) if (itineraries.laterRef) { var lastJourney = itineraries.journeys[itineraries.journeys.length - 1] var lastLeg = lastJourney.legs[lastJourney.legs.length - 1] var departureDate = new Date(lastLeg.plannedDeparture) if (departureDate.getTime() - Date.now() < 86400000) { setTimeout( function () { fetchMore((timeoutMs || 500) * 1.5) }, timeoutMs || 500, ) } } } }) } if (departureDate) { fetchMore() } }) .catch(function (e) { loadingP.textContent = 'An error has occured' var errorP = document.createElement('p') contentDiv.appendChild(errorP) errorP.classList.add('sec') errorP.textContent = e.toString() var retryLink = document.createElement('a') contentDiv.appendChild(retryLink) retryLink.classList.add('items') retryLink.textContent = 'Retry' retryLink.href = '' }) } document.querySelectorAll('.lsk').forEach(function (lskElem) { lskElem.addEventListener('click', function (e) { lsk() }) }) document.querySelectorAll('.csk').forEach(function (cskElem) { cskElem.addEventListener('click', function (e) { csk() }) }) document.body.addEventListener('keydown', function (e) { if (e.key == 'SoftLeft') { lsk() } else if (e.key == 'Enter') { csk() } }) })