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);
|
|||
|
// });
|
|||
|
// };
|