Compare commits
2 Commits
5db750adca
...
d9869ce2f7
Author | SHA1 | Date |
---|---|---|
Mattéo Delabre | d9869ce2f7 | |
Mattéo Delabre | fe08c42920 |
|
@ -7,21 +7,31 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
body, html
|
body, html {
|
||||||
{
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#map
|
body {
|
||||||
{
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
#informations {
|
||||||
|
flex: 0 0 600px;
|
||||||
|
padding: 20px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#map {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<div id="informations"></div>
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -1,6 +1,89 @@
|
||||||
// eslint-disable-next-line node/no-extraneous-require
|
// eslint-disable-next-line node/no-extraneous-require
|
||||||
require("regenerator-runtime/runtime");
|
require("regenerator-runtime/runtime");
|
||||||
|
|
||||||
|
const network = require("../tam/network.json");
|
||||||
|
const simulation = require("../tam/simulation");
|
||||||
const map = require("./map/index.js");
|
const map = require("./map/index.js");
|
||||||
|
|
||||||
map.create(/* map = */ "map");
|
// Run courses simulation
|
||||||
|
const coursesSimulation = simulation.start();
|
||||||
|
global.courses = coursesSimulation.courses;
|
||||||
|
|
||||||
|
const informations = document.querySelector("#informations");
|
||||||
|
let courseId = null;
|
||||||
|
|
||||||
|
const displayTime = date => [
|
||||||
|
date.getHours(),
|
||||||
|
date.getMinutes(),
|
||||||
|
date.getSeconds()
|
||||||
|
].map(number => number.toString().padStart(2, '0')).join(':');
|
||||||
|
|
||||||
|
setInterval(() => {
|
||||||
|
let html = `
|
||||||
|
<dl>
|
||||||
|
<dt>Heure actuelle</dt>
|
||||||
|
<dd>${displayTime(new Date())}</dd>
|
||||||
|
</dl>
|
||||||
|
`;
|
||||||
|
|
||||||
|
if (courseId !== null && courseId in coursesSimulation.courses) {
|
||||||
|
const course = coursesSimulation.courses[courseId];
|
||||||
|
|
||||||
|
const timeToHTML = time => Math.ceil((time - Date.now()) / 1000);
|
||||||
|
|
||||||
|
const stopToHTML = stopId => network.stops[stopId].properties.name;
|
||||||
|
|
||||||
|
const passingsToHTML = passings => passings.map(([stopId, time]) => `
|
||||||
|
<tr>
|
||||||
|
<td>${stopToHTML(stopId)}</td>
|
||||||
|
<td>${displayTime(new Date(time))}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('\n');
|
||||||
|
|
||||||
|
html += `
|
||||||
|
<dl>
|
||||||
|
<dt>ID</dt>
|
||||||
|
<dd>${courseId}</dd>
|
||||||
|
|
||||||
|
<dt>Ligne</dt>
|
||||||
|
<dd>${course.line}</dd>
|
||||||
|
|
||||||
|
<dt>Destination</dt>
|
||||||
|
<dd>${stopToHTML(course.finalStop)}</dd>
|
||||||
|
|
||||||
|
<dt>État</dt>
|
||||||
|
<dd>${course.state === 'moving'
|
||||||
|
? `Entre ${stopToHTML(course.departureStop)}
|
||||||
|
et ${stopToHTML(course.arrivalStop)}`
|
||||||
|
: `À l’arrêt ${stopToHTML(course.currentStop)}`}</dd>
|
||||||
|
|
||||||
|
${course.state === 'moving' ? `
|
||||||
|
<dt>Arrivée dans</dt>
|
||||||
|
<dd>${timeToHTML(course.arrivalTime)} s</dd>
|
||||||
|
|
||||||
|
<dt>Distance parcourue</dt>
|
||||||
|
<dd>${Math.ceil(course.traveledDistance)} m</dd>
|
||||||
|
|
||||||
|
<dt>Vitesse</dt>
|
||||||
|
<dd>${Math.ceil(course.speed * 3600)} km/h</dd>
|
||||||
|
` : `
|
||||||
|
<dt>Départ dans</dt>
|
||||||
|
<dd>${timeToHTML(course.departureTime)} s</dd>
|
||||||
|
`}
|
||||||
|
</dl>
|
||||||
|
|
||||||
|
<h2>Arrêts précédents</h2>
|
||||||
|
<table>${passingsToHTML(course.prevPassings)}</table>
|
||||||
|
|
||||||
|
<h2>Arrêts suivants</h2>
|
||||||
|
<table>${passingsToHTML(course.nextPassings)}</table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
informations.innerHTML = html;
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
// Create the network and courses map
|
||||||
|
map.create(/* map = */ "map", coursesSimulation, course => {
|
||||||
|
courseId = course;
|
||||||
|
});
|
||||||
|
|
|
@ -9,7 +9,6 @@ const tilesLayers = require("./tiles");
|
||||||
const networkLayers = require("./network");
|
const networkLayers = require("./network");
|
||||||
const { sizes, makeBorderColor, makeCourseColor } = require("./common");
|
const { sizes, makeBorderColor, makeCourseColor } = require("./common");
|
||||||
const network = require("../../tam/network.json");
|
const network = require("../../tam/network.json");
|
||||||
const simulation = require("../../tam/simulation");
|
|
||||||
|
|
||||||
const courseStyles = {};
|
const courseStyles = {};
|
||||||
|
|
||||||
|
@ -59,7 +58,7 @@ const getCourseStyle = lineColor => {
|
||||||
return courseStyles[lineColor];
|
return courseStyles[lineColor];
|
||||||
};
|
};
|
||||||
|
|
||||||
const create = target => {
|
const create = (target, coursesSimulation, onClick) => {
|
||||||
const view = new View({
|
const view = new View({
|
||||||
center: proj.fromLonLat([3.88, 43.605]),
|
center: proj.fromLonLat([3.88, 43.605]),
|
||||||
zoom: 14,
|
zoom: 14,
|
||||||
|
@ -78,32 +77,9 @@ const create = target => {
|
||||||
|
|
||||||
const stopsLayer = map.getLayers().item(3);
|
const stopsLayer = map.getLayers().item(3);
|
||||||
|
|
||||||
// Run courses simulation
|
|
||||||
const simulInstance = simulation.start();
|
|
||||||
|
|
||||||
// Course on which the view is currently focused
|
|
||||||
let focusedCourse = null;
|
|
||||||
|
|
||||||
const startFocus = courseId => {
|
|
||||||
if (courseId in simulInstance.courses) {
|
|
||||||
const course = simulInstance.courses[courseId];
|
|
||||||
|
|
||||||
view.animate({
|
|
||||||
center: course.position,
|
|
||||||
duration: 500
|
|
||||||
}, () => {
|
|
||||||
focusedCourse = courseId;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const stopFocus = () => {
|
|
||||||
focusedCourse = null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Draw courses directly on the map
|
// Draw courses directly on the map
|
||||||
map.on("postcompose", ev => {
|
map.on("postcompose", ev => {
|
||||||
simulInstance.update();
|
coursesSimulation.update();
|
||||||
|
|
||||||
// The normal way to access a layer’s vector context is through the
|
// The normal way to access a layer’s vector context is through the
|
||||||
// `postrender` event of that layer. However, `postrender` is not
|
// `postrender` event of that layer. However, `postrender` is not
|
||||||
|
@ -121,7 +97,8 @@ const create = target => {
|
||||||
|
|
||||||
const ctx = getVectorContext(ev);
|
const ctx = getVectorContext(ev);
|
||||||
|
|
||||||
for (const course of Object.values(simulInstance.courses)) {
|
for (const course of Object.values(coursesSimulation.courses)) {
|
||||||
|
const point = new Point(course.position);
|
||||||
const color = network.lines[course.line].color;
|
const color = network.lines[course.line].color;
|
||||||
const style = getCourseStyle(color);
|
const style = getCourseStyle(color);
|
||||||
|
|
||||||
|
@ -131,14 +108,7 @@ const create = target => {
|
||||||
);
|
);
|
||||||
|
|
||||||
ctx.setStyle(style);
|
ctx.setStyle(style);
|
||||||
|
|
||||||
const point = new Point(course.position);
|
|
||||||
|
|
||||||
ctx.drawGeometry(point);
|
ctx.drawGeometry(point);
|
||||||
|
|
||||||
if (course.id === focusedCourse) {
|
|
||||||
view.setCenter(course.position);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,22 +119,18 @@ const create = target => {
|
||||||
|
|
||||||
map.on("singleclick", ev => {
|
map.on("singleclick", ev => {
|
||||||
const mousePixel = map.getPixelFromCoordinate(ev.coordinate);
|
const mousePixel = map.getPixelFromCoordinate(ev.coordinate);
|
||||||
const maxDistance = sizes.courseSize + sizes.courseOuterBorder;
|
const maxDistance = sizes.courseSize + sizes.courseInnerBorder;
|
||||||
|
|
||||||
for (const course of Object.values(simulInstance.courses)) {
|
for (const course of Object.values(coursesSimulation.courses)) {
|
||||||
const coursePixel = map.getPixelFromCoordinate(course.position);
|
const coursePixel = map.getPixelFromCoordinate(course.position);
|
||||||
const dx = mousePixel[0] - coursePixel[0];
|
const dx = mousePixel[0] - coursePixel[0];
|
||||||
const dy = mousePixel[1] - coursePixel[1];
|
const dy = mousePixel[1] - coursePixel[1];
|
||||||
const distance = dx * dx + dy * dy;
|
const distance = dx * dx + dy * dy;
|
||||||
|
|
||||||
if (distance <= maxDistance * maxDistance) {
|
if (distance <= maxDistance * maxDistance) {
|
||||||
startFocus(course.id);
|
onClick(course.id);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clicking anywhere else resets focus
|
|
||||||
stopFocus();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
|
|
|
@ -88,7 +88,8 @@ a “ref” tag`);
|
||||||
stop.lat
|
stop.lat
|
||||||
], {
|
], {
|
||||||
name: stop.tags.name,
|
name: stop.tags.name,
|
||||||
routes: [[lineRef, routeRef]]
|
routes: [[lineRef, routeRef]],
|
||||||
|
successors: [],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
stops[stop.tags.ref].properties.routes.push([
|
stops[stop.tags.ref].properties.routes.push([
|
||||||
|
@ -223,6 +224,7 @@ different sequence of nodes in two or more lines.`);
|
||||||
routes: [[lineRef, routeRef]]
|
routes: [[lineRef, routeRef]]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
stops[begin].properties.successors.push(end);
|
||||||
segments[id].properties.length = (
|
segments[id].properties.length = (
|
||||||
1000 * turfLength(segments[id]));
|
1000 * turfLength(segments[id]));
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,8 +13,9 @@ let currentCourses = null;
|
||||||
* @property {string} id Unique identifier for this course.
|
* @property {string} id Unique identifier for this course.
|
||||||
* @property {string} line Transport line number.
|
* @property {string} line Transport line number.
|
||||||
* @property {string} finalStop Final stop to which the course is headed.
|
* @property {string} finalStop Final stop to which the course is headed.
|
||||||
* @property {Object.<string,number>} nextPassings Next stations to which
|
* @property {Array.<Array>} nextPassings Next stations to which
|
||||||
* the vehicle will stop, associated to the passing timestamp.
|
* the vehicle will stop, associated to the passing timestamp, ordered by
|
||||||
|
* increasing passing timestamp.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,10 +53,10 @@ const fetch = async() => {
|
||||||
id,
|
id,
|
||||||
line,
|
line,
|
||||||
finalStop,
|
finalStop,
|
||||||
nextPassings: { [stopId]: arrivalTime }
|
nextPassings: [[stopId, arrivalTime]]
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
courses[id].nextPassings[stopId] = arrivalTime;
|
courses[id].nextPassings.push([stopId, arrivalTime]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,7 +67,7 @@ const fetch = async() => {
|
||||||
if (!(course.line in network.lines)) {
|
if (!(course.line in network.lines)) {
|
||||||
delete courses[courseId];
|
delete courses[courseId];
|
||||||
} else {
|
} else {
|
||||||
for (const stopId of Object.keys(course.nextPassings)) {
|
for (const [stopId,] of course.nextPassings) {
|
||||||
if (!(stopId in network.stops)) {
|
if (!(stopId in network.stops)) {
|
||||||
delete courses[courseId];
|
delete courses[courseId];
|
||||||
break;
|
break;
|
||||||
|
@ -75,6 +76,13 @@ const fetch = async() => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Order next passings by increasing passing time
|
||||||
|
for (const courseId of Object.keys(courses)) {
|
||||||
|
courses[courseId].nextPassings.sort(
|
||||||
|
([, time1], [, time2]) => time1 - time2
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
currentCourses = courses;
|
currentCourses = courses;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,14 +5,38 @@ const network = require("./network.json");
|
||||||
|
|
||||||
const server = "http://localhost:4321";
|
const server = "http://localhost:4321";
|
||||||
|
|
||||||
|
const stops = Object.keys(network.stops);
|
||||||
|
|
||||||
|
const findRoute = (from, to) => {
|
||||||
|
const queue = [[from, []]];
|
||||||
|
|
||||||
|
while (queue.length) {
|
||||||
|
const [head, path] = queue.shift();
|
||||||
|
|
||||||
|
for (const successor of network.stops[head].properties.successors) {
|
||||||
|
if (successor === to) {
|
||||||
|
return path.concat([head, successor]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.includes(successor)) {
|
||||||
|
queue.push([successor, path.concat([head])]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
class Course {
|
class Course {
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
this.id = data.id;
|
this.id = data.id;
|
||||||
this.passings = {};
|
this.prevPassings = [];
|
||||||
|
this.nextPassings = [];
|
||||||
this.state = null;
|
this.state = null;
|
||||||
|
|
||||||
// Attributes for the `stopped` state
|
// Attributes for the `stopped` state
|
||||||
this.currentStop = null;
|
this.currentStop = null;
|
||||||
|
this.departureTime = 0;
|
||||||
|
|
||||||
// Attributes for the `moving` state
|
// Attributes for the `moving` state
|
||||||
this.departureStop = null;
|
this.departureStop = null;
|
||||||
|
@ -38,76 +62,118 @@ class Course {
|
||||||
updateData(data) {
|
updateData(data) {
|
||||||
this.line = data.line;
|
this.line = data.line;
|
||||||
this.finalStop = data.finalStop;
|
this.finalStop = data.finalStop;
|
||||||
Object.assign(this.passings, data.nextPassings);
|
this.nextPassings = data.nextPassings;
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// Make sure we’re on the right `stopped`/`moving` state
|
|
||||||
if (this.state === null) {
|
if (this.state === null) {
|
||||||
let previousStop = null;
|
// Initialize the course on the first available segment
|
||||||
let departureTime = 0;
|
const index = this.nextPassings.findIndex(
|
||||||
|
([, time]) => time >= now
|
||||||
|
);
|
||||||
|
|
||||||
let nextStop = null;
|
if (index === -1) {
|
||||||
let arrivalTime = Infinity;
|
|
||||||
|
|
||||||
for (const [stopId, time] of Object.entries(this.passings)) {
|
|
||||||
if (time > now && time < arrivalTime) {
|
|
||||||
nextStop = stopId;
|
|
||||||
arrivalTime = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (time < now && time > departureTime) {
|
|
||||||
previousStop = stopId;
|
|
||||||
departureTime = time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (nextStop === null) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (previousStop === null) {
|
if (index === 0) {
|
||||||
// Teleport to the first known stop
|
this.arriveToStop(this.nextPassings[index][0]);
|
||||||
this.arriveToStop(nextStop);
|
|
||||||
} else {
|
} else {
|
||||||
|
this.arriveToStop(this.nextPassings[index - 1][0]);
|
||||||
// Teleport to the first known segment
|
this.moveToStop(...this.nextPassings[index]);
|
||||||
this.arriveToStop(previousStop);
|
|
||||||
this.moveToStop(nextStop, arrivalTime);
|
|
||||||
}
|
}
|
||||||
} else if (this.state === "moving") {
|
} else if (this.state === "moving") {
|
||||||
if (this.passings[this.arrivalStop] <= now) {
|
const index = this.nextPassings.findIndex(
|
||||||
// Should already be at the next stop
|
([stop, ]) => stop === this.arrivalStop
|
||||||
this.arriveToStop(this.arrivalStop);
|
);
|
||||||
|
|
||||||
|
if (index === -1 || this.nextPassings[index][1] <= now) {
|
||||||
|
// Next stop is not announced or in the past,
|
||||||
|
// move towards it as fast as possible
|
||||||
|
this.arrivalTime = now;
|
||||||
} else {
|
} else {
|
||||||
// On the right track, update the arrival time
|
// On the right track, update the arrival time
|
||||||
this.arrivalTime = this.passings[this.arrivalStop];
|
this.arrivalTime = this.nextPassings[index][1];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// (this.state === 'stopped')
|
// (this.state === 'stopped')
|
||||||
// Try moving to the next stop
|
// Try moving to the next stop
|
||||||
let nextStop = null;
|
const index = this.nextPassings.findIndex(
|
||||||
let arrivalTime = Infinity;
|
([stop, ]) => stop === this.currentStop
|
||||||
|
);
|
||||||
|
|
||||||
for (const [stopId, time] of Object.entries(this.passings)) {
|
if (index !== -1) {
|
||||||
if (time > now && time < arrivalTime) {
|
if (this.nextPassings[index][1] <= now) {
|
||||||
nextStop = stopId;
|
// Current stop is still announced but in the past
|
||||||
arrivalTime = time;
|
if (index + 1 < this.nextPassings.length) {
|
||||||
}
|
// Move to next stop
|
||||||
}
|
this.moveToStop(...this.nextPassings[index + 1]);
|
||||||
|
} else {
|
||||||
if (nextStop === null) {
|
// No next stop announced, end of course
|
||||||
// This course is finished
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Cannot move yet, departure is in the future
|
||||||
|
this.departureTime = this.nextPassings[index][1];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Current stop is not announced, find the first stop
|
||||||
|
// announced in the future to which is connection is
|
||||||
|
// possible
|
||||||
|
let found = false;
|
||||||
|
|
||||||
if (nextStop !== this.currentStop) {
|
for (
|
||||||
this.moveToStop(nextStop, arrivalTime);
|
let index = 0;
|
||||||
|
index < this.nextPassings.length;
|
||||||
|
++index
|
||||||
|
) {
|
||||||
|
const [stop, arrivalTime] = this.nextPassings[index];
|
||||||
|
|
||||||
|
if (arrivalTime > now) {
|
||||||
|
const route = findRoute(this.currentStop, stop);
|
||||||
|
|
||||||
|
if (route !== null) {
|
||||||
|
// Move to the first intermediate stop, guess the
|
||||||
|
// arrival time based on the final arrival time and
|
||||||
|
// the relative distance of the stops
|
||||||
|
const midDistance = network.segments[
|
||||||
|
`${route[0]}-${route[1]}`
|
||||||
|
].properties.length;
|
||||||
|
|
||||||
|
let totalDistance = midDistance;
|
||||||
|
|
||||||
|
for (
|
||||||
|
let midIndex = 1;
|
||||||
|
midIndex + 1 < route.length;
|
||||||
|
++midIndex
|
||||||
|
) {
|
||||||
|
totalDistance += network.segments[
|
||||||
|
`${route[midIndex]}-${route[midIndex + 1]}`
|
||||||
|
].properties.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
const midTime = now + (arrivalTime - now) *
|
||||||
|
midDistance / totalDistance;
|
||||||
|
|
||||||
|
this.moveToStop(route[1], midTime);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
// No valid next stop available
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state === "moving") {
|
if (this.state === "moving") {
|
||||||
this.speed = this.computeTheoreticalSpeed();
|
const segment = this.currentSegment;
|
||||||
|
const distance = segment.properties.length - this.traveledDistance;
|
||||||
|
const duration = this.arrivalTime - Date.now();
|
||||||
|
this.speed = this.computeSpeed(distance, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
@ -115,7 +181,6 @@ class Course {
|
||||||
|
|
||||||
tick(time) {
|
tick(time) {
|
||||||
if (this.state === "moving") {
|
if (this.state === "moving") {
|
||||||
|
|
||||||
// Integrate current speed in travelled distance
|
// Integrate current speed in travelled distance
|
||||||
this.traveledDistance += this.speed * time;
|
this.traveledDistance += this.speed * time;
|
||||||
const segment = this.currentSegment;
|
const segment = this.currentSegment;
|
||||||
|
@ -155,10 +220,12 @@ class Course {
|
||||||
arriveToStop(stop) {
|
arriveToStop(stop) {
|
||||||
this.state = "stopped";
|
this.state = "stopped";
|
||||||
this.currentStop = stop;
|
this.currentStop = stop;
|
||||||
|
this.departureTime = Date.now();
|
||||||
|
this.prevPassings.push([stop, Date.now()]);
|
||||||
this.position = (
|
this.position = (
|
||||||
turfProjection.toMercator(network.stops[stop])
|
turfProjection.toMercator(network.stops[stop])
|
||||||
.geometry.coordinates);
|
.geometry.coordinates
|
||||||
this.history.push(["arriveToStop", stop]);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,49 +235,43 @@ class Course {
|
||||||
* @returns {undefined}
|
* @returns {undefined}
|
||||||
*/
|
*/
|
||||||
moveToStop(stop, arrivalTime) {
|
moveToStop(stop, arrivalTime) {
|
||||||
if (!(`${this.currentStop}-${stop}` in network.segments)) {
|
const segmentId = `${this.currentStop}-${stop}`;
|
||||||
|
|
||||||
|
if (!(segmentId in network.segments)) {
|
||||||
console.warn(`Course ${this.id} cannot go from stop
|
console.warn(`Course ${this.id} cannot go from stop
|
||||||
${this.currentStop} to stop ${stop}. Teleporting to ${stop}`);
|
${this.currentStop} to stop ${stop}. Teleporting to ${stop}`);
|
||||||
this.arriveToStop(stop);
|
this.arriveToStop(stop);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const distance = network.segments[segmentId].properties.length;
|
||||||
|
const duration = arrivalTime - Date.now();
|
||||||
|
|
||||||
|
if (this.computeSpeed(distance, duration) === 0) {
|
||||||
|
// Speed would be too low, better wait for some time
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
this.state = "moving";
|
this.state = "moving";
|
||||||
this.departureStop = this.currentStop;
|
this.departureStop = this.currentStop;
|
||||||
this.arrivalStop = stop;
|
this.arrivalStop = stop;
|
||||||
this.arrivalTime = arrivalTime;
|
this.arrivalTime = arrivalTime;
|
||||||
this.traveledDistance = 0;
|
this.traveledDistance = 0;
|
||||||
this.speed = 0;
|
this.speed = 0;
|
||||||
this.history.push(["moveToStop", stop, arrivalTime]);
|
|
||||||
|
|
||||||
console.info(`Course ${this.id} leaving stop ${this.currentStop} \
|
|
||||||
with initial speed ${this.computeTheoreticalSpeed() * 3600} km/h`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
computeSpeed(distance, duration) {
|
||||||
* Compute the speed that needs to be maintained to arrive on time.
|
if (duration <= 0) {
|
||||||
* @returns {number} Speed in meters per millisecond.
|
// Late: go to maximum speed
|
||||||
*/
|
return 50 / 3600;
|
||||||
computeTheoreticalSpeed() {
|
}
|
||||||
if (this.state !== "moving") {
|
|
||||||
|
if (distance / duration <= 10 / 3600) {
|
||||||
|
// Too slow: pause until speed is sufficient
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const segment = this.currentSegment;
|
return distance / duration;
|
||||||
const remainingTime = this.arrivalTime - Date.now();
|
|
||||||
const remainingDistance = (
|
|
||||||
segment.properties.length - this.traveledDistance
|
|
||||||
);
|
|
||||||
|
|
||||||
if (remainingDistance <= 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (remainingTime <= 0) {
|
|
||||||
// We’re late, go to maximum speed
|
|
||||||
return 50 / 3600; // 50 km/h
|
|
||||||
}
|
|
||||||
|
|
||||||
return remainingDistance / remainingTime;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -221,16 +282,12 @@ const updateData = async courses => {
|
||||||
for (const [id, data] of Object.entries(dataset)) {
|
for (const [id, data] of Object.entries(dataset)) {
|
||||||
if (id in courses) {
|
if (id in courses) {
|
||||||
if (!courses[id].updateData(data)) {
|
if (!courses[id].updateData(data)) {
|
||||||
console.info(`Course ${id} is finished.`);
|
|
||||||
delete courses[id];
|
delete courses[id];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const newCourse = new Course(data);
|
const newCourse = new Course(data);
|
||||||
|
|
||||||
if (!newCourse.updateData(data)) {
|
if (newCourse.updateData(data)) {
|
||||||
console.info(`Ignoring course ${id} which is outdated.`);
|
|
||||||
} else {
|
|
||||||
console.info(`Course ${id} starting.`);
|
|
||||||
courses[id] = newCourse;
|
courses[id] = newCourse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue