tracktracker/src/data/courses.js

165 lines
4.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @fileoverview
*
* Fetch and aggregate information about vehicles in transit on the TaM network
* from the official endpoints.
*/
import * as tam from "./sources/tam.js";
import * as routing from "./routing.js";
import network from "./network.json";
/**
* Information about the course of a vehicle.
* @typedef {Object} Course
* @property {string} id Unique identifier for this course.
* @property {string} line Transport line number.
* @property {string} finalStop Final stop to which the course is headed.
* @property {Array.<Array>} passings Next stations to which
* the vehicle will stop, associated to the passing timestamp, ordered by
* increasing passing timestamp.
*/
/** Parse time information relative to the current date. */
const parseTime = (time, reference) => {
const [hours, minutes, seconds] = time.split(':').map(x => parseInt(x, 10));
const result = new Date(reference);
result.setHours(hours);
result.setMinutes(minutes);
result.setSeconds(seconds);
if (reference > result.getTime()) {
// Timestamps in the past refer to the next day
result.setDate(result.getDate() + 1);
}
return result;
};
/** List of OSM nodes that are stops. */
const stopsSet = new Set(
Object.values(network.stops).map(stop => stop.properties.node)
);
/** Guess whether a stop comes directly before another one. */
const isPenultimateStop = (stop, finalStop) => {
// If there is a standard segment linking both stops, its certainly
// the penultimate stop
if ((stop + "-" + finalStop) in network.segments) {
return true;
}
const route = routing.findPath(stop, finalStop);
// If there is no way to link both stops, it cant be
if (route === null) {
return false;
}
// If there is another stop in the way, it cant be either
for (const nodeId of route.slice(1, -1)) {
if (stopsSet.has(nodeId)) {
return false;
}
}
// Otherwise, assume its the penultimate stop
return true;
};
/**
* Fetch information about courses in the TaM network.
*
* @async
* @param {string} kind Pass 'realtime' to get real-time information,
* or 'theoretical' to get planned courses for the day.
* @returns {Object.<string,Course>} Mapping from active course IDs to
* information about each course.
*/
export const fetch = async (kind = 'realtime') => {
const courses = {};
const passings = (
kind === 'realtime'
? tam.fetchRealtime()
: tam.fetchTheoretical()
);
const timing = (await passings.next()).value;
// Aggregate passings relative to the same course
for await (const passing of passings) {
const {
course: id,
routeShortName: line,
stopId,
destArCode: finalStopId,
} = passing;
const direction = (
'direction' in passing
? passing.direction
: passing.directionId
);
const departureTime = (
'delaySec' in passing
? timing.lastUpdate + parseInt(passing.delaySec, 10) * 1000
: parseTime(passing.departureTime, timing.lastUpdate)
);
if (!(id in courses)) {
courses[id] = {
id,
line,
direction,
finalStopId,
passings: {},
};
}
if (!(stopId in courses[id].passings) ||
courses[id].passings[stopId] < departureTime) {
// Only consider passings with an increased passing time
// or for stops not seen before
courses[id].passings[stopId] = departureTime;
}
}
// Filter courses to only keep those referring to known data
for (const courseId of Object.keys(courses)) {
const course = courses[courseId];
if (!(course.line in network.lines)) {
delete courses[courseId];
} else {
for (const stopId of Object.keys(course.passings)) {
if (!(stopId in network.stops)) {
delete courses[courseId];
break;
}
}
}
}
// Order next passings by increasing passing time
for (const course of Object.values(courses)) {
course.passings = (
Object.entries(course.passings).sort(
([, time1], [, time2]) => time1 - time2
)
);
const lastPassing = course.passings[course.passings.length - 1];
if (course.finalStopId === undefined) {
course.finalStopId = lastPassing[0];
} else if (course.finalStopId !== lastPassing[0]) {
if (isPenultimateStop(lastPassing[0], course.finalStopId)) {
course.passings.push([course.finalStopId, lastPassing[1] + 60000]);
}
}
}
return courses;
};