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>
165 lines
3.9 KiB
JavaScript
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,
|
|
};
|