diff --git a/src/front/index.js b/src/front/index.js index 588797f..3771d79 100644 --- a/src/front/index.js +++ b/src/front/index.js @@ -1,6 +1,6 @@ // eslint-disable-next-line node/no-extraneous-require require("regenerator-runtime/runtime"); -const { createMap } = require("./map"); +const map = require("./map/index.js"); -createMap(/* map = */ "map"); +map.create(/* map = */ "map"); diff --git a/src/front/map/common.js b/src/front/map/common.js new file mode 100644 index 0000000..23f2704 --- /dev/null +++ b/src/front/map/common.js @@ -0,0 +1,42 @@ +const colorModule = require("color"); + +/** + * Turn the main color of a line into a color suitable for using as a border. + * @param {string} mainColor Original color. + * @returns {string} Hexadecimal representation of the border color. + */ +const makeBorderColor = mainColor => { + const hsl = colorModule(mainColor).hsl(); + + hsl.color = Math.max(0, hsl.color[2] -= 20); + return hsl.hex(); +}; + +exports.makeBorderColor = makeBorderColor; + +/** + * Turn the main color of a line into a color suitable for using as a border. + * @param {string} mainColor Original color. + * @returns {string} Hexadecimal representation of the border color. + */ +const makeCourseColor = mainColor => { + const hsl = colorModule(mainColor).hsl(); + + hsl.color = Math.max(0, hsl.color[2] += 10); + return hsl.hex(); +}; + +exports.makeCourseColor = makeCourseColor; + +const sizes = { + segmentOuter: 8, + segmentInner: 6, + stopRadius: 6, + stopBorder: 1.5, + courseSize: 15, + courseOuterBorder: 13, + courseBorder: 10, + courseInnerBorder: 7 +}; + +exports.sizes = sizes; diff --git a/src/front/map.js b/src/front/map/index.js similarity index 51% rename from src/front/map.js rename to src/front/map/index.js index 8d384af..fa53b28 100644 --- a/src/front/map.js +++ b/src/front/map/index.js @@ -1,125 +1,15 @@ require("ol/ol.css"); const { Map, View } = require("ol"); - -const GeoJSON = require("ol/format/GeoJSON").default; -const reader = new GeoJSON({ featureProjection: "EPSG:3857" }); - -const TileLayer = require("ol/layer/Tile").default; -const XYZSource = require("ol/source/XYZ").default; - -const VectorLayer = require("ol/layer/Vector").default; -const VectorSource = require("ol/source/Vector").default; const { getVectorContext } = require("ol/render"); - const Point = require("ol/geom/Point").default; - const proj = require("ol/proj"); - -const { Style, Fill, Stroke, Circle, Icon } = require("ol/style"); -const colorModule = require("color"); - -const mapboxToken = "pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJja2NxaTUyMmUwcmFhMn\ -h0NmFsdzQ3emxqIn0.cyxF0h36emIMTk3cc4VqUw"; - -const simulation = require("../tam/simulation"); -const network = require("../tam/network.json"); - -const lineFeaturesOrder = (feature1, feature2) => { - const lines1 = feature1.get("lines"); - - if (lines1.length === 0) { - return -1; - } - - const lines2 = feature2.get("lines"); - - if (lines2.length === 0) { - return 1; - } - - return Math.min(...lines1) - Math.min(...lines2); -}; - -const makeDataSources = () => { - const segmentsSource = new VectorSource(); - const stopsSource = new VectorSource(); - - const readFeatures = hash => Object.values(hash).map(json => { - json.properties.lines = json.properties.routes.filter( - - // Only consider normal routes (excluding alternate routes) - ([lineRef, routeRef]) => - network.lines[lineRef].routes[routeRef].state === "normal" - ).map(([lineRef]) => lineRef); - - if (json.properties.lines.length >= 1) { - json.properties.colors = json.properties.lines.map( - lineRef => network.lines[lineRef].color - ); - } else { - json.properties.colors = ["#FFFFFF"]; - } - - return reader.readFeature(json); - }); - - segmentsSource.addFeatures(readFeatures(network.segments)); - stopsSource.addFeatures(readFeatures(network.stops)); - return { segmentsSource, stopsSource }; -}; - -const makeBorderColor = mainColor => { - const hsl = colorModule(mainColor).hsl(); - - hsl.color = Math.max(0, hsl.color[2] -= 20); - return hsl.hex(); -}; - -const makeCourseColor = mainColor => { - const hsl = colorModule(mainColor).hsl(); - - hsl.color = Math.max(0, hsl.color[2] += 10); - return hsl.hex(); -}; - -const sizes = { - segmentOuter: 8, - segmentInner: 6, - stopRadius: 6, - stopBorder: 1.5, - courseSize: 15, - courseOuterBorder: 13, - courseBorder: 10, - courseInnerBorder: 7 -}; - -const segmentBorderStyle = feature => new Style({ - stroke: new Stroke({ - color: makeBorderColor(feature.get("colors")[0]), - width: sizes.segmentOuter - }) -}); - -const segmentInnerStyle = feature => new Style({ - stroke: new Stroke({ - color: feature.get("colors")[0], - width: sizes.segmentInner - }) -}); - -const stopStyle = feature => new Style({ - image: new Circle({ - fill: new Fill({ - color: feature.get("colors")[0] - }), - stroke: new Stroke({ - color: makeBorderColor(feature.get("colors")[0]), - width: sizes.stopBorder - }), - radius: sizes.stopRadius - }) -}); +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"); const courseStyles = {}; @@ -169,53 +59,7 @@ const getCourseStyle = lineColor => { return courseStyles[lineColor]; }; -const createMap = target => { - - // Map background - const backgroundSource = new XYZSource({ - url: `https://api.mapbox.com/${[ - "styles", "v1", "mapbox", "streets-v11", - "tiles", "512", "{z}", "{x}", "{y}" - ].join("/")}?access_token=${mapboxToken}`, - tileSize: [512, 512] - }); - - const backgroundLayer = new TileLayer({ - source: backgroundSource - }); - - // Static data overlay - const { segmentsSource, stopsSource } = makeDataSources(); - - const segmentsBorderLayer = new VectorLayer({ - source: segmentsSource, - renderOrder: lineFeaturesOrder, - style: segmentBorderStyle, - - updateWhileInteracting: true, - updateWhileAnimating: true - }); - - const segmentsInnerLayer = new VectorLayer({ - source: segmentsSource, - renderOrder: lineFeaturesOrder, - style: segmentInnerStyle, - - updateWhileInteracting: true, - updateWhileAnimating: true - }); - - const stopsLayer = new VectorLayer({ - source: stopsSource, - renderOrder: lineFeaturesOrder, - style: stopStyle, - - minZoom: 13, - updateWhileInteracting: true, - updateWhileAnimating: true - }); - - // Setup map +const create = target => { const view = new View({ center: proj.fromLonLat([3.88, 43.605]), zoom: 14, @@ -226,14 +70,14 @@ const createMap = target => { const map = new Map({ target, layers: [ - backgroundLayer, - segmentsBorderLayer, - segmentsInnerLayer, - stopsLayer + ...tilesLayers.getLayers(), + ...networkLayers.getLayers() ], view }); + const stopsLayer = map.getLayers().item(3); + // Run courses simulation const simulInstance = simulation.start(); @@ -322,4 +166,4 @@ const createMap = target => { return map; }; -exports.createMap = createMap; +exports.create = create; diff --git a/src/front/map/network.js b/src/front/map/network.js new file mode 100644 index 0000000..05ce9ec --- /dev/null +++ b/src/front/map/network.js @@ -0,0 +1,132 @@ +const network = require("../../tam/network.json"); +const { makeBorderColor, sizes } = require("./common"); + +const GeoJSON = require("ol/format/GeoJSON").default; +const VectorLayer = require("ol/layer/Vector").default; +const VectorSource = require("ol/source/Vector").default; + +const { Style, Fill, Stroke, Circle } = require("ol/style"); + +const geojsonReader = new GeoJSON({ featureProjection: "EPSG:3857" }); + +// Style used for the border of line segments +const segmentBorderStyle = feature => new Style({ + stroke: new Stroke({ + color: makeBorderColor(feature.get("colors")[0]), + width: sizes.segmentOuter + }) +}); + +// Style used for the inner part of line segments +const segmentInnerStyle = feature => new Style({ + stroke: new Stroke({ + color: feature.get("colors")[0], + width: sizes.segmentInner + }) +}); + +// Style used for line stops +const stopStyle = feature => new Style({ + image: new Circle({ + fill: new Fill({ + color: feature.get("colors")[0] + }), + stroke: new Stroke({ + color: makeBorderColor(feature.get("colors")[0]), + width: sizes.stopBorder + }), + radius: sizes.stopRadius + }) +}); + +/** + * Order for features related to a network line inside the same layer. + * @param {Feature} feature1 First feature to order. + * @param {Feature} feature2 Second feature to order. + * @returns {number} -1 if `feature1` comes before `feature2`, 1 if + * `feature2` comes before `feature1`, 0 if the order is irrelevant. + */ +const lineFeaturesOrder = (feature1, feature2) => { + // Place features with no lines attributed on the background + const lines1 = feature1.get("lines"); + + if (lines1.length === 0) { + return -1; + } + + const lines2 = feature2.get("lines"); + + if (lines2.length === 0) { + return 1; + } + + // Draw lines with a lower numeric value first + return Math.max(...lines2) - Math.max(...lines1); +}; + +/** + * Create the list of layers for displaying the transit network. + * @returns {Array.} List of map layers. + */ +const getLayers = () => { + const segmentsSource = new VectorSource(); + const stopsSource = new VectorSource(); + + // Turn GeoJSON stops list and segments list into vector sources + const readFeatures = hash => Object.values(hash).map(json => { + json.properties.lines = json.properties.routes.filter( + + // Only consider normal routes (excluding alternate routes) + ([lineRef, routeRef]) => + network.lines[lineRef].routes[routeRef].state === "normal" + ).map(([lineRef]) => lineRef); + + if (json.properties.lines.length >= 1) { + json.properties.colors = json.properties.lines.map( + lineRef => network.lines[lineRef].color + ); + } else { + json.properties.colors = ["#FFFFFF"]; + } + + return geojsonReader.readFeature(json); + }); + + segmentsSource.addFeatures(readFeatures(network.segments)); + stopsSource.addFeatures(readFeatures(network.stops)); + + // Background layer on which the darker borders of line segments are drawn + const segmentsBorderLayer = new VectorLayer({ + source: segmentsSource, + renderOrder: lineFeaturesOrder, + style: segmentBorderStyle, + + updateWhileInteracting: true, + updateWhileAnimating: true + }); + + // Foreground layer on which the lighter inner part of line segments are + // drawn. The two layers are separated so that forks blend nicely together + const segmentsInnerLayer = new VectorLayer({ + source: segmentsSource, + renderOrder: lineFeaturesOrder, + style: segmentInnerStyle, + + updateWhileInteracting: true, + updateWhileAnimating: true + }); + + const stopsLayer = new VectorLayer({ + source: stopsSource, + renderOrder: lineFeaturesOrder, + style: stopStyle, + + minZoom: 13, + updateWhileInteracting: true, + updateWhileAnimating: true + }); + + return [segmentsBorderLayer, segmentsInnerLayer, stopsLayer]; +}; + +exports.getLayers = getLayers; diff --git a/src/front/map/tiles.js b/src/front/map/tiles.js new file mode 100644 index 0000000..502a67b --- /dev/null +++ b/src/front/map/tiles.js @@ -0,0 +1,25 @@ +const TileLayer = require("ol/layer/Tile").default; +const XYZSource = require("ol/source/XYZ").default; + +const mapboxToken = "pk.eyJ1IjoibWF0dGVvZGVsYWJyZSIsImEiOiJja2NxaTUyMmUwcmFhMn\ +h0NmFsdzQ3emxqIn0.cyxF0h36emIMTk3cc4VqUw"; + +/** + * Create the list of layers for displaying the background map. + * @returns {Array.} List of map layers. + */ +const getLayers = () => { + const backgroundSource = new XYZSource({ + url: `https://api.mapbox.com/${[ + "styles", "v1", "mapbox", "streets-v11", + "tiles", "512", "{z}", "{x}", "{y}" + ].join("/")}?access_token=${mapboxToken}`, + tileSize: [512, 512] + }); + + return [new TileLayer({ + source: backgroundSource + })]; +}; + +exports.getLayers = getLayers;