171 lines
5.4 KiB
JavaScript
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`]),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
};
|