diff --git a/elevators.js b/elevators.js
index f302008..5f4fc3b 100644
--- a/elevators.js
+++ b/elevators.js
@@ -3,8 +3,64 @@ const internalData = {
stations: [],
};
let geolocationPermission = false;
-let geolocation = [null, null];
+let geolocation = null;
const openStations = new Set();
+let sortByDistance = false;
+
+const substituteCoordinates = [
+ {
+ name: 'Borgweg (Stadtpark)',
+ coordinates: [53.5907696, 10.0147719],
+ },
+ {
+ name: 'Emilienstraße',
+ coordinates: [53.5716862, 9.9525424],
+ },
+ {
+ name: 'Garstedt',
+ coordinates: [53.6844739, 9.9860415],
+ },
+ {
+ name: 'Hagenbecks Tierpark',
+ coordinates: [53.5925874, 9.9440359],
+ },
+ {
+ name: 'Hauptbahnhof Nord',
+ coordinates: [53.5541197, 10.0061270],
+ },
+ {
+ name: 'Hoheneichen',
+ coordinates: [53.6355141, 10.0677176],
+ },
+ {
+ name: 'Kornweg (Klein Borstel)',
+ coordinates: [53.6324430, 10.0541722],
+ },
+ {
+ name: 'Lauenbrück',
+ coordinates: [53.1971209, 9.5640765],
+ },
+ {
+ name: 'Lutterothstraße',
+ coordinates: [53.5819938, 9.9476215],
+ },
+ {
+ name: 'Meckelfeld',
+ coordinates: [53.4248897, 10.0291223],
+ },
+ {
+ name: 'Sengelmannstraße (City Nord)',
+ coordinates: [53.6093953, 10.0220004],
+ },
+ {
+ name: 'St.Pauli',
+ coordinates: [53.5507957, 9.9700752],
+ },
+ {
+ name: 'Winsen(Luhe)',
+ coordinates: [53.3534304, 10.2086841],
+ },
+]
Object.defineProperty(String.prototype, 'capitalize', {
@@ -15,12 +71,17 @@ Object.defineProperty(String.prototype, 'capitalize', {
});
async function loadElevators() {
- const res = await fetch('https://www.hvv.de/elevators', {referrer: ""});
+ document.querySelector('#errorMessage').classList.add('hidden');
+ const res = await fetch('https://www.hvv.de/elevators', {
+ referrer: "",
+ }).catch(e => {
+ document.querySelector('#errorMessage').classList.remove('hidden');
+ });
const data = await res.json();
const stations = data['stations'];
- stations.sort(sortStations)
+ stations.sort(sortStations);
internalData.lastUpdate = new Date(data['lastUpdate']);
let stationIndex = 0;
@@ -102,7 +163,10 @@ async function loadElevators() {
}
}
- localStorage.setItem("internal_data", JSON.stringify(internalData));
+ localStorage.setItem("internal_data", JSON.stringify({
+ api: 'v1',
+ ...internalData
+ }));
}
const dateTimeStyle = new Intl.DateTimeFormat('de-DE', {
@@ -119,12 +183,55 @@ async function loadOsmData() {
}
}
- const osmData = await fetch(`https://overpass-api.de/api/interpreter?data=[out:json];node(id:${nodeIdList.join(',')});out%20body;`, {});
- const osmJson = await osmData.json();
+ const osmResponse = await fetch(`https://overpass-api.de/api/interpreter?data=[out:json];node(id:${nodeIdList.join(',')});out%20body;`, {});
+ const osmJson = await osmResponse.json();
if (!osmJson.hasOwnProperty('elements')) return;
- localStorage.setItem("osm_data", JSON.stringify(osmJson));
+ const osmNodes = {};
+ for await (const node of osmJson.elements) {
+ if (node.hasOwnProperty('tags')) {
+ const tags = node['tags'];
+ if (tags['highway'] === 'elevator') {
+ osmNodes[node['id']] = node;
+ } else {
+ console.warn(`OSM Node is not an elevator. (NodeID: ${node['id']})`);
+ }
+ } else {
+ console.warn(`OSM Node has no Tags. (NodeID: ${node['id']})`);
+ }
+ }
+
+ //update coordinates in stations
+ for (const stationIndex in internalData.stations) {
+ const station = internalData.stations[stationIndex];
+ for (const elevator of station.elevators) {
+ const node = osmNodes[elevator.osmNodeId]
+ if (node) {
+ internalData.stations[stationIndex]['coordinates'] = [
+ node['lat'],
+ node['lon'],
+ ]
+ }
+ }
+
+ if (!internalData.stations[stationIndex].hasOwnProperty('coordinates')) {
+ const substitute = substituteCoordinates.filter(subs => subs.name === internalData.stations[stationIndex].name)
+ if (substitute.length) {
+ internalData.stations[stationIndex]['coordinates'] = substitute[0].coordinates;
+ }
+ }
+ }
+
+ localStorage.setItem("osm_data", JSON.stringify({
+ api: 'v1',
+ lastUpdate: new Date(),
+ nodes: osmNodes
+ }));
+ localStorage.setItem("internal_data", JSON.stringify({
+ api: 'v1',
+ ...internalData
+ }));
}
function distance([lat1, lon1], [lat2, lon2]) {
@@ -145,9 +252,12 @@ function distance([lat1, lon1], [lat2, lon2]) {
function registerGeolocationWatcher() {
navigator.geolocation.watchPosition((pos) => {
- geolocation = [pos.coords.latitude, pos.coords.longitude]
+ if (geolocation === null) {
+ geolocation = [pos.coords.latitude, pos.coords.longitude];
+ renderData(geolocation);
+ }
}, (e) => {
- console.log(e)
+ console.warn(e)
}, {
enableHighAccuracy: true,
timeout: 5000,
@@ -177,8 +287,6 @@ function allowGeolocation() {
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
@@ -193,6 +301,20 @@ function sortStations(stationA, stationB) {
return 0;
}
+function sortStationsByDistance(stationA, stationB) {
+ const distanceA = stationA.distance; // ignore upper and lowercase
+ const distanceB = stationB.distance; // ignore upper and lowercase
+ if (distanceA < distanceB) {
+ return -1;
+ }
+ if (distanceA > distanceB) {
+ return 1;
+ }
+
+ // names must be equal
+ return 0;
+}
+
function getType(line) {
const type = line.replace(/[^A-z]/g, "");
switch (type) {
@@ -218,7 +340,7 @@ function getTypes(lines) {
return types;
}
-function renderData() {
+function renderData(location = null) {
const ls = JSON.parse(localStorage.getItem("internal_data"));
if (!ls) return;
internalData.lastUpdate = ls['lastUpdate'];
@@ -230,6 +352,18 @@ function renderData() {
return;
}
+ if (
+ location !== null
+ && (
+ location.length !== 2
+ || typeof location[0] !== 'number'
+ || typeof location[1] !== 'number'
+ )
+ ) {
+ console.error('No valid location provided')
+ return;
+ }
+
document.querySelector('#updateInfo').classList.remove('hidden');
document.querySelector('#loadElevators').classList.remove('hidden');
document.querySelector('#filters').classList.remove('hidden');
@@ -241,18 +375,57 @@ function renderData() {
//clear list before update
listContainer.innerHTML = '';
- const stations = internalData['stations'];
+ let stations = [...internalData['stations']];
for (const stationIndex in stations) {
const station = stations[stationIndex];
+ station.id = stationIndex;
+
+ if (location !== null) {
+ if (station.hasOwnProperty('coordinates')) {
+ station.distance = distance(location, station.coordinates);
+ } else {
+ console.log('station has no position:', station.name);
+ }
+ }
+ }
+
+ stations = stations.sort(sortStationsByDistance);
+
+ for (const stationIndex in stations) {
+ const station = stations[stationIndex];
+
+ if (location !== null) {
+ if (station.hasOwnProperty('coordinates')) {
+ station.distance = distance(location, station.coordinates);
+ } else {
+ console.log('station has no position:', station.name);
+ }
+ }
let elevatorsTemplate = '';
let previewTemplate = '';
for (const elevator of station.elevators) {
- let linesTemplate = '
Linien: ';
- for (const line of elevator.lines) {
- linesTemplate += `
${line.line}`;
+ const stateTemplate = `
+ `;
+
+ let linesTemplate = '';
+ linesTemplate = `
${stateTemplate}`;
+ if (elevator.lines.length) {
+ linesTemplate = `
${stateTemplate}`;
+ linesTemplate += `
Linien: `;
+ for (const line of elevator.lines) {
+ linesTemplate += `${line.line}`;
+ }
+ linesTemplate += '
';
}
linesTemplate += '
';
@@ -264,41 +437,32 @@ function renderData() {
let osmTemplate = '';
if (osmData) {
- const nodes = osmData.elements.filter(node => node.id === elevator.osmNodeId)
- if (nodes.length) {
- const nodeInfo = nodes[0];
- if (nodeInfo.hasOwnProperty('tags')) {
- const tags = nodeInfo['tags'];
- if (tags['highway'] === 'elevator') {
- osmTemplate = '
';
- osmTemplate += ``;
- if (tags.hasOwnProperty('description')) {
- osmTemplate += `- Beschreibung
- ${tags['description']}
`;
- }
- if (tags.hasOwnProperty('level')) {
- osmTemplate += `- Ebenen
- ${tags['level'].split(';').sort().join(', ')}
`;
- }
- if (tags.hasOwnProperty('wheelchair')) {
- osmTemplate += `- Rollstühle
- ${tags['wheelchair'] === 'yes' ? 'Ja' : 'Nein'}
`;
- }
- if (tags.hasOwnProperty('bicycle')) {
- osmTemplate += `- Fahrräder
- ${tags['bicycle'] === 'yes' ? 'Ja' : 'Nein'}
`;
- }
+ const node = osmData.nodes[elevator.osmNodeId]
+ if (node) {
- osmTemplate += '
';
- } else {
- console.warn(`OSM Node is not an elevator. At:\t${station.name}\t${elevator.label} (NodeID: ${elevator.osmNodeId})`);
- }
- } else {
- console.warn(`OSM Node has no Tags. At:\t${station.name}\t${elevator.label} (NodeID: ${elevator.osmNodeId})`);
+ osmTemplate = '
';
+ osmTemplate += ``;
+ if (node.tags.hasOwnProperty('description')) {
+ osmTemplate += `- Beschreibung
- ${node.tags['description']}
`;
}
+ if (node.tags.hasOwnProperty('level')) {
+ osmTemplate += `- Ebenen
- ${node.tags['level'].split(';').sort().join(', ')}
`;
+ }
+ if (node.tags.hasOwnProperty('wheelchair')) {
+ osmTemplate += `- Rollstühle
- ${node.tags['wheelchair'] === 'yes' ? 'Ja' : 'Nein'}
`;
+ }
+ if (node.tags.hasOwnProperty('bicycle')) {
+ osmTemplate += `- Fahrräder
- ${node.tags['bicycle'] === 'yes' ? 'Ja' : 'Nein'}
`;
+ }
+
+ osmTemplate += '
';
} else {
console.warn(`OSM Node not found (deleted). At:\t${station.name}\t${elevator.label} (NodeID: ${elevator.osmNodeId})`);
}
@@ -310,19 +474,10 @@ function renderData() {
previewTemplate += `
`
-
elevatorsTemplate += `
-
-
+ ${stateTemplate}
- ${elevator.lines.length ? `${linesTemplate}` : ''}
+ ${linesTemplate}
${elevator.instCause !== '' ? `
${elevator.instCause}
` : ''}
${elevator.levels.length ? levelsTemplate : elevator.description}
@@ -365,11 +520,14 @@ function renderData() {
`;
}
- const template = `
+ const template = `
+
${station.types.sort().map(t => `${t}`).join('')}
+ ${typeof station.distance !== 'undefined' ? `
${Math.round(station.distance / 100) / 10}
km
` : ''}
+
-
+
Aufzüge anzeigen
-
@@ -393,7 +551,7 @@ ${station.state.unavailable ? `Bei ${station.state.unavailable} ${station.state.
//immediate invocation
(function () {
- listContainer.querySelectorAll(`#station_${stationIndex} .loadOSM`).forEach(e => {
+ listContainer.querySelectorAll(`#station_${station.id} .loadOSM`).forEach(e => {
e.addEventListener('click', (ev) => {
ev.target.querySelector('.spinner').classList.remove('hidden');
loadOsmData().then(() => {
@@ -416,6 +574,8 @@ ${station.state.unavailable ? `Bei ${station.state.unavailable} ${station.state.
}
});
})
+
+ filterData();
}
document.querySelector('#loadElevators')
@@ -424,7 +584,6 @@ document.querySelector('#loadElevators')
loadElevators().then(() => {
e.target.querySelector('.spinner').classList.add('hidden');
renderData();
- filterData();
});
})
@@ -434,19 +593,65 @@ document.querySelector('#initialLoad')
loadElevators().then(() => {
e.target.classList.add('hidden');
renderData();
- filterData();
});
})
-renderData();
+document.querySelector('#loadOsm')
+ .addEventListener('click', (e) => {
+ e.target.querySelector('.spinner').classList.remove('hidden');
+ loadOsmData().then(() => {
+ e.target.querySelector('.spinner').classList.add('hidden');
+ renderData();
+ closeDialog('#dialog_osm');
+ });
+ })
+
+document.querySelector('#stationsNearMe')
+ .addEventListener('click', async (e) => {
+ e.target.querySelector('.spinner').classList.remove('hidden');
+ if (!sortByDistance) {
+ if (JSON.parse(localStorage.getItem("osm_data")) === null) {
+ openDialog('#dialog_osm');
+ } else {
+ if (geolocationPermission !== 'granted') {
+ allowGeolocation();
+ } else {
+ // If geolocation is already set.
+ // If not the location watcher will re-render our data.
+ if (geolocation !== null) {
+ renderData(geolocation)
+ }
+ sortByDistance = e.target.ariaPressed = true;
+ }
+ }
+ } else {
+ sortByDistance = e.target.ariaPressed = false;
+
+ renderData();
+ }
+ e.target.querySelector('.spinner').classList.add('hidden');
+ })
function filterData() {
const searchString = document.querySelector('#searchStation').value;
+ const typeU = document.querySelector('button.typeChip[data-type="U"]');
+ const typeS = document.querySelector('button.typeChip[data-type="S"]');
+ const typeA = document.querySelector('button.typeChip[data-type="A"]');
+ const typeR = document.querySelector('button.typeChip[data-type="R"]');
+ const activeTypes = [];
+ if(typeU.ariaPressed === 'true') activeTypes.push('U');
+ if(typeS.ariaPressed === 'true') activeTypes.push('S');
+ if(typeA.ariaPressed === 'true') activeTypes.push('A');
+ if(typeR.ariaPressed === 'true') activeTypes.push('R');
if (internalData) {
for (const stationIndex in internalData.stations) {
- const matches = internalData.stations[stationIndex].name.toLowerCase().search(searchString.toLowerCase()) >= 0;
- document.querySelector(`#station_${stationIndex}`).classList.toggle('hidden', !matches);
+ const matchesSearch = internalData.stations[stationIndex].name.toLowerCase().search(searchString.toLowerCase()) >= 0;
+ let matchesType = false;
+ internalData.stations[stationIndex].types.forEach(type => {
+ if(activeTypes.includes(type)) matchesType = true;
+ })
+ document.querySelector(`#station_${stationIndex}`).classList.toggle('hidden', !(matchesSearch && matchesType));
}
}
}
@@ -455,4 +660,36 @@ document.querySelector('#searchStation').addEventListener('input', (e) => {
filterData();
})
-filterData()
\ No newline at end of file
+document.querySelectorAll('button.typeChip').forEach(e => {
+ e.addEventListener('click', (event) => {
+ e.ariaPressed = e.ariaPressed === 'true' ? 'false' : 'true';
+ filterData();
+ })
+})
+
+function openDialog(selector) {
+ document.querySelector('body').classList.add('has-dialog')
+ document.querySelector('#dialog_layer').classList.add('active')
+ document.querySelector(selector).classList.remove('hidden')
+}
+
+function closeDialog(selector) {
+ document.querySelector('body').classList.remove('has-dialog')
+ document.querySelector('#dialog_layer').classList.remove('active')
+ document.querySelector(selector).classList.add('hidden')
+}
+
+// data api version check
+const check_internal = JSON.parse(localStorage.getItem("internal_data"));
+const check_osm = JSON.parse(localStorage.getItem("osm_data"));
+if (check_internal === null || check_internal.hasOwnProperty('api') && check_internal.api === 'v1') {
+ if (check_osm === null || check_osm.hasOwnProperty('api') && check_osm.api === 'v1') {
+ renderData();
+ } else {
+ console.log('osm_data: version mismatch')
+ localStorage.removeItem('osm_data');
+ }
+} else {
+ console.log('internal_data: version mismatch')
+ localStorage.removeItem('internal_data');
+}
diff --git a/index.html b/index.html
index 9770960..58e4796 100644
--- a/index.html
+++ b/index.html
@@ -37,10 +37,17 @@
+
+ Leider ist ein Fehler beim Abrufen der Daten aufgetreten.
+
+
+
+
+
+
+
IPv4 RDAP Info
+
+
+
+ Um die Stationen nach Entfernung sortieren zu können müssen zusätzliche Daten von OpenStreetMap geladen
+ werden.
+
+
+
+