require('ol/ol.css'); const axios = require('axios'); const {Map, View} = require('ol'); const TileLayer = require('ol/layer/Tile').default; const XYZSource = require('ol/source/XYZ').default; const VectorLayer = require('ol/layer/Vector').default; const VectorSource = require('ol/source/Vector').default; const Feature = require('ol/Feature').default; const Point = require('ol/geom/Point').default; const LineString = require('ol/geom/LineString').default; const proj = require('ol/proj'); const {Style, Fill, Stroke, Circle} = require('ol/style'); const color = require('color'); const mapboxToken = `pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJja2NxaTUyMmUwcmFhMn\ h0NmFsdzQ3emxqIn0.cyxF0h36emIMTk3cc4VqUw`; const simulation = require('../tam/simulation'); const network = require('../tam/network.json'); const getRouteColors = routes => { const colors = routes.filter( // Only consider normal routes (excluding alternate routes) ([lineRef, routeRef]) => network.lines[lineRef].routes[routeRef].state === 'normal' ).map(([lineRef]) => network.lines[lineRef].color); if (colors.length >= 1) { return colors; } return ['#FFFFFF']; }; const makeDataSources = () => { const segmentsSource = new VectorSource(); const stopsSource = new VectorSource(); segmentsSource.addFeatures( Object.values(network.segments).map(({routes, points}) => new Feature({ colors: getRouteColors(routes), geometry: new LineString(points.map( ({lat, lon}) => proj.fromLonLat([lon, lat]) )), }) ) ); stopsSource.addFeatures( Object.values(network.stops).map(({routes, lon, lat}) => new Feature({ colors: getRouteColors(routes), geometry: new Point(proj.fromLonLat([lon, lat])), }) ) ); return {segmentsSource, stopsSource}; }; const makeBorderColor = mainColor => { const hsl = color(mainColor).hsl(); hsl.color = Math.max(0, hsl.color[2] -= 20); return hsl.hex(); }; const segmentsBorderStyle = feature => new Style({ stroke: new Stroke({ color: makeBorderColor(feature.get('colors')[0]), width: 8, }), }); const segmentsInnerStyle = feature => new Style({ stroke: new Stroke({ color: feature.get('colors')[0], width: 6, }), }); const stopsStyle = feature => new Style({ image: new Circle({ fill: new Fill({ color: feature.get('colors')[0], }), stroke: new Stroke({ color: makeBorderColor(feature.get('colors')[0]), width: 1.5, }), radius: 6, }), }); const createMap = target => { // Map background const backgroundSource = new XYZSource({ url: 'https://api.mapbox.com/' + [ 'styles', 'v1', 'mapbox', 'streets-v11', 'tiles', '512', '{z}', '{x}', '{y}', ].join('/') + `?access_token=${mapboxToken}`, tileSize: [512, 512], }); const backgroundLayer = new TileLayer({ source: backgroundSource, }); // Static data overlay const {segmentsSource, stopsSource} = makeDataSources(); const segmentsBorderLayer = new VectorLayer({ source: segmentsSource, style: segmentsBorderStyle, updateWhileInteracting: true, updateWhileAnimating: true, }); const segmentsInnerLayer = new VectorLayer({ source: segmentsSource, style: segmentsInnerStyle, updateWhileInteracting: true, updateWhileAnimating: true, }); const stopsLayer = new VectorLayer({ source: stopsSource, style: stopsStyle, minZoom: 13, updateWhileInteracting: true, 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, layers: [ backgroundLayer, segmentsBorderLayer, segmentsInnerLayer, stopsLayer, coursesLayer, ], view: new View({ center: proj.fromLonLat([3.88, 43.605]), zoom: 13, maxZoom: 22, constrainResolution: true, }), }); return map; }; exports.createMap = createMap;