Compare commits
	
		
			5 Commits
		
	
	
		
			5c4f219f7a
			...
			51ec5f5d40
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
									
								
								 | 
						51ec5f5d40 | |
| 
							
							
								
									
								
								 | 
						e907d22faf | |
| 
							
							
								
									
								
								 | 
						41a92ff826 | |
| 
							
							
								
									
								
								 | 
						5dfa49b75f | |
| 
							
							
								
									
								
								 | 
						91d6771f5e | 
| 
						 | 
					@ -5,7 +5,8 @@
 | 
				
			||||||
  "main": "index.js",
 | 
					  "main": "index.js",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "back": "node src/back",
 | 
					    "back": "node src/back",
 | 
				
			||||||
    "front": "parcel serve src/front/index.html",
 | 
					    "front:debug": "parcel serve src/front/index.html",
 | 
				
			||||||
 | 
					    "front:prod": "npx parcel build src/front/index.html --no-source-maps --no-autoinstall",
 | 
				
			||||||
    "lint": "eslint ."
 | 
					    "lint": "eslint ."
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "keywords": [],
 | 
					  "keywords": [],
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -239,7 +239,6 @@ const createMap = target =>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Course on which the view is currently focused
 | 
					    // Course on which the view is currently focused
 | 
				
			||||||
    let focusedCourse = null;
 | 
					    let focusedCourse = null;
 | 
				
			||||||
    const focusZoom = 17;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const startFocus = courseId =>
 | 
					    const startFocus = courseId =>
 | 
				
			||||||
    {
 | 
					    {
 | 
				
			||||||
| 
						 | 
					@ -248,7 +247,6 @@ const createMap = target =>
 | 
				
			||||||
            const course = simulInstance.courses[courseId];
 | 
					            const course = simulInstance.courses[courseId];
 | 
				
			||||||
            view.animate({
 | 
					            view.animate({
 | 
				
			||||||
                center: course.position,
 | 
					                center: course.position,
 | 
				
			||||||
                zoom: focusZoom,
 | 
					 | 
				
			||||||
                duration: 500,
 | 
					                duration: 500,
 | 
				
			||||||
            }, () => focusedCourse = courseId);
 | 
					            }, () => focusedCourse = courseId);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					@ -294,7 +292,6 @@ const createMap = target =>
 | 
				
			||||||
                if (course.id === focusedCourse)
 | 
					                if (course.id === focusedCourse)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    view.setCenter(course.position);
 | 
					                    view.setCenter(course.position);
 | 
				
			||||||
                    // view.setZoom(focus);
 | 
					 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,18 +2,6 @@ const tam = require('./sources/tam');
 | 
				
			||||||
const util = require('../util');
 | 
					const util = require('../util');
 | 
				
			||||||
const network = require('./network.json');
 | 
					const network = require('./network.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					 | 
				
			||||||
 * Comparison function between two stop passings.
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * @param passing1 First stop passing.
 | 
					 | 
				
			||||||
 * @param passing2 Second stop passing.
 | 
					 | 
				
			||||||
 * @return Negative value if passing1 is sooner than passing2, positive
 | 
					 | 
				
			||||||
 * otherwise, zero if they occur at the same time.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
const passingCompare = ({arrivalTime: time1}, {arrivalTime: time2}) => (
 | 
					 | 
				
			||||||
    time1 - time2
 | 
					 | 
				
			||||||
);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Time at which the course data needs to be updated next
 | 
					// Time at which the course data needs to be updated next
 | 
				
			||||||
let nextUpdate = null;
 | 
					let nextUpdate = null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -31,9 +19,8 @@ let currentCourses = null;
 | 
				
			||||||
 * - `id`: Unique identifier for the course.
 | 
					 * - `id`: Unique identifier for the course.
 | 
				
			||||||
 * - `line`: Line number.
 | 
					 * - `line`: Line number.
 | 
				
			||||||
 * - `finalStop`: The final stop to which the course is headed.
 | 
					 * - `finalStop`: The final stop to which the course is headed.
 | 
				
			||||||
 * - `nextPassings`: Next passings of the vehicle, sorted by increasing
 | 
					 * - `nextPassings`: Next passings of the vehicle, as a dictionary associating
 | 
				
			||||||
 *   arrival time, containing both the stop identifier (`stopId`) and the
 | 
					 *   each next stop to the passing timestamp.
 | 
				
			||||||
 *   expected arrival timestamp (`arrivalTime`).
 | 
					 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @return Mapping from active course IDs to information about each course.
 | 
					 * @return Mapping from active course IDs to information about each course.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -69,9 +56,9 @@ const getCourses = () => new Promise((res, rej) =>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                else
 | 
					                else
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    for (let passing of course.nextPassings)
 | 
					                    for (let stopId of Object.keys(course.nextPassings))
 | 
				
			||||||
                    {
 | 
					                    {
 | 
				
			||||||
                        if (!(passing.stopId in network.stops))
 | 
					                        if (!(stopId in network.stops))
 | 
				
			||||||
                        {
 | 
					                        {
 | 
				
			||||||
                            delete courses[courseId];
 | 
					                            delete courses[courseId];
 | 
				
			||||||
                            break;
 | 
					                            break;
 | 
				
			||||||
| 
						 | 
					@ -80,13 +67,6 @@ const getCourses = () => new Promise((res, rej) =>
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // End of courses information stream. Sort next stops by increasing
 | 
					 | 
				
			||||||
            // arrival time in each course then save result in memory cache
 | 
					 | 
				
			||||||
            for (let course of Object.values(courses))
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                course.nextPassings.sort(passingCompare);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            currentCourses = courses;
 | 
					            currentCourses = courses;
 | 
				
			||||||
            res(currentCourses);
 | 
					            res(currentCourses);
 | 
				
			||||||
            return;
 | 
					            return;
 | 
				
			||||||
| 
						 | 
					@ -113,12 +93,12 @@ const getCourses = () => new Promise((res, rej) =>
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            courses[id] = {
 | 
					            courses[id] = {
 | 
				
			||||||
                id, line, finalStop,
 | 
					                id, line, finalStop,
 | 
				
			||||||
                nextPassings: [{stopId, arrivalTime}],
 | 
					                nextPassings: {[stopId]: arrivalTime},
 | 
				
			||||||
            };
 | 
					            };
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            courses[id].nextPassings.push({stopId, arrivalTime});
 | 
					            courses[id].nextPassings[stopId] = arrivalTime;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,188 +4,170 @@ const network = require('./network.json');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const server = 'http://localhost:4321';
 | 
					const server = 'http://localhost:4321';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const arriveAtStop = (course, stop) =>
 | 
					class Course
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
    course.state = 'stopped';
 | 
					    constructor(data)
 | 
				
			||||||
    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
 | 
					        this.id = data.id;
 | 
				
			||||||
        // directly to the arrival stop
 | 
					        this.passings = {};
 | 
				
			||||||
        arriveAtStop(course, course.arrivalStop);
 | 
					        this.state = null;
 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
        updateSpeed(course);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const getCurrentSegment = course =>
 | 
					        // Attributes for the `stopped` state
 | 
				
			||||||
{
 | 
					        this.currentStop = null;
 | 
				
			||||||
    if (course.state === 'stopped')
 | 
					
 | 
				
			||||||
    {
 | 
					        // Attributes for the `moving` state
 | 
				
			||||||
        return null;
 | 
					        this.departureStop = null;
 | 
				
			||||||
 | 
					        this.arrivalStop = null;
 | 
				
			||||||
 | 
					        this.arrivalTime = 0;
 | 
				
			||||||
 | 
					        this.traveledDistance = 0;
 | 
				
			||||||
 | 
					        this.speed = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.position = [0, 0];
 | 
				
			||||||
 | 
					        this.angle = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.history = [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return network.segments[`${course.departureStop}-${course.arrivalStop}`];
 | 
					    get currentSegment()
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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);
 | 
					        if (this.state !== 'moving')
 | 
				
			||||||
        return;
 | 
					        {
 | 
				
			||||||
 | 
					            return undefined;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    course.speed = remainingDistance / remainingTime;
 | 
					        return network.segments[`${this.departureStop}-${this.arrivalStop}`];
 | 
				
			||||||
};
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateFromTam = async (courses) =>
 | 
					    updateData(data)
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    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
 | 
					        this.line = data.line;
 | 
				
			||||||
 | 
					        this.finalStop = data.finalStop;
 | 
				
			||||||
 | 
					        Object.assign(this.passings, data.nextPassings);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const now = Date.now();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Make sure we’re on the right `stopped`/`moving` state
 | 
				
			||||||
 | 
					        if (this.state === null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            let previousStop = null;
 | 
				
			||||||
 | 
					            let departureTime = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            let nextStop = null;
 | 
					            let nextStop = null;
 | 
				
			||||||
        let arrivalTime = null;
 | 
					            let arrivalTime = Infinity;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for (let {stopId, arrivalTime: time} of course.nextPassings)
 | 
					            for (let [stopId, time] of Object.entries(this.passings))
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
            if (time > Date.now())
 | 
					                if (time > now && time < arrivalTime)
 | 
				
			||||||
                {
 | 
					                {
 | 
				
			||||||
                    nextStop = stopId;
 | 
					                    nextStop = stopId;
 | 
				
			||||||
                    arrivalTime = time;
 | 
					                    arrivalTime = time;
 | 
				
			||||||
                break;
 | 
					                }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                if (time < now && time > departureTime)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    previousStop = stopId;
 | 
				
			||||||
 | 
					                    departureTime = time;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (nextStop === null)
 | 
					            if (nextStop === null)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
            continue;
 | 
					                return false;
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Update an existing course
 | 
					            if (previousStop === null)
 | 
				
			||||||
        if (id in courses)
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
            const prev = courses[id];
 | 
					                // Teleport to the first known stop
 | 
				
			||||||
 | 
					                this.arriveToStop(nextStop);
 | 
				
			||||||
            if (prev.state === 'stopped')
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                if (prev.currentStop !== nextStop)
 | 
					 | 
				
			||||||
                {
 | 
					 | 
				
			||||||
                    // Start traveling from the current stop to the next
 | 
					 | 
				
			||||||
                    moveToStop(prev, nextStop, arrivalTime);
 | 
					 | 
				
			||||||
                }
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                // Update the ETA if we’re still headed to the same stop
 | 
					                // Teleport to the first known segment
 | 
				
			||||||
                if (prev.arrivalStop === nextStop)
 | 
					                this.arriveToStop(previousStop);
 | 
				
			||||||
                {
 | 
					                this.moveToStop(nextStop, arrivalTime);
 | 
				
			||||||
                    prev.arrivalTime = arrivalTime;
 | 
					 | 
				
			||||||
                    updateSpeed(prev);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
                // Otherwise, we missed a stop, try to go directly to the
 | 
					        }
 | 
				
			||||||
                // next segment
 | 
					        else if (this.state === 'moving')
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Should already be at the next stop
 | 
				
			||||||
 | 
					            if (this.passings[this.arrivalStop] <= now)
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                this.arriveToStop(this.arrivalStop);
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					            // On the right track, update the arrival time
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                    arriveAtStop(prev, prev.arrivalStop);
 | 
					                this.arrivalTime = this.passings[this.arrivalStop];
 | 
				
			||||||
                    moveToStop(prev, nextStop, arrivalTime);
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        }
 | 
					        else // this.state === 'stopped'
 | 
				
			||||||
        // Create a new course
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            courses[id] = {
 | 
					            // Try moving to the next stop
 | 
				
			||||||
                id,
 | 
					            let nextStop = null;
 | 
				
			||||||
                line: course.line,
 | 
					            let arrivalTime = Infinity;
 | 
				
			||||||
                finalStop: course.finalStop,
 | 
					 | 
				
			||||||
                position: [0, 0],
 | 
					 | 
				
			||||||
                angle: 0,
 | 
					 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            arriveAtStop(courses[id], nextStop);
 | 
					            for (let [stopId, time] of Object.entries(this.passings))
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					                if (time > now && time < arrivalTime)
 | 
				
			||||||
 | 
					                {
 | 
				
			||||||
 | 
					                    nextStop = stopId;
 | 
				
			||||||
 | 
					                    arrivalTime = time;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Remove stale courses
 | 
					            if (nextStop === null)
 | 
				
			||||||
    for (let id of Object.keys(courses))
 | 
					 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
        if (!(id in currentCourses))
 | 
					                // This course is finished
 | 
				
			||||||
 | 
					                return false;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (nextStop !== this.currentStop)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
            delete courses[id];
 | 
					                this.moveToStop(nextStop, arrivalTime);
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updatePositions = (courses, time) =>
 | 
					        if (this.state === 'moving')
 | 
				
			||||||
{
 | 
					 | 
				
			||||||
    for (let [id, course] of Object.entries(courses))
 | 
					 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
        if (course.state === 'moving')
 | 
					            this.speed = this.computeTheoreticalSpeed();
 | 
				
			||||||
        {
 | 
					        }
 | 
				
			||||||
            // Increase the travelled distance respective to the current speed
 | 
					 | 
				
			||||||
            const delta = course.speed * time;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const segment = getCurrentSegment(course);
 | 
					        return true;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tick(time)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (this.state === null)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Ignore uninitalized courses
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (this.state === 'moving')
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // Integrate current speed in travelled distance
 | 
				
			||||||
 | 
					            const delta = this.speed * time;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const segment = this.currentSegment;
 | 
				
			||||||
            const length = segment.points[segment.points.length - 1].distance;
 | 
					            const length = segment.points[segment.points.length - 1].distance;
 | 
				
			||||||
 | 
					            this.traveledDistance += delta;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (course.traveledDistance + delta >= length)
 | 
					            if (this.traveledDistance >= length)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                course.traveledDistance = length;
 | 
					                this.arriveToStop(this.arrivalStop);
 | 
				
			||||||
            }
 | 
					                return;
 | 
				
			||||||
            else
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                course.traveledDistance += delta;
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            // Recompute updated position
 | 
					            // Recompute updated position
 | 
				
			||||||
            const departureStop = network.stops[course.departureStop];
 | 
					            const departureStop = network.stops[this.departureStop];
 | 
				
			||||||
            const arrivalStop = network.stops[course.arrivalStop];
 | 
					            const arrivalStop = network.stops[this.arrivalStop];
 | 
				
			||||||
            const nextNodeIndex = segment.points.findIndex(
 | 
					            const nextNodeIndex = segment.points.findIndex(
 | 
				
			||||||
                ({distance}) => distance >= course.traveledDistance);
 | 
					                ({distance}) => distance >= this.traveledDistance);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            if (nextNodeIndex === 0)
 | 
					            if (nextNodeIndex === 0)
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
                course.position = {
 | 
					                this.position = turf.toMercator([
 | 
				
			||||||
                    lat: departureStop.lat,
 | 
					                    departureStop.lon,
 | 
				
			||||||
                    lon: departureStop.lon
 | 
					                    departureStop.lat
 | 
				
			||||||
                };
 | 
					                ]);
 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            else if (nextNodeIndex === -1)
 | 
					 | 
				
			||||||
            {
 | 
					 | 
				
			||||||
                course.position = {
 | 
					 | 
				
			||||||
                    lat: arrivalStop.lat,
 | 
					 | 
				
			||||||
                    lon: arrivalStop.lon
 | 
					 | 
				
			||||||
                };
 | 
					 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
            {
 | 
					            {
 | 
				
			||||||
| 
						 | 
					@ -202,33 +184,161 @@ const updatePositions = (courses, time) =>
 | 
				
			||||||
                    nextNode.lat
 | 
					                    nextNode.lat
 | 
				
			||||||
                ]);
 | 
					                ]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                const curLength = course.traveledDistance
 | 
					                const curLength = this.traveledDistance
 | 
				
			||||||
                    - previousNode.distance;
 | 
					                    - previousNode.distance;
 | 
				
			||||||
                const totalLength = nextNode.distance
 | 
					                const totalLength = nextNode.distance
 | 
				
			||||||
                    - previousNode.distance;
 | 
					                    - previousNode.distance;
 | 
				
			||||||
                const t = curLength / totalLength;
 | 
					                const t = curLength / totalLength;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                course.position = [
 | 
					                this.position = [
 | 
				
			||||||
                    t * nextPoint[0] + (1 - t) * previousPoint[0],
 | 
					                    t * nextPoint[0] + (1 - t) * previousPoint[0],
 | 
				
			||||||
                    t * nextPoint[1] + (1 - t) * previousPoint[1],
 | 
					                    t * nextPoint[1] + (1 - t) * previousPoint[1],
 | 
				
			||||||
                ];
 | 
					                ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                course.angle = Math.atan2(
 | 
					                this.angle = Math.atan2(
 | 
				
			||||||
                    previousPoint[1] - nextPoint[1],
 | 
					                    previousPoint[1] - nextPoint[1],
 | 
				
			||||||
                    nextPoint[0] - previousPoint[0],
 | 
					                    nextPoint[0] - previousPoint[0],
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        else
 | 
					        else // this.state === 'stopped'
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            const currentNode = network.stops[course.currentStop];
 | 
					            const currentNode = network.stops[this.currentStop];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            course.position = turf.toMercator([
 | 
					            this.position = turf.toMercator([
 | 
				
			||||||
                currentNode.lon,
 | 
					                currentNode.lon,
 | 
				
			||||||
                currentNode.lat
 | 
					                currentNode.lat
 | 
				
			||||||
            ]);
 | 
					            ]);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Transition this course to a state where it has arrived to a stop.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param stop Identifier for the stop to which the course arrives.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    arriveToStop(stop)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        this.state = 'stopped';
 | 
				
			||||||
 | 
					        this.currentStop = stop;
 | 
				
			||||||
 | 
					        this.position = turf.toMercator([
 | 
				
			||||||
 | 
					            network.stops[this.currentStop].lon,
 | 
				
			||||||
 | 
					            network.stops[this.currentStop].lat,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        this.history.push(['arriveToStop', stop]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /**
 | 
				
			||||||
 | 
					     * Transition this course to a state where it is moving to a stop.
 | 
				
			||||||
 | 
					     *
 | 
				
			||||||
 | 
					     * @param stop Next stop for this course.
 | 
				
			||||||
 | 
					     * @param arrivalTime Planned arrival time to that stop.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    moveToStop(stop, arrivalTime)
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!(`${this.currentStop}-${stop}` in network.segments))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            console.warn(`Course ${this.id} is cannot go from stop
 | 
				
			||||||
 | 
					${this.currentStop} to stop ${stop}. Teleporting to ${stop}`);
 | 
				
			||||||
 | 
					            this.arriveToStop(stop);
 | 
				
			||||||
 | 
					            return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.state = 'moving';
 | 
				
			||||||
 | 
					        this.departureStop = this.currentStop;
 | 
				
			||||||
 | 
					        this.arrivalStop = stop;
 | 
				
			||||||
 | 
					        this.arrivalTime = arrivalTime;
 | 
				
			||||||
 | 
					        this.traveledDistance = 0;
 | 
				
			||||||
 | 
					        this.speed = 0;
 | 
				
			||||||
 | 
					        this.position = turf.toMercator([
 | 
				
			||||||
 | 
					            network.stops[this.departureStop].lon,
 | 
				
			||||||
 | 
					            network.stops[this.departureStop].lat,
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					        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.
 | 
				
			||||||
 | 
					     */
 | 
				
			||||||
 | 
					    computeTheoreticalSpeed()
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (this.state !== 'moving')
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const segment = this.currentSegment;
 | 
				
			||||||
 | 
					        const length = segment.points[segment.points.length - 1].distance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const remainingTime = this.arrivalTime - Date.now();
 | 
				
			||||||
 | 
					        const remainingDistance = length - this.traveledDistance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (remainingDistance <= 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return 0;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else if (remainingTime <= 0)
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            // We’re late, go to maximum speed
 | 
				
			||||||
 | 
					            return 50 / 3600; // 50 km/h
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            return remainingDistance / remainingTime;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateData = async (courses) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    const dataset = (await axios.get(`${server}/courses`)).data;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Update or create new courses
 | 
				
			||||||
 | 
					    for (let [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.`);
 | 
				
			||||||
 | 
					                courses[id] = newCourse;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Remove stale courses
 | 
				
			||||||
 | 
					    for (let id of Object.keys(courses))
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        if (!(id in dataset))
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					            delete courses[id];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tick = (courses, time) =>
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					    for (let course of Object.values(courses))
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					        course.tick(time);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const start = () =>
 | 
					const start = () =>
 | 
				
			||||||
| 
						 | 
					@ -244,12 +354,12 @@ const start = () =>
 | 
				
			||||||
        if (lastUpdate === null || lastUpdate + 5000 <= now)
 | 
					        if (lastUpdate === null || lastUpdate + 5000 <= now)
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
            lastUpdate = now;
 | 
					            lastUpdate = now;
 | 
				
			||||||
            updateFromTam(courses);
 | 
					            updateData(courses);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        const time = lastFrame === null ? 0 : now - lastFrame;
 | 
					        const time = lastFrame === null ? 0 : now - lastFrame;
 | 
				
			||||||
        lastFrame = now;
 | 
					        lastFrame = now;
 | 
				
			||||||
        updatePositions(courses, time);
 | 
					        tick(courses, time);
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {courses, update};
 | 
					    return {courses, update};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue