import * as turfHelpers from "@turf/helpers"; import { scaleZoom, makeBorderColor, makeCourseColor } from "./common"; import network from "../../data/network.json"; 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.7 * size; const borderSize = 0.3 * 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. * @param {Object.} handlers Handlers for the map events. */ export const addLayers = (map, handlers) => { 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`], }, }); } map.on("click", "vehicles-outer", event => { // Sort clicked courses in increasing order of departing/arrival time event.features.sort((feature1, feature2) => { const course1 = feature1.properties; const course2 = feature2.properties; const time1 = ( course1.speed > 0 ? course1.arrivalTime : course1.departureTime ); const time2 = ( course2.speed > 0 ? course2.arrivalTime : course2.departureTime ); return time1 - time2; }); handlers.onVehicleClick(event.features.map( feature => feature.properties.id )); }); // Show a pointer cursor when hovering over a vehicle map.on("mouseenter", "vehicles-outer", () => { map.getCanvas().style.cursor = "pointer"; }); map.on("mouseleave", "vehicles-outer", () => { map.getCanvas().style.cursor = ''; }); }; /** * 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)) ); };