require("ol/ol.css"); const { Map, View } = require("ol"); const { getVectorContext } = require("ol/render"); const Point = require("ol/geom/Point").default; const proj = require("ol/proj"); const { Style, Icon } = require("ol/style"); const tilesLayers = require("./tiles"); const networkLayers = require("./network"); const { sizes, makeBorderColor, makeCourseColor } = require("./common"); const network = require("../../tam/network.json"); const simulation = require("../../tam/simulation"); const courseStyles = {}; const getCourseStyle = lineColor => { if (!(lineColor in courseStyles)) { const icon = window.document.createElement("canvas"); const shapeSize = sizes.courseSize; const iconSize = sizes.courseSize + sizes.courseOuterBorder; icon.width = iconSize; icon.height = iconSize; const cx = icon.width / 2; const cy = icon.height / 2; const iconCtx = icon.getContext("2d"); for (const [color, size] of [ [makeBorderColor(lineColor), sizes.courseOuterBorder], [lineColor, sizes.courseBorder], [makeCourseColor(lineColor), sizes.courseInnerBorder] ]) { iconCtx.fillStyle = color; iconCtx.strokeStyle = color; iconCtx.lineWidth = size; iconCtx.lineJoin = "round"; iconCtx.miterLimit = 200000; iconCtx.beginPath(); iconCtx.moveTo(cx - 0.5 * shapeSize, cy - 0.3 * shapeSize); iconCtx.lineTo(cx + 0.5 * shapeSize, cy); iconCtx.lineTo(cx - 0.5 * shapeSize, cy + 0.3 * shapeSize); iconCtx.closePath(); iconCtx.stroke(); iconCtx.fill(); } courseStyles[lineColor] = new Style({ image: new Icon({ img: icon, imgSize: [icon.width, icon.height] }) }); } return courseStyles[lineColor]; }; const create = target => { const view = new View({ center: proj.fromLonLat([3.88, 43.605]), zoom: 14, maxZoom: 22, constrainResolution: true }); const map = new Map({ target, layers: [ ...tilesLayers.getLayers(), ...networkLayers.getLayers() ], view }); const stopsLayer = map.getLayers().item(3); // Run courses simulation const simulInstance = simulation.start(); // Course on which the view is currently focused let focusedCourse = null; const startFocus = courseId => { if (courseId in simulInstance.courses) { const course = simulInstance.courses[courseId]; view.animate({ center: course.position, duration: 500 }, () => { focusedCourse = courseId; }); } }; const stopFocus = () => { focusedCourse = null; }; // Draw courses directly on the map map.on("postcompose", ev => { simulInstance.update(); // The normal way to access a layer’s vector context is through the // `postrender` event of that layer. However, `postrender` is not // triggered when no feature of the layer is inside the current // bounding box, but we want to draw vehicles in between stops even // if no stop is visible. This hack listens to the global `postcompose` // event, which is always triggered at every frame, and reconstructs // the stops layer’s vector context from internal variables /* eslint-disable no-underscore-dangle */ if (stopsLayer.renderer_) { ev.context = stopsLayer.renderer_.context; ev.inversePixelTransform = stopsLayer.renderer_.inversePixelTransform; /* eslint-enable no-underscore-dangle */ const ctx = getVectorContext(ev); for (const course of Object.values(simulInstance.courses)) { const color = network.lines[course.line].color; const style = getCourseStyle(color); style.getImage().setRotation( view.getRotation() + course.angle ); ctx.setStyle(style); const point = new Point(course.position); ctx.drawGeometry(point); if (course.id === focusedCourse) { view.setCenter(course.position); } } } map.render(); }); map.render(); map.on("singleclick", ev => { const mousePixel = map.getPixelFromCoordinate(ev.coordinate); const maxDistance = sizes.courseSize + sizes.courseOuterBorder; for (const course of Object.values(simulInstance.courses)) { const coursePixel = map.getPixelFromCoordinate(course.position); const dx = mousePixel[0] - coursePixel[0]; const dy = mousePixel[1] - coursePixel[1]; const distance = dx * dx + dy * dy; if (distance <= maxDistance * maxDistance) { startFocus(course.id); return; } } // Clicking anywhere else resets focus stopFocus(); }); return map; }; exports.create = create;