Files
osmWorkflow/scripts/lib/scene-layers.js
que01 59628cba82 feat: add reimport stage for hand-edited GeoPackages
QGIS 手工修正后的回导流程此前是 README 里的三段 shell:一个硬编码
area-id 和 ogr2ogr 绝对路径的 for 循环、一段内联 node heredoc、再加一次
npm run build。改为一个 reimport 阶段:

  npm run build -- --config config/areas/<area-id>.json \
    --stages reimport,blender,cesium

scripts/reimport-gpkg.js 先把全部图层导出到临时目录并逐个校验,全部通过
才写回 osm2streets_web_out/。ogr2ogr 对不存在的图层退出码非 0 但仍会留下
0 字节文件,原先逐图层 mv 会静默用空图层覆盖好数据。

intermediates 与 reimport 同时指定直接报错——前者用 OSM 重建 GeoPackage,
正好抹掉后者要读回的手工修改。reimport 不含在 all 中。

同时新增 scripts/lib/scene-layers.js 作为 9 个渲染图层的唯一定义源。此前
该表在合并场景、场景样式 JSON、生成的 QGIS 工程、README 手工流程中各有
一份副本,改一处漏其余会导致图层叠放顺序出错并流入 Blender/Cesium。

验证:同一 OSM 输入下,改动前后 intermediates 产物逐字节一致
(scene_style.json、.qgz 符号定义、9 个图层几何与属性);reimport 对
未修改图层无损回导。

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 17:21:59 +08:00

165 lines
3.9 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,
},
];
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,
SCENE_FILE,
SCENE_STYLE_FILE,
layerFile,
mergeScene,
sceneStyle,
qgisRgba,
};