Move courses rendering to separate file
This commit is contained in:
parent
20f04408fc
commit
aeef4b5ae9
|
@ -0,0 +1,139 @@
|
||||||
|
import { getVectorContext } from "ol/render";
|
||||||
|
import Point from "ol/geom/Point";
|
||||||
|
import { fromExtent } from 'ol/geom/Polygon';
|
||||||
|
import { Style, Icon } from "ol/style";
|
||||||
|
import { sizes, cacheStyle, makeBorderColor, makeCourseColor } from "./common";
|
||||||
|
import network from "../../tam/network.json";
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
};
|
|
@ -1,59 +1,10 @@
|
||||||
import "ol/ol.css";
|
import "ol/ol.css";
|
||||||
|
|
||||||
import { Map, View } from "ol";
|
import { Map, View } from "ol";
|
||||||
import { getVectorContext } from "ol/render";
|
|
||||||
import Point from "ol/geom/Point";
|
|
||||||
import { fromExtent } from 'ol/geom/Polygon';
|
|
||||||
import * as proj from "ol/proj";
|
import * as proj from "ol/proj";
|
||||||
import { Style, Icon } from "ol/style";
|
import { getLayers as getTilesLayers } from "./tiles";
|
||||||
import * as tilesLayers from "./tiles";
|
import { getLayers as getNetworkLayers } from "./network";
|
||||||
import * as networkLayers from "./network";
|
import { setupCoursesAnimation } from "./courses";
|
||||||
import { sizes, cacheStyle, makeBorderColor, makeCourseColor } from "./common";
|
|
||||||
import network from "../../tam/network.json";
|
|
||||||
|
|
||||||
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 create = (target, coursesSimulation, onClick) => {
|
export const create = (target, coursesSimulation, onClick) => {
|
||||||
const view = new View({
|
const view = new View({
|
||||||
|
@ -63,101 +14,18 @@ export const create = (target, coursesSimulation, onClick) => {
|
||||||
constrainResolution: true
|
constrainResolution: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const tilesLayers = getTilesLayers();
|
||||||
|
const networkLayers = getNetworkLayers();
|
||||||
|
const stopsLayer = networkLayers[2];
|
||||||
|
|
||||||
const map = new Map({
|
const map = new Map({
|
||||||
target,
|
target,
|
||||||
layers: [
|
layers: [...tilesLayers, ...networkLayers],
|
||||||
...tilesLayers.getLayers(),
|
|
||||||
...networkLayers.getLayers()
|
|
||||||
],
|
|
||||||
view
|
view
|
||||||
});
|
});
|
||||||
|
|
||||||
const stopsLayer = map.getLayers().item(3);
|
setupCoursesAnimation(map, coursesSimulation, stopsLayer, onClick);
|
||||||
|
|
||||||
// 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.render();
|
||||||
});
|
|
||||||
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue