tracktracker/src/tam/simulation.js

245 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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))
{
// 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 were 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')
{
// Increase the travelled distance respective to the current speed
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;
}
// Recompute updated position
const departureStop = network.stops[course.departureStop];
const arrivalStop = network.stops[course.arrivalStop];
const nextNodeIndex = segment.points.findIndex(
({distance}) => distance >= course.traveledDistance);
if (nextNodeIndex === 0)
{
course.position = {
lat: departureStop.lat,
lon: departureStop.lon
};
}
else if (nextNodeIndex === -1)
{
course.position = {
lat: arrivalStop.lat,
lon: arrivalStop.lon
};
}
else
{
const previousNode = segment.points[nextNodeIndex - 1];
const nextNode = segment.points[nextNodeIndex];
const curLength = course.traveledDistance
- previousNode.distance;
const totalLength = nextNode.distance
- previousNode.distance;
const progression = curLength / totalLength;
course.position = {
lat: progression * nextNode.lat
+ (1 - progression) * previousNode.lat,
lon: progression * nextNode.lon
+ (1 - progression) * previousNode.lon,
};
}
}
else
{
const currentStop = network.stops[course.currentStop];
course.position = {
lat: currentStop.lat,
lon: currentStop.lon,
};
}
}
};
const run = callback =>
{
const courses = {};
let lastFrame = null;
let lastUpdate = null;
const loop = () =>
{
const now = Date.now();
if (lastUpdate === null || lastUpdate + 5000 <= now)
{
lastUpdate = now;
updateFromTam(courses);
}
const time = lastFrame === null ? 0 : now - lastFrame;
lastFrame = now;
updatePositions(courses, time);
callback(courses);
};
const interval = setInterval(loop, 24);
return () => clearInterval(interval);
};
exports.run = run;