tracktracker/src/tam/courses.js

133 lines
3.8 KiB
JavaScript

/**
* @fileoverview
*
* Fetch and aggregate information about vehicles in transit on the TaM network
* from the official endpoints.
*/
const tam = require("./sources/tam");
const network = require("./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;
};
/**
* Fetch information about courses in the TaM network.
*
* @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.
*/
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]) {
course.passings.push([course.finalStopId, lastPassing[1] + 60000]);
}
}
return courses;
};
exports.fetch = fetch;