2021-05-11 16:20:47 +00:00
|
|
|
|
/**
|
|
|
|
|
* @fileoverview
|
|
|
|
|
*
|
|
|
|
|
* Fetch and aggregate information about vehicles in transit on the TaM network
|
|
|
|
|
* from the official endpoints.
|
|
|
|
|
*/
|
|
|
|
|
|
2021-05-11 19:39:24 +00:00
|
|
|
|
import * as tam from "./sources/tam.js";
|
2021-05-16 10:01:51 +00:00
|
|
|
|
import * as routing from "./routing.js";
|
|
|
|
|
import network from "./network.json";
|
2021-05-11 15:20:12 +00:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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. */
|
2021-05-16 10:01:51 +00:00
|
|
|
|
const parseTime = (time, reference) => {
|
2021-05-11 15:20:12 +00:00
|
|
|
|
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;
|
|
|
|
|
};
|
|
|
|
|
|
2021-05-16 10:01:51 +00:00
|
|
|
|
/** Guess whether a stop comes directly before another one. */
|
|
|
|
|
const isPenultimateStop = (stop, finalStop) => {
|
|
|
|
|
// If there is a standard segment linking both stops, it’s certainly
|
|
|
|
|
// the penultimate stop
|
|
|
|
|
if ((stop + "-" + finalStop) in network.segments) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-21 21:59:51 +00:00
|
|
|
|
const route = routing.findPath(stop, finalStop);
|
2021-05-16 10:01:51 +00:00
|
|
|
|
|
|
|
|
|
// If there is no way to link both stops, it can’t be
|
|
|
|
|
if (route === null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// If there is another stop in the way, it can’t be either
|
|
|
|
|
for (const node of route.slice(1, -1)) {
|
|
|
|
|
if (node in network.stops) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Otherwise, assume it’s the penultimate stop
|
|
|
|
|
return true;
|
|
|
|
|
};
|
|
|
|
|
|
2021-05-11 15:20:12 +00:00
|
|
|
|
/**
|
|
|
|
|
* Fetch information about courses in the TaM network.
|
|
|
|
|
*
|
2021-05-11 19:39:24 +00:00
|
|
|
|
* @async
|
2021-05-11 15:20:12 +00:00
|
|
|
|
* @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.
|
|
|
|
|
*/
|
2021-05-11 19:39:24 +00:00
|
|
|
|
export const fetch = async (kind = 'realtime') => {
|
2021-05-11 15:20:12 +00:00
|
|
|
|
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
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
2021-05-11 16:20:47 +00:00
|
|
|
|
const lastPassing = course.passings[course.passings.length - 1];
|
|
|
|
|
|
2021-05-11 15:20:12 +00:00
|
|
|
|
if (course.finalStopId === undefined) {
|
2021-05-11 16:20:47 +00:00
|
|
|
|
course.finalStopId = lastPassing[0];
|
|
|
|
|
} else if (course.finalStopId !== lastPassing[0]) {
|
2021-05-16 10:01:51 +00:00
|
|
|
|
if (isPenultimateStop(lastPassing[0], course.finalStopId)) {
|
|
|
|
|
course.passings.push([course.finalStopId, lastPassing[1] + 60000]);
|
|
|
|
|
}
|
2021-05-11 15:20:12 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return courses;
|
|
|
|
|
};
|