234 lines
7.5 KiB
JavaScript
234 lines
7.5 KiB
JavaScript
// 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<Object>} 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);
|
||
// });
|
||
// };
|