import { getVectorContext } from "ol/render"; import Point from "ol/geom/Point"; import { fromExtent } from 'ol/geom/Polygon'; import { Style, Icon } from "ol/style"; import { sizes, cacheStyle, makeBorderColor, makeCourseColor } from "./common"; import network from "../../data/network.json"; const courseStyle = cacheStyle( lineColor => { 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(); } return new Style({ image: new Icon({ img: icon, imgSize: [icon.width, icon.height] }) }); }, ); export const setupCoursesAnimation = ( map, coursesSimulation, stopsLayer, onClick ) => { const view = map.getView(); // Draw courses directly on the map map.on("postcompose", ev => { coursesSimulation.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); const bbox = fromExtent(map.getView().calculateExtent()); bbox.scale(1.05); for (const course of Object.values(coursesSimulation.courses)) { if (!bbox.intersectsCoordinate(course.position)) { continue; } const point = new Point(course.position); const color = network.lines[course.line].color; const style = courseStyle(color); style.getImage().setRotation( view.getRotation() + course.angle ); ctx.setStyle(style); ctx.drawGeometry(point); } } map.render(); }); map.on("singleclick", ev => { const mousePixel = map.getPixelFromCoordinate(ev.coordinate); const maxDistance = sizes.courseSize + sizes.courseInnerBorder; const clicked = []; for (const course of Object.values(coursesSimulation.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) { clicked.push(course.id); } } // Sort selected courses in increasing departing/arrival time clicked.sort((id1, id2) => { const course1 = coursesSimulation.courses[id1]; const course2 = coursesSimulation.courses[id2]; const time1 = ( course1.state === "moving" ? course1.arrivalTime : course1.departureTime ); const time2 = ( course2.state === "moving" ? course2.arrivalTime : course2.departureTime ); return time1 - time2; }); onClick(clicked); }); };