2020-07-25 16:05:43 +00:00
|
|
|
|
require("ol/ol.css");
|
2020-07-17 17:17:06 +00:00
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
const { Map, View } = require("ol");
|
|
|
|
|
const { getVectorContext } = require("ol/render");
|
|
|
|
|
const Point = require("ol/geom/Point").default;
|
|
|
|
|
const proj = require("ol/proj");
|
2020-07-26 13:57:09 +00:00
|
|
|
|
const { Style, Icon } = require("ol/style");
|
|
|
|
|
const tilesLayers = require("./tiles");
|
|
|
|
|
const networkLayers = require("./network");
|
|
|
|
|
const { sizes, makeBorderColor, makeCourseColor } = require("./common");
|
|
|
|
|
const network = require("../../tam/network.json");
|
|
|
|
|
const simulation = require("../../tam/simulation");
|
2020-07-17 17:59:34 +00:00
|
|
|
|
|
2020-07-23 21:09:05 +00:00
|
|
|
|
const courseStyles = {};
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
const getCourseStyle = lineColor => {
|
|
|
|
|
if (!(lineColor in courseStyles)) {
|
|
|
|
|
const icon = window.document.createElement("canvas");
|
2020-07-23 21:09:05 +00:00
|
|
|
|
|
2020-07-23 23:32:39 +00:00
|
|
|
|
const shapeSize = sizes.courseSize;
|
|
|
|
|
const iconSize = sizes.courseSize + sizes.courseOuterBorder;
|
2020-07-23 21:09:05 +00:00
|
|
|
|
|
|
|
|
|
icon.width = iconSize;
|
2020-07-23 23:32:39 +00:00
|
|
|
|
icon.height = iconSize;
|
2020-07-23 21:09:05 +00:00
|
|
|
|
|
|
|
|
|
const cx = icon.width / 2;
|
|
|
|
|
const cy = icon.height / 2;
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
const iconCtx = icon.getContext("2d");
|
2020-07-23 21:09:05 +00:00
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
for (const [color, size] of [
|
|
|
|
|
[makeBorderColor(lineColor), sizes.courseOuterBorder],
|
|
|
|
|
[lineColor, sizes.courseBorder],
|
|
|
|
|
[makeCourseColor(lineColor), sizes.courseInnerBorder]
|
|
|
|
|
]) {
|
2020-07-23 21:09:05 +00:00
|
|
|
|
iconCtx.fillStyle = color;
|
|
|
|
|
iconCtx.strokeStyle = color;
|
|
|
|
|
iconCtx.lineWidth = size;
|
2020-07-25 16:05:43 +00:00
|
|
|
|
iconCtx.lineJoin = "round";
|
2020-07-23 21:09:05 +00:00
|
|
|
|
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();
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
courseStyles[lineColor] = new Style({
|
2020-07-23 21:09:05 +00:00
|
|
|
|
image: new Icon({
|
|
|
|
|
img: icon,
|
2020-07-25 16:05:43 +00:00
|
|
|
|
imgSize: [icon.width, icon.height]
|
|
|
|
|
})
|
2020-07-23 21:09:05 +00:00
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
return courseStyles[lineColor];
|
2020-07-23 21:09:05 +00:00
|
|
|
|
};
|
2020-07-23 18:49:01 +00:00
|
|
|
|
|
2020-07-26 13:57:09 +00:00
|
|
|
|
const create = target => {
|
2020-07-23 18:49:01 +00:00
|
|
|
|
const view = new View({
|
|
|
|
|
center: proj.fromLonLat([3.88, 43.605]),
|
2020-07-23 21:09:05 +00:00
|
|
|
|
zoom: 14,
|
2020-07-23 18:49:01 +00:00
|
|
|
|
maxZoom: 22,
|
2020-07-25 16:05:43 +00:00
|
|
|
|
constrainResolution: true
|
2020-07-23 15:29:35 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-07-17 17:17:06 +00:00
|
|
|
|
const map = new Map({
|
|
|
|
|
target,
|
|
|
|
|
layers: [
|
2020-07-26 13:57:09 +00:00
|
|
|
|
...tilesLayers.getLayers(),
|
|
|
|
|
...networkLayers.getLayers()
|
2020-07-17 17:17:06 +00:00
|
|
|
|
],
|
2020-07-25 16:05:43 +00:00
|
|
|
|
view
|
2020-07-23 18:49:01 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-07-26 13:57:09 +00:00
|
|
|
|
const stopsLayer = map.getLayers().item(3);
|
|
|
|
|
|
2020-07-23 23:32:39 +00:00
|
|
|
|
// Run courses simulation
|
2020-07-23 18:49:01 +00:00
|
|
|
|
const simulInstance = simulation.start();
|
|
|
|
|
|
2020-07-23 23:32:39 +00:00
|
|
|
|
// Course on which the view is currently focused
|
|
|
|
|
let focusedCourse = null;
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
const startFocus = courseId => {
|
|
|
|
|
if (courseId in simulInstance.courses) {
|
2020-07-23 23:32:39 +00:00
|
|
|
|
const course = simulInstance.courses[courseId];
|
2020-07-25 16:05:43 +00:00
|
|
|
|
|
2020-07-23 23:32:39 +00:00
|
|
|
|
view.animate({
|
|
|
|
|
center: course.position,
|
2020-07-25 16:05:43 +00:00
|
|
|
|
duration: 500
|
|
|
|
|
}, () => {
|
|
|
|
|
focusedCourse = courseId;
|
|
|
|
|
});
|
2020-07-23 23:32:39 +00:00
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
const stopFocus = () => {
|
2020-07-23 23:32:39 +00:00
|
|
|
|
focusedCourse = null;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// Draw courses directly on the map
|
2020-07-25 16:05:43 +00:00
|
|
|
|
map.on("postcompose", ev => {
|
2020-07-23 18:49:01 +00:00
|
|
|
|
simulInstance.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
|
2020-07-25 16:05:43 +00:00
|
|
|
|
/* eslint-disable no-underscore-dangle */
|
|
|
|
|
if (stopsLayer.renderer_) {
|
2020-07-23 18:49:01 +00:00
|
|
|
|
ev.context = stopsLayer.renderer_.context;
|
2020-07-25 16:05:43 +00:00
|
|
|
|
ev.inversePixelTransform =
|
|
|
|
|
stopsLayer.renderer_.inversePixelTransform;
|
|
|
|
|
/* eslint-enable no-underscore-dangle */
|
2020-07-23 18:49:01 +00:00
|
|
|
|
|
|
|
|
|
const ctx = getVectorContext(ev);
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
for (const course of Object.values(simulInstance.courses)) {
|
2020-07-23 21:09:05 +00:00
|
|
|
|
const color = network.lines[course.line].color;
|
|
|
|
|
const style = getCourseStyle(color);
|
|
|
|
|
|
2020-07-26 19:03:41 +00:00
|
|
|
|
style.getImage().setRotation(
|
|
|
|
|
view.getRotation() +
|
|
|
|
|
course.angle
|
|
|
|
|
);
|
|
|
|
|
|
2020-07-23 21:09:05 +00:00
|
|
|
|
ctx.setStyle(style);
|
|
|
|
|
|
2020-07-23 18:49:01 +00:00
|
|
|
|
const point = new Point(course.position);
|
2020-07-25 16:05:43 +00:00
|
|
|
|
|
2020-07-23 18:49:01 +00:00
|
|
|
|
ctx.drawGeometry(point);
|
2020-07-23 23:32:39 +00:00
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
if (course.id === focusedCourse) {
|
2020-07-23 23:32:39 +00:00
|
|
|
|
view.setCenter(course.position);
|
|
|
|
|
}
|
2020-07-23 18:49:01 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
map.render();
|
2020-07-17 17:17:06 +00:00
|
|
|
|
});
|
|
|
|
|
|
2020-07-23 18:49:01 +00:00
|
|
|
|
map.render();
|
2020-07-23 23:32:39 +00:00
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
map.on("singleclick", ev => {
|
2020-07-23 23:32:39 +00:00
|
|
|
|
const mousePixel = map.getPixelFromCoordinate(ev.coordinate);
|
|
|
|
|
const maxDistance = sizes.courseSize + sizes.courseOuterBorder;
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
for (const course of Object.values(simulInstance.courses)) {
|
2020-07-23 23:32:39 +00:00
|
|
|
|
const coursePixel = map.getPixelFromCoordinate(course.position);
|
|
|
|
|
const dx = mousePixel[0] - coursePixel[0];
|
|
|
|
|
const dy = mousePixel[1] - coursePixel[1];
|
|
|
|
|
const distance = dx * dx + dy * dy;
|
|
|
|
|
|
2020-07-25 16:05:43 +00:00
|
|
|
|
if (distance <= maxDistance * maxDistance) {
|
2020-07-23 23:32:39 +00:00
|
|
|
|
startFocus(course.id);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clicking anywhere else resets focus
|
|
|
|
|
stopFocus();
|
|
|
|
|
});
|
|
|
|
|
|
2020-07-17 17:17:06 +00:00
|
|
|
|
return map;
|
|
|
|
|
};
|
|
|
|
|
|
2020-07-26 13:57:09 +00:00
|
|
|
|
exports.create = create;
|