cff-displays/index.js

321 lines
8.5 KiB
JavaScript

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()
})