Refonte du code de simulation
This commit is contained in:
		
							parent
							
								
									7a418f8ada
								
							
						
					
					
						commit
						6d76d874a1
					
				|  | @ -4,8 +4,8 @@ | |||
|   "type": "module", | ||||
|   "scripts": { | ||||
|     "back": "node src/back", | ||||
|     "front:dev": "vite", | ||||
|     "front:prod": "vite build", | ||||
|     "front:dev": "vite src/front", | ||||
|     "front:prod": "vite build src/front", | ||||
|     "lint": "eslint ." | ||||
|   }, | ||||
|   "dependencies": { | ||||
|  |  | |||
|  | @ -33,6 +33,6 @@ | |||
|     <body> | ||||
|         <aside id="panel"></aside> | ||||
|         <div id="map"></div> | ||||
|         <script type="module" src="index.js"></script> | ||||
|         <script type="module" src="/index.js"></script> | ||||
|     </body> | ||||
| </html> | ||||
|  |  | |||
|  | @ -1,6 +1,6 @@ | |||
| import network from "../tam/network.json"; | ||||
| import * as simulation from "../tam/simulation"; | ||||
| import * as map from "./map/index"; | ||||
| import * as simulation from "../tam/simulation.js"; | ||||
| import * as map from "./map/index.js"; | ||||
| 
 | ||||
| // Run courses simulation
 | ||||
| const coursesSimulation = simulation.start(); | ||||
|  | @ -15,6 +15,18 @@ const displayTime = date => [ | |||
|     date.getSeconds() | ||||
| ].map(number => number.toString().padStart(2, "0")).join(":"); | ||||
| 
 | ||||
| const timeToHTML = time => { | ||||
|     const delta = Math.ceil((time - Date.now()) / 1000); | ||||
| 
 | ||||
|     if (delta <= 0) { | ||||
|         return `Imminent`; | ||||
|     } else if (delta < 60) { | ||||
|         return `${delta} s`; | ||||
|     } else { | ||||
|         return `${Math.floor(delta / 60)} min ${delta % 60} s`; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| setInterval(() => { | ||||
|     let html = ` | ||||
|         <dl> | ||||
|  | @ -26,8 +38,6 @@ setInterval(() => { | |||
|     if (courseId !== null && courseId in coursesSimulation.courses) { | ||||
|         const course = coursesSimulation.courses[courseId]; | ||||
| 
 | ||||
|         const timeToHTML = time => Math.ceil((time - Date.now()) / 1000); | ||||
| 
 | ||||
|         const stopToHTML = stopId => stopId in network.stops ? | ||||
|             network.stops[stopId].properties.name : | ||||
|             '<em>Arrêt inconnu</em>'; | ||||
|  | @ -39,6 +49,17 @@ setInterval(() => { | |||
|             </tr> | ||||
|         `).join("\n");
 | ||||
| 
 | ||||
|         const state = ( | ||||
|             course.traveledDistance === 0 && course.speed === 0 | ||||
|                 ? "stopped" : "moving" | ||||
|         ); | ||||
| 
 | ||||
|         let prevPassings = course.prevPassings; | ||||
| 
 | ||||
|         if (state === "moving") { | ||||
|             prevPassings = prevPassings.concat([[course.departureStop, course.departureTime]]); | ||||
|         } | ||||
| 
 | ||||
|         html += ` | ||||
|             <dl> | ||||
|                 <dt>ID</dt> | ||||
|  | @ -51,14 +72,14 @@ setInterval(() => { | |||
|                 <dd>${stopToHTML(course.finalStop)}</dd> | ||||
| 
 | ||||
|                 <dt>État</dt> | ||||
|                 <dd>${course.state === "moving" | ||||
|                 <dd>${state === "moving" | ||||
|         ? `Entre ${stopToHTML(course.departureStop)} | ||||
|             et ${stopToHTML(course.arrivalStop)}` | ||||
|         : `À l’arrêt ${stopToHTML(course.currentStop)}`}</dd> | ||||
|         : `À l’arrêt ${stopToHTML(course.departureStop)}`}</dd> | ||||
| 
 | ||||
|                 ${course.state === "moving" ? ` | ||||
|                 ${state === "moving" ? ` | ||||
|                     <dt>Arrivée dans</dt> | ||||
|                     <dd>${timeToHTML(course.arrivalTime)} s</dd> | ||||
|                     <dd>${timeToHTML(course.arrivalTime - 10000)}</dd> | ||||
| 
 | ||||
|                     <dt>Distance parcourue</dt> | ||||
|                     <dd>${Math.ceil(course.traveledDistance)} m</dd> | ||||
|  | @ -67,12 +88,12 @@ setInterval(() => { | |||
|                     <dd>${Math.ceil(course.speed * 3600)} km/h</dd> | ||||
|                 ` : ` | ||||
|                     <dt>Départ dans</dt> | ||||
|                     <dd>${timeToHTML(course.departureTime)} s</dd> | ||||
|                     <dd>${timeToHTML(course.departureTime)}</dd> | ||||
|                 `}
 | ||||
|             </dl> | ||||
| 
 | ||||
|             <h2>Arrêts précédents</h2> | ||||
|             <table>${passingsToHTML(course.prevPassings)}</table> | ||||
|             <table>${passingsToHTML(prevPassings)}</table> | ||||
| 
 | ||||
|             <h2>Arrêts suivants</h2> | ||||
|             <table>${passingsToHTML(course.nextPassings)}</table> | ||||
|  |  | |||
|  | @ -1,277 +1,275 @@ | |||
| import axios from "axios"; | ||||
| import turfAlong from "@turf/along"; | ||||
| import turfLength from "@turf/length"; | ||||
| import * as turfHelpers from "@turf/helpers"; | ||||
| import * as turfProjection from "@turf/projection"; | ||||
| import network from "./network.json"; | ||||
| 
 | ||||
| const server = "http://localhost:4321"; | ||||
| 
 | ||||
| const findRoute = (from, to) => { | ||||
|     const queue = [[from, []]]; | ||||
| // Number of milliseconds to stay at each stop
 | ||||
| const stopTime = 10000; | ||||
| 
 | ||||
|     while (queue.length) { | ||||
|         const [head, path] = queue.shift(); | ||||
| // Step used to compute the vehicle angle in meters
 | ||||
| const angleStep = 10; | ||||
| 
 | ||||
|         for (const successor of network.stops[head].properties.successors) { | ||||
|             if (successor === to) { | ||||
|                 return path.concat([head, successor]); | ||||
|             } | ||||
| // Maximum speed of a vehicle
 | ||||
| const maxSpeed = 60 / 3600; | ||||
| 
 | ||||
|             if (!path.includes(successor)) { | ||||
|                 queue.push([successor, path.concat([head])]); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| // Minimum speed of a vehicle
 | ||||
| const minSpeed = 10 / 3600; | ||||
| 
 | ||||
|     return null; | ||||
| }; | ||||
| // Normal speed of a vehicle
 | ||||
| const normSpeed = (2 * maxSpeed + minSpeed) / 3; | ||||
| 
 | ||||
| /** Simulate the evolution of a vehicle course in the network. */ | ||||
| class Course { | ||||
|     constructor(data) { | ||||
|         this.id = data.id; | ||||
|         this.prevPassings = []; | ||||
|         this.nextPassings = []; | ||||
|         this.state = null; | ||||
|     constructor(id) { | ||||
|         // Unique identifier of this course
 | ||||
|         this.id = id; | ||||
| 
 | ||||
|         // Attributes for the `stopped` state
 | ||||
|         this.currentStop = null; | ||||
|         // Line on which this vehicle operates
 | ||||
|         this.line = null; | ||||
| 
 | ||||
|         // Line direction of this course
 | ||||
|         this.direction = null; | ||||
| 
 | ||||
|         // Stop to which this course is headed
 | ||||
|         this.finalStop = null; | ||||
| 
 | ||||
|         // Previous stops that this course left (with timestamps)
 | ||||
|         this.prevPassings = []; | ||||
| 
 | ||||
|         // Next stops that this course will leave (with timestamps)
 | ||||
|         this.nextPassings = []; | ||||
| 
 | ||||
|         // Stop that this course just left or will leave
 | ||||
|         this.departureStop = null; | ||||
| 
 | ||||
|         // Time at which the last stop was left or will be left
 | ||||
|         this.departureTime = 0; | ||||
| 
 | ||||
|         // Attributes for the `moving` state
 | ||||
|         this.departureStop = null; | ||||
|         // Next stop that this course will reach
 | ||||
|         // (if equal to departureStop, the course has reached its last stop)
 | ||||
|         this.arrivalStop = null; | ||||
| 
 | ||||
|         // Time at which the next stop will be left
 | ||||
|         this.arrivalTime = 0; | ||||
| 
 | ||||
|         // Segment of points between the current departure and arrival
 | ||||
|         this.segment = null; | ||||
| 
 | ||||
|         // Number of meters travelled between the two stops
 | ||||
|         this.traveledDistance = 0; | ||||
| 
 | ||||
|         // Current vehicle speed in meters per millisecond
 | ||||
|         this.speed = 0; | ||||
| 
 | ||||
|         // Current vehicle latitude and longitude
 | ||||
|         this.position = [0, 0]; | ||||
|         this.angle = 0; | ||||
| 
 | ||||
|         this.history = []; | ||||
|         // Current vehicle bearing
 | ||||
|         this.angle = 0; | ||||
|     } | ||||
| 
 | ||||
|     get currentSegment() { | ||||
|         if (this.state !== "moving") { | ||||
|             return null; | ||||
|     /** Retrieve information about the current segment used by the vehicle. */ | ||||
|     updateSegment() { | ||||
|         if (this.departureStop === null || this.arrivalStop === null) { | ||||
|             this.segment = null; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         return network.segments[`${this.departureStop}-${this.arrivalStop}`]; | ||||
|         const name = `${this.departureStop}-${this.arrivalStop}`; | ||||
| 
 | ||||
|         if (name in network.segments) { | ||||
|             this.segment = network.segments[name]; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (!(this.departureStop in network.stops)) { | ||||
|             console.warn(`Unknown stop: ${this.departureStop}`); | ||||
|             this.segment = null; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if (!(this.arrivalStop in network.stops)) { | ||||
|             console.warn(`Unknown stop: ${this.arrivalStop}`); | ||||
|             this.segment = null; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.segment = turfHelpers.lineString([ | ||||
|             network.stops[this.departureStop].geometry.coordinates, | ||||
|             network.stops[this.arrivalStop].geometry.coordinates, | ||||
|         ]); | ||||
|         this.segment.properties.length = turfLength(this.segment); | ||||
|     } | ||||
| 
 | ||||
|     updateData(data) { | ||||
|     /** Merge data received from the server. */ | ||||
|     receiveData(data) { | ||||
|         this.line = data.line; | ||||
|         this.direction = data.direction; | ||||
|         this.finalStop = data.finalStopId; | ||||
|         this.nextPassings = data.passings; | ||||
| 
 | ||||
|         const now = Date.now(); | ||||
|         const passings = Object.assign( | ||||
|             Object.fromEntries(this.nextPassings), | ||||
|             Object.fromEntries(data.passings), | ||||
|         ); | ||||
| 
 | ||||
|         if (this.state === null) { | ||||
|             // Initialize the course on the first available segment
 | ||||
|             const index = this.nextPassings.findIndex( | ||||
|                 ([, time]) => time >= now | ||||
|             ); | ||||
|         // Remove older passings from next passings
 | ||||
|         for (let [stop, time] of this.prevPassings) { | ||||
|             delete passings[stop]; | ||||
|         } | ||||
| 
 | ||||
|             if (index === -1) { | ||||
|                 return false; | ||||
|             } | ||||
| 
 | ||||
|             if (index === 0) { | ||||
|                 this.arriveToStop(this.nextPassings[index][0]); | ||||
|             } else { | ||||
|                 this.arriveToStop(this.nextPassings[index - 1][0]); | ||||
|                 this.moveToStop(...this.nextPassings[index]); | ||||
|             } | ||||
|         } else if (this.state === "moving") { | ||||
|             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.nextPassings[index][1]; | ||||
|             } | ||||
|         } else { | ||||
|             // (this.state === 'stopped')
 | ||||
|             // Try moving to the next stop
 | ||||
|             const index = this.nextPassings.findIndex( | ||||
|                 ([stop]) => stop === this.currentStop | ||||
|             ); | ||||
| 
 | ||||
|             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; | ||||
| 
 | ||||
|                 for ( | ||||
|                     let nextIndex = 0; | ||||
|                     nextIndex < this.nextPassings.length; | ||||
|                     ++nextIndex | ||||
|                 ) { | ||||
|                     const [stop, arrivalTime] = this.nextPassings[nextIndex]; | ||||
| 
 | ||||
|                     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; | ||||
|                 } | ||||
|         // Update departure time if still announced
 | ||||
|         if (this.departureStop !== null) { | ||||
|             if (this.departureStop in passings) { | ||||
|                 this.departureTime = passings[this.departureStop]; | ||||
|                 delete passings[this.departureStop]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.state === "moving") { | ||||
|             const segment = this.currentSegment; | ||||
|             const distance = segment.properties.length - this.traveledDistance; | ||||
|             const duration = this.arrivalTime - Date.now(); | ||||
|         // Update arrival time
 | ||||
|         if (this.arrivalStop !== null) { | ||||
|             if (this.arrivalStop in passings) { | ||||
|                 // Use announced time if available
 | ||||
|                 this.arrivalTime = passings[this.arrivalStop]; | ||||
|                 delete passings[this.arrivalStop]; | ||||
|             } else { | ||||
|                 // Otherwise, arrive using a normal speed from current position
 | ||||
|                 const segment = this.segment; | ||||
|                 const distance = segment.properties.length - this.traveledDistance; | ||||
|                 const time = Math.floor(distance / normSpeed); | ||||
|                 this.arrivalTime = Date.now() + time; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|             this.speed = Course.computeSpeed(distance, duration); | ||||
|         this.nextPassings = Object.entries(passings).sort( | ||||
|             ([, time1], [, time2]) => time1 - time2 | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** Update the vehicle state. */ | ||||
|     update() { | ||||
|         const now = Date.now(); | ||||
| 
 | ||||
|         // When initializing, use the first available passing as start
 | ||||
|         if (this.departureStop === null) { | ||||
|             if (this.nextPassings.length > 0) { | ||||
|                 const [stopId, time] = this.nextPassings.shift(); | ||||
|                 this.departureStop = stopId; | ||||
|                 this.departureTime = time; | ||||
|                 this.updateSegment(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // …and the second one as the arrival
 | ||||
|         if (this.arrivalStop === null) { | ||||
|             if (this.nextPassings.length > 0) { | ||||
|                 const [stopId, time] = this.nextPassings.shift(); | ||||
|                 this.arrivalStop = stopId; | ||||
|                 this.arrivalTime = time; | ||||
|                 this.updateSegment(); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (this.segment !== null) { | ||||
|             const segment = this.segment; | ||||
|             const distance = segment.properties.length - this.traveledDistance; | ||||
|             const duration = this.arrivalTime - stopTime - now; | ||||
| 
 | ||||
|             // Arrive to the next stop
 | ||||
|             if (distance === 0) { | ||||
|                 this.prevPassings.push([this.departureStop, this.departureTime]); | ||||
|                 this.departureStop = this.arrivalStop; | ||||
|                 this.departureTime = this.arrivalTime; | ||||
| 
 | ||||
|                 if (this.nextPassings.length > 0) { | ||||
|                     const [stopId, time] = this.nextPassings.shift(); | ||||
|                     this.arrivalStop = stopId; | ||||
|                     this.arrivalTime = time; | ||||
|                 } else { | ||||
|                     this.arrivalStop = null; | ||||
|                     this.arrivalTime = 0; | ||||
|                 } | ||||
| 
 | ||||
|                 this.traveledDistance = 0; | ||||
|                 this.updateSegment(); | ||||
|             } | ||||
| 
 | ||||
|             if (this.departureTime > now) { | ||||
|                 // Wait for departure
 | ||||
|                 this.speed = 0; | ||||
|             } else { | ||||
|                 if (this.traveledDistance === 0 && this.speed === 0) { | ||||
|                     // We’re late, record the actual departure time
 | ||||
|                     this.departureTime = now; | ||||
|                 } | ||||
| 
 | ||||
|                 // Update current speed to arrive on time if possible
 | ||||
|                 this.speed = Course.computeSpeed(distance, duration); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     tick(time) { | ||||
|         if (this.state === "moving") { | ||||
|             // Integrate current speed in travelled distance
 | ||||
|             this.traveledDistance += this.speed * time; | ||||
|             const segment = this.currentSegment; | ||||
|     /** Integrate the current vehicle speed and update distance. */ | ||||
|     move(time) { | ||||
|         if (this.segment === null) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|             if (this.traveledDistance >= segment.properties.length) { | ||||
|                 this.arriveToStop(this.arrivalStop); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // Compute updated position and angle based on a small step
 | ||||
|             const step = 10; // In meters
 | ||||
| 
 | ||||
|             const positions = [ | ||||
|                 Math.max(0, this.traveledDistance - step / 2), | ||||
|                 this.traveledDistance, | ||||
|                 this.traveledDistance + step / 2 | ||||
|             ].map(distance => turfProjection.toMercator(turfAlong( | ||||
|                 segment, | ||||
|                 distance / 1000 | ||||
|             )).geometry.coordinates); | ||||
| 
 | ||||
|             this.angle = Math.atan2( | ||||
|                 positions[0][1] - positions[2][1], | ||||
|                 positions[2][0] - positions[0][0] | ||||
|         if (this.speed > 0) { | ||||
|             this.traveledDistance = Math.min( | ||||
|                 this.traveledDistance + this.speed * time, | ||||
|                 this.segment.properties.length, | ||||
|             ); | ||||
| 
 | ||||
|             this.position = positions[1]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Transition this course to a state where it has arrived to a stop. | ||||
|      * @param {string} stop Identifier for the stop to which | ||||
|      * the course arrives. | ||||
|      * @returns {undefined} | ||||
|      */ | ||||
|     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 | ||||
|         // Compute updated position and angle based on a small step
 | ||||
|         let positionBehind; | ||||
|         let positionInFront; | ||||
| 
 | ||||
|         if (this.traveledDistance < angleStep / 2) { | ||||
|             positionBehind = this.traveledDistance; | ||||
|             positionInFront = angleStep; | ||||
|         } else { | ||||
|             positionBehind = this.traveledDistance - angleStep / 2; | ||||
|             positionInFront = this.traveledDistance + angleStep / 2; | ||||
|         } | ||||
| 
 | ||||
|         const positions = [ | ||||
|             positionBehind, | ||||
|             this.traveledDistance, | ||||
|             positionInFront, | ||||
|         ].map(distance => turfProjection.toMercator(turfAlong( | ||||
|             this.segment, | ||||
|             distance / 1000 | ||||
|         )).geometry.coordinates); | ||||
| 
 | ||||
|         this.angle = Math.atan2( | ||||
|             positions[0][1] - positions[2][1], | ||||
|             positions[2][0] - positions[0][0] | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Transition this course to a state where it is moving to a stop. | ||||
|      * @param {string} stop Next stop for this course. | ||||
|      * @param {number} arrivalTime Planned arrival time to that stop. | ||||
|      * @returns {undefined} | ||||
|      */ | ||||
|     moveToStop(stop, arrivalTime) { | ||||
|         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 (Course.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.position = positions[1]; | ||||
|     } | ||||
| 
 | ||||
|     static computeSpeed(distance, duration) { | ||||
|         if (duration <= 0) { | ||||
|             // Late: go to maximum speed
 | ||||
|             return 50 / 3600; | ||||
|             return maxSpeed; | ||||
|         } | ||||
| 
 | ||||
|         if (distance / duration <= 10 / 3600) { | ||||
|         const speed = distance / duration; | ||||
| 
 | ||||
|         if (speed < minSpeed) { | ||||
|             // Too slow: pause until speed is sufficient
 | ||||
|             return 0; | ||||
|         } | ||||
| 
 | ||||
|         return distance / duration; | ||||
|         return Math.min(maxSpeed, speed); | ||||
|     } | ||||
| } | ||||
| 
 | ||||
|  | @ -281,29 +279,19 @@ const updateData = async courses => { | |||
|     // Update or create new courses
 | ||||
|     for (const [id, data] of Object.entries(dataset)) { | ||||
|         if (id in courses) { | ||||
|             if (!courses[id].updateData(data)) { | ||||
|                 delete courses[id]; | ||||
|             } | ||||
|             courses[id].receiveData(data); | ||||
|         } else { | ||||
|             const newCourse = new Course(data); | ||||
| 
 | ||||
|             if (newCourse.updateData(data)) { | ||||
|                 courses[id] = newCourse; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Remove stale courses
 | ||||
|     for (const id of Object.keys(courses)) { | ||||
|         if (!(id in dataset)) { | ||||
|             delete courses[id]; | ||||
|             const newCourse = new Course(data.id); | ||||
|             newCourse.receiveData(data); | ||||
|             courses[id] = newCourse; | ||||
|         } | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| const tick = (courses, time) => { | ||||
|     for (const course of Object.values(courses)) { | ||||
|         course.tick(time); | ||||
|         course.update(); | ||||
|         course.move(time); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
|  | @ -326,5 +314,7 @@ export const start = () => { | |||
|         tick(courses, time); | ||||
|     }; | ||||
| 
 | ||||
|     window.__courses = courses; | ||||
| 
 | ||||
|     return { courses, update }; | ||||
| }; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue