diff --git a/src/front/index.js b/src/front/index.js index b9befd8..4691e3e 100644 --- a/src/front/index.js +++ b/src/front/index.js @@ -1,7 +1,4 @@ require('regenerator-runtime/runtime'); const {createMap} = require('./map'); - -require('../tam/simulation'); - createMap(/* map = */ 'map'); diff --git a/src/front/map.js b/src/front/map.js index 64b4af9..de3f54c 100644 --- a/src/front/map.js +++ b/src/front/map.js @@ -21,6 +21,7 @@ const color = require('color'); const mapboxToken = `pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJja2NxaTUyMmUwcmFhMn\ h0NmFsdzQ3emxqIn0.cyxF0h36emIMTk3cc4VqUw`; +const simulation = require('../tam/simulation'); const network = require('../tam/network.json'); const getRouteColors = routes => @@ -39,7 +40,7 @@ const getRouteColors = routes => return ['#FFFFFF']; }; -const makeDataSources = async () => +const makeDataSources = () => { const segmentsSource = new VectorSource(); const stopsSource = new VectorSource(); @@ -101,7 +102,7 @@ const stopsStyle = feature => new Style({ }), }); -const createMap = async (target) => +const createMap = target => { // Map background const backgroundSource = new XYZSource({ @@ -116,8 +117,8 @@ const createMap = async (target) => source: backgroundSource, }); - // Data overlay - const {segmentsSource, stopsSource} = await makeDataSources(); + // Static data overlay + const {segmentsSource, stopsSource} = makeDataSources(); const segmentsBorderLayer = new VectorLayer({ source: segmentsSource, @@ -144,6 +145,63 @@ const createMap = async (target) => updateWhileAnimating: true, }); + // Dynamic data overlay + const coursesSource = new VectorSource(); + + const onFrame = courses => + { + // Remove stale courses + for (let feature of coursesSource.getFeatures()) + { + if (!(feature.getId() in courses)) + { + coursesSource.removeFeature(feature); + } + } + + // Add new courses or update existing courses + const newFeatures = []; + + for (let [courseId, course] of Object.entries(courses)) + { + if ('position' in course) + { + const feature = coursesSource.getFeatureById(courseId); + const coords = proj.fromLonLat([ + course.position.lon, + course.position.lat + ]); + + if (feature === null) + { + const feature = new Feature({ + colors: ['#FF0000'], + geometry: new Point(coords) + }); + + feature.setId(courseId); + newFeatures.push(feature); + } + else + { + feature.getGeometry().setCoordinates(coords); + } + } + } + + coursesSource.addFeatures(newFeatures); + }; + + simulation.run(onFrame); + + const coursesLayer = new VectorLayer({ + source: coursesSource, + style: stopsStyle, + + updateWhileInteracting: true, + updateWhileAnimating: true, + }); + // Setup map const map = new Map({ target, @@ -152,6 +210,7 @@ const createMap = async (target) => segmentsBorderLayer, segmentsInnerLayer, stopsLayer, + coursesLayer, ], view: new View({ center: proj.fromLonLat([3.88, 43.605]), diff --git a/src/tam/simulation.js b/src/tam/simulation.js index 41bcb49..8ed06f3 100644 --- a/src/tam/simulation.js +++ b/src/tam/simulation.js @@ -149,6 +149,7 @@ const updatePositions = (courses, time) => { if (course.state === 'moving') { + // Increase the travelled distance respective to the current speed const delta = course.speed * time; const segment = getCurrentSegment(course); @@ -162,28 +163,82 @@ const updatePositions = (courses, time) => { course.traveledDistance += delta; } + + // 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, + }; } } }; -const courses = {}; -let lastFrame = null; -let lastUpdate = null; - -const loop = now => +const run = callback => { - const time = lastFrame === null ? 0 : now - lastFrame; + const courses = {}; + let lastFrame = null; + let lastUpdate = null; - lastFrame = now; - updatePositions(courses, time); - - if (lastUpdate === null || lastUpdate + 5000 <= now) + const loop = () => { - lastUpdate = now; - updateFromTam(courses); - } + const now = Date.now(); - requestAnimationFrame(loop); + if (lastUpdate === null || lastUpdate + 5000 <= now) + { + lastUpdate = now; + updateFromTam(courses); + } + + const time = lastFrame === null ? 0 : now - lastFrame; + lastFrame = now; + updatePositions(courses, time); + + callback(courses); + }; + + const interval = setInterval(loop, 24); + return () => clearInterval(interval); }; -requestAnimationFrame(loop); +exports.run = run;