// import { getVectorContext } from "ol/render"; // import Point from "ol/geom/Point"; // import { fromExtent } from 'ol/geom/Polygon'; // import { Style, Icon } from "ol/style"; import * as turfHelpers from "@turf/helpers"; import { scaleZoom, makeBorderColor, makeCourseColor } from "./common"; import network from "../../data/network.json"; const VEHICLE_SIZE = 15; const VEHICLE_BORDER = 10; const makeVehicleIcon = size => { const canvas = window.document.createElement("canvas"); const context = canvas.getContext("2d"); canvas.width = size; canvas.height = size; const cx = size / 2; const cy = size / 2; const iconSize = 0.4 * size; const borderSize = 0.2 * size; context.fillStyle = "black"; context.strokeStyle = "black"; context.lineWidth = borderSize; context.lineJoin = "round"; context.miterLimit = 200000; context.beginPath(); context.moveTo(cx - 0.3 * iconSize, cy + 0.5 * iconSize); context.lineTo(cx, cy - 0.5 * iconSize); context.lineTo(cx + 0.3 * iconSize, cy + 0.5 * iconSize); context.closePath(); context.stroke(); context.fill(); return { width: size, height: size, data: context.getImageData(0, 0, size, size).data, }; }; /** * Add layers that display the live vehicle positions on the map. * @param {Map} map The map to add the layers to. */ export const addLayers = map => { map.addImage("vehicle", makeVehicleIcon(128), {sdf: true}); map.addSource("vehicles", { type: "geojson", data: turfHelpers.featureCollection([]), }); for (let [kind, factor] of [ ["outer", 0.3], ["border", 0.25], ["inner", 0.2], ]) { map.addLayer({ id: `vehicles-${kind}`, type: "symbol", source: "vehicles", layout: { "icon-image": "vehicle", "icon-size": scaleZoom(factor), "icon-allow-overlap": true, "icon-rotation-alignment": "map", "icon-rotate": ["get", "bearing"], }, paint: { "icon-color": ["get", `${kind}Color`], }, }); } }; /** * Update the vehicle positions on the map. * @param {Map} map The map to update. * @param {Array} courses The active courses. */ export const update = (map, courses) => { const features = Object.values(courses); for (let course of features) { const props = course.properties; props.borderColor = network.lines[props.line].color; props.innerColor = makeCourseColor(props.borderColor); props.outerColor = makeBorderColor(props.borderColor); } map.getSource("vehicles").setData( turfHelpers.featureCollection(Object.values(courses)) ); }; // 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); // }); // };