tracktracker/src/data/courses.js

165 lines
4.8 KiB
JavaScript
Raw Normal View History

/**
* @fileoverview
*
* Fetch and aggregate information about vehicles in transit on the TaM network
* from the official endpoints.
*/
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;
};
/** List of OSM nodes that are stops. */
const stopsSet = new Set(
Object.values(network.stops).map(stop => stop.properties.node)
);
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, its certainly
// the penultimate stop
if ((stop + "-" + finalStop) in network.segments) {
return true;
}
const route = routing.findPath(stop, finalStop);
2021-05-16 10:01:51 +00:00
// 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)) {
2021-05-16 10:01:51 +00:00
return false;
}
}
// Otherwise, assume its the penultimate stop
return true;
};
2021-05-11 15:20:12 +00:00
/**
* Fetch information about courses in the TaM network.
*
* @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.
*/
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
)
);
const lastPassing = course.passings[course.passings.length - 1];
2021-05-11 15:20:12 +00:00
if (course.finalStopId === undefined) {
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;
};