diff --git a/src/front/map/courses.js b/src/front/map/courses.js new file mode 100644 index 0000000..34a303d --- /dev/null +++ b/src/front/map/courses.js @@ -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); + }); +}; diff --git a/src/front/map/index.js b/src/front/map/index.js index 9f97422..2be9e31 100644 --- a/src/front/map/index.js +++ b/src/front/map/index.js @@ -1,59 +1,10 @@ import "ol/ol.css"; 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 { Style, Icon } from "ol/style"; -import * as tilesLayers from "./tiles"; -import * as networkLayers from "./network"; -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] - }) - }); - }, -); +import { getLayers as getTilesLayers } from "./tiles"; +import { getLayers as getNetworkLayers } from "./network"; +import { setupCoursesAnimation } from "./courses"; export const create = (target, coursesSimulation, onClick) => { const view = new View({ @@ -63,101 +14,18 @@ export const create = (target, coursesSimulation, onClick) => { constrainResolution: true }); + const tilesLayers = getTilesLayers(); + const networkLayers = getNetworkLayers(); + const stopsLayer = networkLayers[2]; + const map = new Map({ target, - layers: [ - ...tilesLayers.getLayers(), - ...networkLayers.getLayers() - ], + layers: [...tilesLayers, ...networkLayers], view }); - const stopsLayer = map.getLayers().item(3); - - // 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(); - }); - + setupCoursesAnimation(map, coursesSimulation, stopsLayer, onClick); 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; };