Files
osmWorkflow/scripts/lib/scene-layers.js

177 lines
4.2 KiB
JavaScript

"use strict";
// Single source of truth for the osm2streets render layers.
//
// The same nine layers, in the same order, previously appeared four times:
// the merged-scene z_index table, the scene style JSON, the generated QGIS
// project (layer dict + draw_order), and the README's manual rebuild snippet.
// Adding a layer or changing a z-index meant editing all of them in lockstep,
// and a missed copy produces a silently mis-stacked scene downstream in
// Blender/Cesium. Everything now derives from SCENE_LAYERS.
//
// zIndex doubles as draw order: lowest paints first (bottom of the stack).
// outline: null means "no stroke" (QGIS gets a fully transparent outline).
const SCENE_LAYERS = [
{
id: "road_surface",
splitKey: "roadSurface",
zIndex: 10,
title: "road surface",
fill: "#2b2b28",
outline: "#1e1e1c",
outlineWidth: 0.04,
},
{
id: "intersection_surface",
splitKey: "intersectionSurface",
zIndex: 20,
title: "intersection surface",
fill: "#2b2b28",
outline: "#1e1e1c",
outlineWidth: 0.04,
},
{
id: "sidewalks",
splitKey: "sidewalks",
zIndex: 30,
title: "sidewalks",
fill: "#bebeb6",
outline: "#9c9c94",
outlineWidth: 0.025,
},
{
id: "sidewalk_corners",
splitKey: "sidewalkCorners",
zIndex: 40,
title: "sidewalk corners",
fill: "#bebeb6",
outline: "#9c9c94",
outlineWidth: 0.025,
},
{
id: "lane_separators",
splitKey: "laneSeparators",
zIndex: 50,
title: "lane separators",
fill: "#eeeee6",
outline: null,
outlineWidth: 0,
},
{
id: "center_lines",
splitKey: "centerLines",
zIndex: 60,
title: "center lines",
fill: "#f5be2a",
outline: null,
outlineWidth: 0,
},
{
id: "crosswalks",
splitKey: "crosswalks",
zIndex: 70,
title: "crosswalks",
fill: "#fffff6",
outline: null,
outlineWidth: 0,
},
{
id: "vehicle_stop_lines",
splitKey: "vehicleStopLines",
zIndex: 80,
title: "vehicle stop lines",
fill: "#fffff6",
outline: null,
outlineWidth: 0,
},
{
id: "lane_arrows_webscale",
splitKey: "laneArrows",
zIndex: 90,
title: "lane arrows",
fill: "#fffff6",
outline: "#2b2b28",
outlineAlpha: 200,
outlineWidth: 0.015,
},
];
// Editable control layers share the GeoPackage/QGIS lifecycle but never enter
// the merged render scene or its draw order.
const AUXILIARY_EDIT_LAYERS = [
{
id: "traffic_signal_assemblies",
file: "traffic_signal_assemblies.geojson",
title: "traffic signal assemblies",
geometry: "Point",
},
];
const SCENE_FILE = "osm2streets_scene.geojson";
const SCENE_STYLE_FILE = "osm2streets_scene_style.json";
function layerFile(layer) {
return `${layer.id}.geojson`;
}
// getCollection(layer) -> FeatureCollection, so callers can source layers from
// the in-memory split (build) or from disk (reimport) with the same merge.
function mergeScene(getCollection) {
return {
type: "FeatureCollection",
features: SCENE_LAYERS.flatMap((layer) => {
const collection = getCollection(layer) || {};
return (collection.features || []).map((feature) => ({
...feature,
properties: {
...(feature.properties || {}),
render_layer: layer.id,
z_index: layer.zIndex,
},
}));
}),
};
}
function sceneStyle() {
return {
version: 1,
geometry: "polygon",
sortProperty: "z_index",
layerProperty: "render_layer",
layers: SCENE_LAYERS.map((layer) => ({
id: layer.id,
zIndex: layer.zIndex,
fill: layer.fill,
outline: layer.outline,
outlineWidth: layer.outlineWidth,
})),
};
}
// QGIS symbol properties want "r,g,b,a" strings rather than hex.
function qgisRgba(hex, alpha = 255) {
if (!hex) return "0,0,0,0";
const match = /^#?([0-9a-f]{6})$/i.exec(hex.trim());
if (!match) {
throw new Error(`Expected #rrggbb color, got: ${hex}`);
}
const value = parseInt(match[1], 16);
const r = (value >> 16) & 0xff;
const g = (value >> 8) & 0xff;
const b = value & 0xff;
return `${r},${g},${b},${alpha}`;
}
module.exports = {
SCENE_LAYERS,
AUXILIARY_EDIT_LAYERS,
SCENE_FILE,
SCENE_STYLE_FILE,
layerFile,
mergeScene,
sceneStyle,
qgisRgba,
};