feat(qgis): add editable traffic signal assemblies

This commit is contained in:
2026-08-07 12:48:38 +08:00
parent 72fa04ddeb
commit e153a1c57d
24 changed files with 766 additions and 243 deletions

View File

@@ -17,8 +17,13 @@ const path = require("path");
const os = require("os");
const { execFileSync } = require("child_process");
const { qgisPaths } = require("./lib/tool-paths");
const { parseOsm } = require("./lib/osm");
const {
validateTrafficSignalSourceReferences,
} = require("./lib/traffic-signals");
const {
SCENE_LAYERS,
AUXILIARY_EDIT_LAYERS,
SCENE_FILE,
SCENE_STYLE_FILE,
layerFile,
@@ -34,6 +39,10 @@ const ogr2ogr = qgis.ogr2ogr;
const ogrinfo = qgis.ogrinfo;
const outDir = path.resolve(requireText(config.outDir, "outDir"));
const gpkgPath = path.resolve(requireText(config.gpkg, "gpkg"));
const inputPath = path.resolve(requireText(config.input, "input"));
const trafficSignalAssembliesPath = path.resolve(
config.trafficSignalAssemblies || path.join(outDir, "traffic_signal_assemblies.geojson"),
);
for (const exe of [ogr2ogr, ogrinfo]) {
if (!fs.existsSync(exe)) {
@@ -43,6 +52,9 @@ for (const exe of [ogr2ogr, ogrinfo]) {
if (!fs.existsSync(gpkgPath)) {
throw new Error(`GeoPackage not found: ${gpkgPath}\nRun the intermediates stage first.`);
}
if (!fs.existsSync(inputPath)) {
throw new Error(`Input OSM XML not found: ${inputPath}`);
}
if (!fs.existsSync(outDir)) {
throw new Error(`GeoJSON output directory not found: ${outDir}`);
}
@@ -51,6 +63,7 @@ console.log(`Reimport: ${gpkgPath}`);
console.log(`Target: ${outDir}`);
const present = gpkgLayers();
const trafficSignalControls = parseOsm(fs.readFileSync(inputPath, "utf8")).trafficSignalControls;
const missing = SCENE_LAYERS.filter((layer) => !present.has(layer.id)).map((layer) => layer.id);
if (missing.length) {
throw new Error(
@@ -68,11 +81,25 @@ try {
console.log(`${layer.id}\tfeatures=${collection.features.length}`);
return { layer, stagedPath, collection };
});
const auxiliary = AUXILIARY_EDIT_LAYERS.map((layer) => {
if (!present.has(layer.id)) throw new Error(`GeoPackage is missing auxiliary layer '${layer.id}'`);
const stagedPath = path.join(stagingDir, layer.file);
exportLayer(layer.id, stagedPath);
const collection = readCollection(stagedPath, layer.id);
const validated = validateTrafficSignalSourceReferences(collection, trafficSignalControls);
console.log(`${layer.id}\tfeatures=${validated.features.length}`);
return { layer, stagedPath, collection: validated };
});
for (const item of staged) {
// Copy rather than rename: the staging dir may be on another filesystem.
fs.copyFileSync(item.stagedPath, path.join(outDir, layerFile(item.layer)));
}
for (const item of auxiliary) {
const destination = item.layer.id === "traffic_signal_assemblies"
? trafficSignalAssembliesPath : path.join(outDir, item.layer.file);
fs.copyFileSync(item.stagedPath, destination);
}
const byId = new Map(staged.map((item) => [item.layer.id, item.collection]));
const scene = mergeScene((layer) => byId.get(layer.id));
@@ -117,7 +144,7 @@ function loadConfig(cliArgs) {
}
Object.assign(base, JSON.parse(fs.readFileSync(file, "utf8")));
}
for (const key of ["qgisApp", "outDir", "gpkg"]) {
for (const key of ["qgisApp", "input", "outDir", "gpkg", "trafficSignalAssemblies"]) {
if (cliArgs[key] !== undefined) base[key] = cliArgs[key];
}
return base;