From 0d7b3848211fbd0f450bf3677911749c5a4fd6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matt=C3=A9o=20Delabre?= Date: Sun, 19 Jul 2020 01:45:36 +0200 Subject: [PATCH] front: Initial implementation of simulation --- front/data.js | 195 +++++++++++++++++++++++++++++++++++++++++++++---- front/index.js | 3 + 2 files changed, 182 insertions(+), 16 deletions(-) diff --git a/front/data.js b/front/data.js index fe61b39..eaabce6 100644 --- a/front/data.js +++ b/front/data.js @@ -1,32 +1,195 @@ +const axios = require('axios'); +const network = require('../back/data/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 getCurrentCourses(); + const currentCourses = (await axios.get(`${server}/courses`)).data; for (let [id, course] of Object.entries(currentCourses)) { - if (!(id in courses)) + // Only track selected lines + if (!['1', '2', '3', '4'].includes(course.line)) { - course.arrivalTime = now() + course.eta; - console.log(`${displayNow(now())} - New course ${id} @ ${course.line} departing from stop ${course.stop} at ${displayNow(course.arrivalTime)}`); - courses[id] = course; + 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 { - course.arrivalTime = now() + course.eta; - console.log(`${displayNow(now())} - Course ${id} @ ${course.line} will arrive to stop ${course.stop} at ${displayNow(course.arrivalTime)} (previously to stop ${courses[id].stop} at ${displayNow(courses[id].arrivalTime)})`); - courses[id] = course; + 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 sleep = time => new Promise(res => setTimeout(res, time)); -const courses = {}; - -const loop = async (courses = {}) => +const updatePositions = (courses, time) => { - await updateFromTam(courses); - await sleep(30000); - return loop(courses); + 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; + } + } + } }; -loop(); +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); diff --git a/front/index.js b/front/index.js index 4691e3e..fdff2fd 100644 --- a/front/index.js +++ b/front/index.js @@ -1,4 +1,7 @@ require('regenerator-runtime/runtime'); const {createMap} = require('./map'); + +require('./data'); + createMap(/* map = */ 'map');