import "ol/ol.css"; import { Map, View } from "ol"; import { getVectorContext } from "ol/render"; import Point from "ol/geom/Point"; import { fromExtent } from 'ol/geom/Polygon'; import * as proj from "ol/proj"; import { Style, Icon } from "ol/style"; import * as tilesLayers from "./tiles"; import * as networkLayers from "./network"; import { sizes, cacheStyle, makeBorderColor, makeCourseColor } from "./common"; import network from "../../tam/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 create = (target, coursesSimulation, onClick) => { 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); // 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.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); } } onClick(clicked); }); return map; };