tracktracker/src/front/map/network.js

171 lines
5.4 KiB
JavaScript

import * as turfHelpers from "@turf/helpers";
import turfClone from "@turf/clone";
import network from "../../data/network.json";
import * as routing from "../../data/routing.js";
import { makeBorderColor, scaleZoom } from "./common";
const MIN_ZOOM_SEGMENTS = 9;
const MIN_ZOOM_STOPS = 11;
const MAX_ZOOM = 22;
const BORDER_SIZE = 3;
const SEGMENT_SIZE = 6;
const STOP_SIZE = 12;
/** Compute a line offset to share a line span with other adjacent lines. */
const getOffset = (size, index, count) => (
-size / 2
+ size / count / 2
+ index * size / count
);
/** Get the collection of line segments. */
const getSegments = () => {
// Aggregate segments spanning multiple routes
const groupedSegments = {};
for (const [lineRef, line] of Object.entries(network.lines)) {
for (const route of line.routes) {
for (let i = 0; i + 1 < route.stops.length; ++i) {
const stop1 = network.stops[route.stops[i]].id;
const stop2 = network.stops[route.stops[i + 1]].id;
const waypoints = routing.findPath(stop1, stop2);
for (let j = 0; j + 1 < waypoints.length; ++j) {
const point1 = waypoints[j];
const point2 = waypoints[j + 1];
const id = `${point1}-${point2}`;
if (id in groupedSegments) {
groupedSegments[id].properties.lines.add(lineRef);
} else {
groupedSegments[id] = (
turfClone(network.navigation[point1][point2])
);
groupedSegments[id].properties.lines = new Set();
groupedSegments[id].properties.lines.add(lineRef);
}
}
}
}
}
const segments = [];
for (const segment of Object.values(groupedSegments)) {
const lines = [...segment.properties.lines];
lines.sort();
const count = lines.length;
// Duplicate and offset segments for each route
for (const [index, line] of lines.entries()) {
const feature = turfClone(segment);
const props = feature.properties;
delete props.lines;
props.line = line;
props.innerSize = SEGMENT_SIZE / count;
props.innerColor = network.lines[line].color;
props.innerOffset = getOffset(SEGMENT_SIZE, index, count);
props.outerSize = (SEGMENT_SIZE + BORDER_SIZE) / count;
props.outerColor = makeBorderColor(props.innerColor);
props.outerOffset = getOffset(SEGMENT_SIZE + BORDER_SIZE, index, count);
segments.push(feature);
}
}
return turfHelpers.featureCollection(segments);
};
/** Get the collection of stops. */
const getStops = () => {
const stops = [];
for (const stop of Object.values(network.stops)) {
const lines = [...new Set(
stop.properties.routes.map(([lineRef]) => lineRef)
)];
lines.sort();
const count = lines.length;
// Duplicate and offset stops for each route
for (const [index, line] of lines.entries()) {
const feature = turfClone(stop);
const props = feature.properties;
props.line = line;
props.innerSize = (count - index) * STOP_SIZE / 2 / count;
props.innerColor = network.lines[line].color;
if (index === 0) {
props.outerSize = (STOP_SIZE + BORDER_SIZE) / 2;
} else {
props.outerSize = 0;
}
props.outerColor = makeBorderColor(props.innerColor);
stops.push(feature);
}
}
return turfHelpers.featureCollection(stops);
};
/**
* Add layers that display the transit network on the map.
* @param {Map} map The map to add the layers to.
*/
export const addLayers = map => {
map.addSource("lines", {
type: "geojson",
data: getSegments(),
});
map.addSource("stops", {
type: "geojson",
data: getStops(),
});
for (const lineRef of Object.keys(network.lines)) {
for (const kind of ["outer", "inner"]) {
map.addLayer({
id: `line-${lineRef}-${kind}`,
type: "line",
source: "lines",
filter: ["==", "line", lineRef],
minzoom: MIN_ZOOM_SEGMENTS,
layout: {
"line-cap": "round",
"line-join": "round",
},
paint: {
"line-color": ["get", `${kind}Color`],
"line-width": scaleZoom(["get", `${kind}Size`]),
"line-offset": scaleZoom(["get", `${kind}Offset`]),
},
});
}
}
for (const lineRef of Object.keys(network.lines)) {
for (const kind of ["outer", "inner"]) {
map.addLayer({
id: `stops-${lineRef}-${kind}`,
type: "circle",
source: "stops",
filter: ["==", "line", lineRef],
minzoom: MIN_ZOOM_STOPS,
paint: {
"circle-pitch-alignment": "map",
"circle-color": ["get", `${kind}Color`],
"circle-radius": scaleZoom(["get", `${kind}Size`]),
},
});
}
}
};