const axios = require('axios'); const network = require('./network.json'); const server = 'http://localhost:4321'; const arriveAtStop = (course, stop) => { course.state = 'stopped'; course.currentStop = stop; delete course.departureStop; delete course.arrivalStop; delete course.arrivalTime; delete course.traveledDistance; delete course.speed; }; const moveToStop = (course, stop, arrivalTime) => { course.state = 'moving'; course.departureStop = course.currentStop; course.arrivalStop = stop; course.arrivalTime = arrivalTime; course.traveledDistance = 0; course.speed = 0; delete course.currentStop; const segment = `${course.departureStop}-${course.arrivalStop}`; if (!(segment in network.segments)) { // There is no segment between the two requested stops, jump // directly to the arrival stop arriveAtStop(course, course.arrivalStop); } else { updateSpeed(course); } }; const getCurrentSegment = course => { if (course.state === 'stopped') { return null; } return network.segments[`${course.departureStop}-${course.arrivalStop}`]; }; const updateSpeed = course => { const segment = getCurrentSegment(course); const length = segment.points[segment.points.length - 1].distance; const remainingTime = course.arrivalTime - Date.now(); const remainingDistance = length - course.traveledDistance; if (remainingTime <= 0 || remainingDistance <= 0) { arriveAtStop(course, course.arrivalStop); return; } course.speed = remainingDistance / remainingTime; }; const updateFromTam = async (courses) => { const currentCourses = (await axios.get(`${server}/courses`)).data; for (let [id, course] of Object.entries(currentCourses)) { // Only track selected lines if (!['1', '2', '3', '4'].includes(course.line)) { continue; } // Find out the next stop, ignoring the ones that are in the past let nextStop = null; let arrivalTime = null; for (let {stopId, arrivalTime: time} of course.nextPassings) { if (time > Date.now()) { nextStop = stopId; arrivalTime = time; break; } } if (nextStop === null) { continue; } // Update an existing course if (id in courses) { const prev = courses[id]; if (prev.state === 'stopped') { if (prev.currentStop !== nextStop) { // Start traveling from the current stop to the next moveToStop(prev, nextStop, arrivalTime); } } else { // Update the ETA if we’re still headed to the same stop if (prev.arrivalStop === nextStop) { prev.arrivalTime = arrivalTime; updateSpeed(prev); } // Otherwise, we missed a stop, try to go directly to the // next segment else { arriveAtStop(prev, prev.arrivalStop); moveToStop(prev, nextStop, arrivalTime); } } } // Create a new course else { courses[id] = { id, line: course.line, finalStop: course.finalStop, }; arriveAtStop(courses[id], nextStop); } } // Remove stale courses for (let id of Object.keys(courses)) { if (!(id in currentCourses)) { delete courses[id]; } } }; const updatePositions = (courses, time) => { for (let [id, course] of Object.entries(courses)) { if (course.state === 'moving') { const delta = course.speed * time; const segment = getCurrentSegment(course); const length = segment.points[segment.points.length - 1].distance; if (course.traveledDistance + delta >= length) { course.traveledDistance = length; } else { course.traveledDistance += delta; } } } }; const courses = {}; let lastFrame = null; let lastUpdate = null; const loop = now => { const time = lastFrame === null ? 0 : now - lastFrame; lastFrame = now; updatePositions(courses, time); if (lastUpdate === null || lastUpdate + 5000 <= now) { lastUpdate = now; updateFromTam(courses); } requestAnimationFrame(loop); }; requestAnimationFrame(loop);