hvvstuhl.de/elevators.js
2024-06-04 11:29:49 +02:00

363 lines
No EOL
14 KiB
JavaScript

Object.defineProperty(String.prototype, 'capitalize', {
value: function () {
return this.charAt(0).toUpperCase() + this.toLowerCase().slice(1);
},
enumerable: false
});
async function loadElevators() {
const res = await fetch('https://www.hvv.de/elevators', {referrer: ""});
const data = await res.json();
localStorage.setItem("elevator_data", JSON.stringify(data));
}
const dateTimeStyle = new Intl.DateTimeFormat('de-DE', {
dateStyle: 'medium',
timeStyle: 'medium',
timeZone: 'Europe/Berlin',
})
const internalData = []
let geolocationPermission = false;
let geolocation = [null, null];
async function loadOsmData() {
const elevatorNodes = document.querySelectorAll('.elevator');
for (const elevator of elevatorNodes) {
elevator.querySelector('.osm').insertAdjacentHTML("beforeend", '<dl class="osmTags">loading...</dl>')
}
for (const elevator of elevatorNodes) {
const osmContainer = elevator.querySelector('.osmTags');
const nodeId = elevator.querySelector('.osm').dataset.nodeid;
const osmData = await fetch(`https://overpass-api.de/api/interpreter?data=[out:json];node(${nodeId});out%20body;`, {});
const responseData = await osmData.json();
if (responseData.elements.length) {
const node = responseData.elements[0];
const tags = node['tags'];
console.log(tags)
if (tags['highway'] === 'elevator') {
let osmTemplate = '';
osmTemplate += `<div><dt><span data-icon="location" class="size-l"></span>Link zur Karte</dt><dd><a href="https://www.openstreetmap.org/node/${nodeId}" target="_blank">
Auf Karte anzeigen
</a></dd></div>`;
if (tags.hasOwnProperty('description')) {
osmTemplate += `<div><dt><span data-icon="info" class="size-l"></span>Beschreibung</dt><dd>${tags['description']}</dd></div>`;
}
if (tags.hasOwnProperty('level')) {
osmTemplate += `<div><dt><span data-icon="up-down" class="size-l"></span>Ebenen</dt><dd>${tags['level'].split(';').sort().join(', ')}</dd></div>`;
}
if (tags.hasOwnProperty('wheelchair')) {
osmTemplate += `<div><dt><span data-icon="wheelchair" class="size-l"></span>Rollstühle</dt><dd>${tags['wheelchair'] === 'yes' ? 'Ja' : 'Nein'}</dd></div>`;
}
if (tags.hasOwnProperty('bicycle')) {
osmTemplate += `<div><dt><span data-icon="bicycle" class="size-l"></span>Fahrräder</dt><dd>${tags['bicycle'] === 'yes' ? 'Ja' : 'Nein'}</dd></div>`;
}
osmContainer.innerHTML = ''; // clear loading state
osmContainer.insertAdjacentHTML('beforeend', osmTemplate);
}
}
}
}
function distance([lat1, lon1], [lat2, lon2]) {
if (!lat1 || !lat2 || !lon1 || !lon2) return null
const R = 6371e3; // metres
const phi1 = lat1 * Math.PI / 180; // φ, λ in radians
const phi2 = lat2 * Math.PI / 180;
const dPhi = (lat2 - lat1) * Math.PI / 180;
const dLambda = (lon2 - lon1) * Math.PI / 180;
const a = Math.sin(dPhi / 2) * Math.sin(dPhi / 2) +
Math.cos(phi1) * Math.cos(phi2) *
Math.sin(dLambda / 2) * Math.sin(dLambda / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // in metres
}
function registerGeolocationWatcher() {
navigator.geolocation.watchPosition((pos) => {
geolocation = [pos.coords.latitude, pos.coords.longitude]
}, (e) => {
console.log(e)
}, {
enableHighAccuracy: true,
timeout: 5000,
maximumAge: 0,
})
}
function checkGeolocationPermission() {
navigator.permissions.query({name: "geolocation"}).then((result) => {
geolocationPermission = result.state
if (result.state === 'granted') {
registerGeolocationWatcher()
}
});
}
function allowGeolocation() {
navigator.geolocation.getCurrentPosition(() => {
console.log('success')
}, () => {
console.log('error')
});
checkGeolocationPermission()
registerGeolocationWatcher()
}
checkGeolocationPermission()
document.querySelector('#stationsNearMe').addEventListener('click', allowGeolocation)
function sortStations(stationA, stationB) {
const nameA = stationA.mainSubStation.stationName.toUpperCase(); // ignore upper and lowercase
const nameB = stationB.mainSubStation.stationName.toUpperCase(); // ignore upper and lowercase
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
}
function getType(line) {
const type = line.replace(/[^A-z]/g, "");
switch (type) {
case 'RE':
case 'RB':
case 'R':
case 'DB':
return 'R';
case 'S':
return 'S';
case 'U':
return 'U';
case 'A':
return 'A';
}
}
function getTypes(lines) {
const types = new Set();
for (const line of lines) {
types.add(getType(line))
}
return types;
}
function renderData() {
const data = JSON.parse(localStorage.getItem("elevator_data"));
if (!data) {
console.error('No Data available!')
return;
}
let stations = data.stations;
stations.sort(sortStations)
const date = new Date(data.lastUpdate);
document.querySelector('#loadElevators').innerHTML = 'Daten aktualisieren';
const listContainer = document.querySelector('#stationList');
const dateContainer = document.querySelector('#lastUpdated');
dateContainer.innerHTML = dateTimeStyle.format(date);
let stationIndex = 0;
for (const station of stations) {
const stationName = station.mainSubStation.stationName;
const lines = new Set();
const elevators = [];
for (const elevatorKey of Object.keys(station.elevators)) {
const elevatorApi = station.elevators[elevatorKey];
const elevatorLines = [];
for (let line of elevatorApi.lines) {
line = line.replace(/[\(\)]/g, "");
lines.add(line);
elevatorLines.push({
line: line,
type: getType(line),
});
}
// try to detect levels from description
let levels = [];
if (elevatorApi.description.search('<->') >= 0) {
levels = elevatorApi.description.split('<->').map(level => level.trim());
} else if (elevatorApi.description.search('<>') >= 0) {
levels = elevatorApi.description.split('<>').map(level => level.trim());
} else if (elevatorApi.description.search('/ ') >= 0) {
levels = elevatorApi.description.split('/ ').map(level => level.trim());
}
const elevator = {
working: elevatorApi['state'] === 1,
stateUnavailable: elevatorApi['state'] === -1,
dimensions: {
width: elevatorApi['cabinWidth'],
length: elevatorApi['cabinLength'],
door: elevatorApi['doorWidth'],
},
description: elevatorApi['description'],
label: elevatorApi['label'],
type: elevatorApi['type'].replace('_', ' ').capitalize(),
braille: elevatorApi['tasterType'] === 'UNBEKANNT' ? -1 : elevatorApi['tasterType'] === 'KOMBI' || elevatorApi['tasterType'] === 'BRAILLE',
speaker: elevatorApi['tasterType'] === 'UNBEKANNT' ? -1 : elevatorApi['tasterType'] === 'KOMBI',
lines: elevatorLines,
levels: levels,
instCause: elevatorApi['instCause'],
osmNodeId: elevatorApi['osmId'],
}
elevators.push(elevator);
}
const stationTypes = getTypes(Array.from(lines));
let elevatorsTemplate = '';
let previewTemplate = '';
let stationState = {
unavailable: 0,
working: 0,
outOfOrder: 0,
}
for (const elevator of elevators) {
let linesTemplate = '<div class="lineList">Linien: ';
for (const line of elevator.lines) {
linesTemplate += `<span data-type="${line.type}" data-line="${line.line}" class="lineChip">${line.line}</span>`;
}
linesTemplate += '</div>';
let levelsTemplate = '<ol class="levelList">';
for (const levelDescription of elevator.levels) {
levelsTemplate += `<li>${levelDescription}</li>`;
}
levelsTemplate += '</ol>';
previewTemplate += `<span data-icon="${elevator.working || elevator.stateUnavailable ? 'elevator' : 'elevator-slash'}"
class="size-l ${elevator.working ? 'text-green' : elevator.stateUnavailable ? 'text-orange' : 'text-red'}"></span>`
if (elevator.stateUnavailable) {
stationState.unavailable++;
} else if (elevator.working) {
stationState.working++;
} else {
stationState.outOfOrder++;
}
elevatorsTemplate += `<li class="elevator">
<span data-icon="${elevator.working || elevator.stateUnavailable ? 'elevator' : 'elevator-slash'}"
class="size-xl ${elevator.working ? 'text-green' : elevator.stateUnavailable ? 'text-orange' : 'text-red'}"
role="img"
aria-label="${elevator.stateUnavailable
? 'Status nicht verfügbar.'
: elevator.working
? 'Funktionsfähig'
: 'Außer Betrieb'}">
</span>
<div class="elevatorData">
${elevator.instCause !== '' ? `<div class="elevatorInfo">${elevator.instCause}</div>` : ''}
${elevator.levels.length ? levelsTemplate : elevator.description}
<dl>
<div>
<dt><span data-icon="fit-width" class="size-l"></span>Durchgang</dt>
<dd>${elevator.dimensions.door} cm</dd>
</div>
<div>
<dt><span data-icon="width" class="size-l"></span>Breite</dt>
<dd>${elevator.dimensions.width} cm</dd>
</div>
<div>
<dt><span data-icon="length" class="size-l"></span>Länge</dt>
<dd>${elevator.dimensions.length} cm</dd>
</div>
<div>
<dt><span data-icon="braille" class="size-l"></span>Braille</dt>
<dd>${elevator.braille === -1 ? 'unbekannt' : elevator.braille ? `verfügbar` : 'nicht verfügbar'}</dd>
</div>
<div>
<dt><span data-icon="speaker" class="size-l"></span>Ansage</dt>
<dd>${elevator.speaker === -1 ? 'unbekannt' : elevator.speaker ? `verfügbar` : 'nicht verfügbar'}</dd>
</div>
</dl>
${elevator.lines.length ? `${linesTemplate}` : ''}
<div class="osm" data-nodeid="${elevator.osmNodeId}">
<h4>Daten von OpenStreetMap</h4>
</div>
</div>
</li>`;
}
const template = `<li class="station" id="station_${stationIndex}">
<div class="stationSummary">
<div class="typeList">
${Array.from(stationTypes).sort().map(t => `<span class="typeChip" data-type="${t}">${t}</span>`).join('')}
</div>
<div class="stationTitle">
<h3>${stationName}</h3>
<div class="elevator-preview" role="img"
aria-label="${stationState.working ? `${stationState.working} ${stationState.working > 1 ? 'Aufzüge sind' : 'Aufzug ist'} funktionstüchtig.` : ''}
${stationState.outOfOrder ? `${stationState.outOfOrder} ${stationState.outOfOrder > 1 ? 'Aufzüge sind' : 'Aufzug ist'} außer Betrieb.` : ''}
${stationState.unavailable ? `Bei ${stationState.unavailable} ${stationState.unavailable > 1 ? 'Aufzügen' : 'Aufzug'} ist der Funktionsstatus unbekannt.` : ''}">
${previewTemplate}
</div>
</div>
</div>
<details>
<summary>
Aufzüge anzeigen
</summary>
<ul class="elevatorList collapsed" aria-expanded="false" id="station_${stationIndex}_elevatorList">
${elevatorsTemplate}
</ul>
</details>
</li>`;
listContainer.insertAdjacentHTML('beforeend', template);
// immediate invocation
// (function () {
// const stationId = stationIndex;
// listContainer.querySelector(`#station_${stationIndex}`)
// .addEventListener('click', () => toggleElevatorList(stationId))
// }());
internalData[stationIndex++] = {
name: stationName,
state: stationState,
elevators: elevators,
}
}
}
document.querySelector('#loadElevators')
.addEventListener('click', (e) => {
loadElevators().then(() => renderData());
})
renderData();
function filterData(searchString) {
for (const stationIndex in internalData) {
const matches = internalData[stationIndex].name.toLowerCase().search(searchString.toLowerCase()) >= 0;
document.querySelector(`#station_${stationIndex}`).classList.toggle('hidden', !matches);
}
}
document.querySelector('#searchStation').addEventListener('input', (e) => {
filterData(e.target.value);
})
filterData(document.querySelector('#searchStation').value)