/** * @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.} 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.} 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;