const MAX_DISPLAYED_VIAS = 4 let currentBpuic = null let currentPlatform = null let currentInfo = null function initSearch() { const sInput = document.getElementById("search-input") const form = document.getElementById("search") const refresh = document.getElementById("refresh") const platforms = document.getElementById("platforms") form.addEventListener("submit", e => { e.preventDefault() searchStation(sInput.value) .then(stations => updateStationsList(stations)) }) refresh.addEventListener("click", () => { if (currentBpuic !== null) { setStation(currentBpuic) } }) platforms.addEventListener("change", () => { setPlatform(platforms.value) }) } function updateStationsList(stations) { const stationsList = document.getElementById("stations") stationsList.innerHTML = "" stations.forEach(station => { const div = document.createElement("div") div.classList.add("station") div.innerText = station.bezeichnungOffiziell div.addEventListener("click", () => setStation(station.bpuic)) stationsList.appendChild(div) }) } function setStation(bpuic) { if (bpuic !== currentBpuic) { currentPlatform = null } currentBpuic = bpuic fetchInfo(bpuic).then(res => { currentInfo = res document.getElementById("search-input").value = "" document.getElementById("stations").innerHTML = "" const platformsList = document.getElementById("platforms") platformsList.innerHTML = "" let platforms = res.contents[0].verkehrsmittels.map(train => train.gleisAbIst) platforms = [... new Set(platforms)] platforms = platforms.sort() platforms.forEach(platform => { const opt = document.createElement("option") opt.value = platform opt.innerText = platform if (currentPlatform === platform) { opt.selected = true } platformsList.appendChild(opt) }) const station = document.getElementById("station") station.querySelector(".name").innerText = res.contents[0].betriebspunkt.bezeichnungOffiziell updateDisplay(res, 0, currentPlatform) }) } function setPlatform(platform) { const platforms = document.getElementById("platforms") currentPlatform = platforms.value if (currentInfo === null) return updateDisplay(currentInfo, 0, platform) } function searchStation(query) { return fetch(`https://displays.api.sbb.ch/internal/api/v1/betriebspunkt?query=${query}`, { headers: { "X-API-Key": API_KEY } }).then(async (res) => { let stations = [] if (res.status === 200) { stations = await res.json() } return stations }) } function fetchInfo(bpuic) { return fetch(`https://displays.api.sbb.ch/internal/api/v1/data/${bpuic}`, { headers: { "X-API-Key": API_KEY } }).then(res => { return res.json() /* }).then(res => { const trains = res.contents[0].verkehrsmittels console.log(`${trains.length} trains`) let platforms = trains.map(t => t.gleisAbIst) platforms = [... new Set(platforms)] console.log(`Platforms: ${platforms.sort()}`) return res*/ }) } const example = { config: { stationName: "" }, contents: [ { type: "GA", verkehrsmittels: [ { vmArt: "IR", liniennummer: 90, dsVmArt: { vmArt: "IR", ttsTexte: { FR: "InterRegio" } }, zeitAbKb: "2024-04-08T18:32:00", // heure de départ prévue zeitAbErw: "2024-04-08T18:33:00", // heure de départ réelle gleisAbKb: "1", // voie prévue gleisAbIst: "1", // voie réelle verspaetungsminutenAb: 0, // au moins n minutes de retard verspaetungsminutenAn: 0, // au plus n minutes de retard ziele: [ { betriebspunkt: { bpuic: 8501609, bezeichnungOffiziell: "Brig" } } ], vias: [ { betriebspunkt: { bpuic: 8501500, bezeichnungOffiziell: "Martigny" }, prio: 601, trennung: false // séparation } ], formation: { richtung: "RECHTS", waggons: [ { sektor: "A", form: "MITTE", // icône (MITTE, START_LINKS, END_RECHT) typ: "FIKTIV", // classe (FIKTIV = rien, ERSTE, ZWEITE, ERSTE_ZWEITE, RESTAURANT_ERSTE, FAMILIENWAGEN, TRIEBFAHRZEUG = loco) angebote: [], // services (vélos, resto, etc.) statusList: [], // status (GESCHLOSSEN) nummer: null, zielIndex: null // terminus (utile si séparation) }, { sektor: "A", form: "MITTE", typ: "ZWEITE", angebote: [ "VELOHAKEN" // NIEDERFLUREINSTIEG, ROLLSTUHLSTELLPLAETZE, KINDERWAGEN, RESERVATIONSPFLICHTIGE ], nummer: null, zielIndex: 0 }, { sektor: "A", form: "MITTE", typ: "ZWEITE", angebote: [ "VELOHAKEN" ], nummer: null, zielIndex: 0 } ], ziele: [ { zielIndex: 0, betriebspunkt: { bpuic: 8501609, bezeichnungOffiziell: "Brig" }, fromPos: 1, // premier wagon concerné toPos: 10 // dernier wagon concerné } ] } } ] } ] } function getTimeFromTS(timestamp) { const date = new Date(timestamp) const hours = date.getHours().toString().padStart(2, "0") const minutes = date.getMinutes().toString().padStart(2, "0") return `${hours}:${minutes}` } function selectVias(vias) { if (vias.length <= MAX_DISPLAYED_VIAS) return vias let priorities = vias.map((via, index) => [via.prio, index]) priorities = priorities.sort((a, b) => a[0] - b[0]) let selectedVias = [] let selectedPrio = priorities.slice(0, MAX_DISPLAYED_VIAS).sort((a, b) => a[1] - b[1]) selectedPrio.forEach(([prio, index]) => { selectedVias.push(vias[index]) }) return selectedVias } function buildComposition(formation) { const template = document.getElementById("waggon-template").cloneNode(true) template.removeAttribute("id") template.classList.remove("template") const composition = document.getElementById("composition") const sectors = document.getElementById("sectors") sectors.innerHTML = "" const carriages = document.getElementById("carriages") carriages.innerHTML = "" carriages.dataset.direction = formation.richtung === "RECHTS" ? "right" : "left" const weights = {} let first = true for (let i = 0; i < formation.waggons.length; i++) { const waggon = formation.waggons[i] if (!(waggon.sektor in weights)) { weights[waggon.sektor] = 0 } weights[waggon.sektor]++ if (waggon.typ === "FIKTIV") { continue } const elmt = template.cloneNode(true) if (first) { first = false carriages.style.setProperty("--offset", i.toString()) } elmt.classList.add("form-" + waggon.form.toLowerCase()) const cls = { ERSTE: ["1", "first"], ZWEITE: ["2", "second"], ERSTE_ZWEITE: ["1|2", "first-second"], RESTAURANT_ERSTE: ["1", "first"], RESTAURANT_ZWEITE: ["1", "first"], FAMILIENWAGEN: ["", "familie"], TRIEBFAHRZEUG: ["", "loc"] }[waggon.typ] elmt.querySelector(".class").innerText = cls[0] elmt.classList.add("class-" + cls[1]) if (waggon.nummer !== null) { elmt.querySelector(".number").innerText = waggon.nummer } carriages.appendChild(elmt) } composition.style.setProperty("--n-waggons", formation.waggons.length.toString()) const sectorLetters = Object.keys(weights).sort() sectorLetters.forEach(letter => { const sector = document.createElement("div") sector.classList.add("sector") sector.style.setProperty("--weight", weights[letter].toString()) sector.innerText = (letter === "null" ? "" : letter) sectors.appendChild(sector) }) } function updateDisplay(result, idx=0, platform=null) { let trains = result.contents[0].verkehrsmittels if (platform !== null) { trains = trains.filter(t => t.gleisAbIst === platform) } const train = trains[idx] let icon = train.vmArt let iconTxt = train.dsVmArt.ttsTexte.FR if (train.liniennummer !== null) { icon += "-" + train.liniennummer iconTxt += " " + train.liniennummer } icon = "icons/" + icon.toLowerCase() + "-negative.svg" document.getElementById("train-type").src = icon document.getElementById("train-type").alt = iconTxt document.getElementById("departure").innerText = getTimeFromTS(train.zeitAbKb) document.getElementById("destination").innerText = train.ziele[0].betriebspunkt.bezeichnungOffiziell const stopsList = document.getElementById("stops") stopsList.innerHTML = "" const vias = selectVias(train.vias) vias.forEach(via => { const stop = document.createElement("div") stop.classList.add("stop") stop.innerText = via.betriebspunkt.bezeichnungOffiziell stopsList.appendChild(stop) }) buildComposition(train.formation) } window.addEventListener("load", () => { initSearch() })