Sort passings, improve simulation

* use DFS to find path when passings are missing
* give passings sorted by increasing time
This commit is contained in:
Mattéo Delabre 2020-07-27 21:01:21 +02:00
parent 5db750adca
commit fe08c42920
Signed by: matteo
GPG Key ID: AE3FBD02DC583ABB
4 changed files with 717 additions and 93 deletions

View File

@ -88,7 +88,8 @@ a “ref” tag`);
stop.lat
], {
name: stop.tags.name,
routes: [[lineRef, routeRef]]
routes: [[lineRef, routeRef]],
successors: [],
});
} else {
stops[stop.tags.ref].properties.routes.push([
@ -223,6 +224,7 @@ different sequence of nodes in two or more lines.`);
routes: [[lineRef, routeRef]]
});
stops[begin].properties.successors.push(end);
segments[id].properties.length = (
1000 * turfLength(segments[id]));
}

File diff suppressed because it is too large Load Diff

View File

@ -13,8 +13,9 @@ let currentCourses = null;
* @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 {Object.<string,number>} nextPassings Next stations to which
* the vehicle will stop, associated to the passing timestamp.
* @property {Array.<Array>} nextPassings Next stations to which
* the vehicle will stop, associated to the passing timestamp, ordered by
* increasing passing timestamp.
*/
/**
@ -52,10 +53,10 @@ const fetch = async() => {
id,
line,
finalStop,
nextPassings: { [stopId]: arrivalTime }
nextPassings: [[stopId, arrivalTime]]
};
} 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)) {
delete courses[courseId];
} else {
for (const stopId of Object.keys(course.nextPassings)) {
for (const [stopId,] of course.nextPassings) {
if (!(stopId in network.stops)) {
delete courses[courseId];
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;
}

View File

@ -5,14 +5,38 @@ const network = require("./network.json");
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 {
constructor(data) {
this.id = data.id;
this.passings = {};
this.prevPassings = [];
this.nextPassings = [];
this.state = null;
// Attributes for the `stopped` state
this.currentStop = null;
this.departureTime = 0;
// Attributes for the `moving` state
this.departureStop = null;
@ -38,76 +62,118 @@ class Course {
updateData(data) {
this.line = data.line;
this.finalStop = data.finalStop;
Object.assign(this.passings, data.nextPassings);
this.nextPassings = data.nextPassings;
const now = Date.now();
// Make sure were on the right `stopped`/`moving` state
if (this.state === null) {
let previousStop = null;
let departureTime = 0;
// Initialize the course on the first available segment
const index = this.nextPassings.findIndex(
([, time]) => time >= now
);
let nextStop = null;
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) {
if (index === -1) {
return false;
}
if (previousStop === null) {
// Teleport to the first known stop
this.arriveToStop(nextStop);
if (index === 0) {
this.arriveToStop(this.nextPassings[index][0]);
} else {
// Teleport to the first known segment
this.arriveToStop(previousStop);
this.moveToStop(nextStop, arrivalTime);
this.arriveToStop(this.nextPassings[index - 1][0]);
this.moveToStop(...this.nextPassings[index]);
}
} else if (this.state === "moving") {
if (this.passings[this.arrivalStop] <= now) {
// Should already be at the next stop
this.arriveToStop(this.arrivalStop);
const index = this.nextPassings.findIndex(
([stop, ]) => stop === 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 {
// On the right track, update the arrival time
this.arrivalTime = this.passings[this.arrivalStop];
this.arrivalTime = this.nextPassings[index][1];
}
} else {
// (this.state === 'stopped')
// Try moving to the next stop
let nextStop = null;
let arrivalTime = Infinity;
const index = this.nextPassings.findIndex(
([stop, ]) => stop === this.currentStop
);
for (const [stopId, time] of Object.entries(this.passings)) {
if (time > now && time < arrivalTime) {
nextStop = stopId;
arrivalTime = time;
if (index !== -1) {
if (this.nextPassings[index][1] <= now) {
// Current stop is still announced but in the past
if (index + 1 < this.nextPassings.length) {
// Move to next stop
this.moveToStop(...this.nextPassings[index + 1]);
} else {
// No next stop announced, end of course
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 === null) {
// This course is finished
return false;
}
for (
let index = 0;
index < this.nextPassings.length;
++index
) {
const [stop, arrivalTime] = this.nextPassings[index];
if (nextStop !== this.currentStop) {
this.moveToStop(nextStop, arrivalTime);
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") {
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;
@ -115,7 +181,6 @@ class Course {
tick(time) {
if (this.state === "moving") {
// Integrate current speed in travelled distance
this.traveledDistance += this.speed * time;
const segment = this.currentSegment;
@ -155,10 +220,12 @@ class Course {
arriveToStop(stop) {
this.state = "stopped";
this.currentStop = stop;
this.departureTime = Date.now();
this.prevPassings.push([stop, Date.now()]);
this.position = (
turfProjection.toMercator(network.stops[stop])
.geometry.coordinates);
this.history.push(["arriveToStop", stop]);
.geometry.coordinates
);
}
/**
@ -168,49 +235,43 @@ class Course {
* @returns {undefined}
*/
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
${this.currentStop} to stop ${stop}. Teleporting to ${stop}`);
this.arriveToStop(stop);
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.departureStop = this.currentStop;
this.arrivalStop = stop;
this.arrivalTime = arrivalTime;
this.traveledDistance = 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`);
}
/**
* Compute the speed that needs to be maintained to arrive on time.
* @returns {number} Speed in meters per millisecond.
*/
computeTheoreticalSpeed() {
if (this.state !== "moving") {
computeSpeed(distance, duration) {
if (duration <= 0) {
// Late: go to maximum speed
return 50 / 3600;
}
if (distance / duration <= 10 / 3600) {
// Too slow: pause until speed is sufficient
return 0;
}
const segment = this.currentSegment;
const remainingTime = this.arrivalTime - Date.now();
const remainingDistance = (
segment.properties.length - this.traveledDistance
);
if (remainingDistance <= 0) {
return 0;
}
if (remainingTime <= 0) {
// Were late, go to maximum speed
return 50 / 3600; // 50 km/h
}
return remainingDistance / remainingTime;
return distance / duration;
}
}
@ -221,16 +282,12 @@ const updateData = async courses => {
for (const [id, data] of Object.entries(dataset)) {
if (id in courses) {
if (!courses[id].updateData(data)) {
console.info(`Course ${id} is finished.`);
delete courses[id];
}
} else {
const newCourse = new Course(data);
if (!newCourse.updateData(data)) {
console.info(`Ignoring course ${id} which is outdated.`);
} else {
console.info(`Course ${id} starting.`);
if (newCourse.updateData(data)) {
courses[id] = newCourse;
}
}