| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  | const axios = require('axios'); | 
					
						
							| 
									
										
										
										
											2020-07-19 20:13:26 +00:00
										 |  |  |  | const network = require('./network.json'); | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  | 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; | 
					
						
							|  |  |  |  | }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-17 17:17:06 +00:00
										 |  |  |  | const updateFromTam = async (courses) => | 
					
						
							|  |  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  |     const currentCourses = (await axios.get(`${server}/courses`)).data; | 
					
						
							| 
									
										
										
										
											2020-07-17 17:17:06 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |     for (let [id, course] of Object.entries(currentCourses)) | 
					
						
							|  |  |  |  |     { | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  |         // 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
 | 
					
						
							| 
									
										
										
										
											2020-07-17 17:17:06 +00:00
										 |  |  |  |         else | 
					
						
							|  |  |  |  |         { | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  |             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') | 
					
						
							|  |  |  |  |         { | 
					
						
							| 
									
										
										
										
											2020-07-23 15:29:35 +00:00
										 |  |  |  |             // Increase the travelled distance respective to the current speed
 | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  |             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; | 
					
						
							|  |  |  |  |             } | 
					
						
							| 
									
										
										
										
											2020-07-23 15:29:35 +00:00
										 |  |  |  | 
 | 
					
						
							|  |  |  |  |             // 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, | 
					
						
							|  |  |  |  |             }; | 
					
						
							| 
									
										
										
										
											2020-07-17 17:17:06 +00:00
										 |  |  |  |         } | 
					
						
							|  |  |  |  |     } | 
					
						
							|  |  |  |  | }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-23 15:29:35 +00:00
										 |  |  |  | const run = callback => | 
					
						
							| 
									
										
										
										
											2020-07-17 17:17:06 +00:00
										 |  |  |  | { | 
					
						
							| 
									
										
										
										
											2020-07-23 15:29:35 +00:00
										 |  |  |  |     const courses = {}; | 
					
						
							|  |  |  |  |     let lastFrame = null; | 
					
						
							|  |  |  |  |     let lastUpdate = null; | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-23 15:29:35 +00:00
										 |  |  |  |     const loop = () => | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  |     { | 
					
						
							| 
									
										
										
										
											2020-07-23 15:29:35 +00:00
										 |  |  |  |         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); | 
					
						
							|  |  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-07-18 23:45:36 +00:00
										 |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-23 15:29:35 +00:00
										 |  |  |  |     const interval = setInterval(loop, 24); | 
					
						
							|  |  |  |  |     return () => clearInterval(interval); | 
					
						
							| 
									
										
										
										
											2020-07-17 17:17:06 +00:00
										 |  |  |  | }; | 
					
						
							|  |  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-23 15:29:35 +00:00
										 |  |  |  | exports.run = run; |