feat: add native road compiler provider
This commit is contained in:
@@ -4,6 +4,7 @@ const fs = require("fs");
|
||||
const path = require("path");
|
||||
const { spawnSync } = require("child_process");
|
||||
const { readAreaConfig } = require("./lib/area-config");
|
||||
const { compileArea: compileNativeRoads } = require("./compile-native-roads");
|
||||
const { resolveStages } = require("./lib/build-stages");
|
||||
const { validateManifest, addIntegrity } = require("./lib/package-contract");
|
||||
const { blenderExecutable: resolveBlenderExecutable } = require("./lib/tool-paths");
|
||||
@@ -42,6 +43,10 @@ const requestedStages = args.stages
|
||||
? splitList(args.stages)
|
||||
: null;
|
||||
const stages = resolveStages(area.stages, requestedStages);
|
||||
const roadProvider = args.roadProvider || area.blender.roadProvider;
|
||||
if (!new Set(["osm2streets", "native"]).has(roadProvider)) {
|
||||
throw new Error("--road-provider must be \"osm2streets\" or \"native\".");
|
||||
}
|
||||
|
||||
|
||||
console.log(`Area: ${area.id}`);
|
||||
@@ -55,10 +60,10 @@ if (stages.reimport) {
|
||||
reimportGpkg(area);
|
||||
}
|
||||
if (stages.blender) {
|
||||
buildBlenderScene(area);
|
||||
buildBlenderScene(area, roadProvider);
|
||||
}
|
||||
if (stages.cesium) {
|
||||
exportCesium(area);
|
||||
exportCesium(area, roadProvider);
|
||||
}
|
||||
if (stages.compress) {
|
||||
compressCesiumGlb(area);
|
||||
@@ -208,16 +213,22 @@ function reimportGpkg(area) {
|
||||
});
|
||||
}
|
||||
|
||||
function buildBlenderScene(area) {
|
||||
function buildBlenderScene(area, roadProvider) {
|
||||
ensureFile(blenderExecutable(area), "Blender executable");
|
||||
ensureFile(path.join(repoRoot, "blender", "generate_scene.py"), "Blender scene generator");
|
||||
ensureFile(area.outputs.trafficSignalAssemblies, "Editable traffic signal assemblies");
|
||||
// Blender consumes the editable assembly layer; OSM only initializes it in
|
||||
// intermediates, so QGIS edits remain authoritative across later stages.
|
||||
writeTrafficSignals(area);
|
||||
ensureFile(area.outputs.trafficSignals, "Traffic signal anchors");
|
||||
if (roadProvider === "osm2streets") {
|
||||
ensureFile(area.outputs.trafficSignalAssemblies, "Editable traffic signal assemblies");
|
||||
// Blender consumes the editable assembly layer; OSM only initializes it in
|
||||
// intermediates, so QGIS edits remain authoritative across later stages.
|
||||
writeTrafficSignals(area);
|
||||
ensureFile(area.outputs.trafficSignals, "Traffic signal anchors");
|
||||
}
|
||||
fs.mkdirSync(path.dirname(area.outputs.blend), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(area.outputs.render), { recursive: true });
|
||||
if (roadProvider === "native") {
|
||||
compileNativeRoads(configPath);
|
||||
ensureNativeRoadLayers(area);
|
||||
}
|
||||
|
||||
const blenderArgs = [
|
||||
"--background",
|
||||
@@ -227,8 +238,6 @@ function buildBlenderScene(area) {
|
||||
"--",
|
||||
"--osm",
|
||||
area.input,
|
||||
"--geojson",
|
||||
area.outputs.geojsonDir,
|
||||
"--output",
|
||||
area.outputs.blend,
|
||||
"--render",
|
||||
@@ -236,6 +245,11 @@ function buildBlenderScene(area) {
|
||||
"--tree-style",
|
||||
area.blender.treeStyle,
|
||||
];
|
||||
if (roadProvider === "native") {
|
||||
blenderArgs.push("--native-road", area.outputs.nativeRoadDir);
|
||||
} else {
|
||||
blenderArgs.push("--geojson", area.outputs.geojsonDir);
|
||||
}
|
||||
if (area.blender.officeOverrides) {
|
||||
blenderArgs.push("--office-overrides", area.blender.officeOverrides);
|
||||
}
|
||||
@@ -255,17 +269,20 @@ function buildBlenderScene(area) {
|
||||
inputs: {
|
||||
config: fileRecord(configPath),
|
||||
osm: fileRecord(area.input),
|
||||
geojsonDir: fileRecord(area.outputs.geojsonDir),
|
||||
...sceneGeojsonRecords(area),
|
||||
trafficSignalAssemblies: fileRecord(area.outputs.trafficSignalAssemblies),
|
||||
trafficSignals: fileRecord(area.outputs.trafficSignals),
|
||||
...(roadProvider === "native" ? nativeRoadRecords(area) : sceneGeojsonRecords(area)),
|
||||
...(roadProvider === "osm2streets" ? {
|
||||
geojsonDir: fileRecord(area.outputs.geojsonDir),
|
||||
trafficSignalAssemblies: fileRecord(area.outputs.trafficSignalAssemblies),
|
||||
trafficSignals: fileRecord(area.outputs.trafficSignals),
|
||||
} : {}),
|
||||
},
|
||||
outputs: {
|
||||
blend: fileRecord(area.outputs.blend),
|
||||
render: fileRecord(area.outputs.render),
|
||||
},
|
||||
summary: {
|
||||
geojson: geojsonFeatureCounts(area),
|
||||
...(roadProvider === "native" ? { nativeRoad: nativeRoadFeatureCounts(area) } : { geojson: geojsonFeatureCounts(area) }),
|
||||
roadProvider,
|
||||
blendBytes: fileRecord(area.outputs.blend).bytes,
|
||||
renderBytes: fileRecord(area.outputs.render).bytes,
|
||||
},
|
||||
@@ -273,21 +290,49 @@ function buildBlenderScene(area) {
|
||||
});
|
||||
}
|
||||
|
||||
function exportCesium(area) {
|
||||
function ensureNativeRoadLayers(area) {
|
||||
ensureFile(path.join(area.outputs.nativeRoadDir, "compiled.json"), "Native road compilation");
|
||||
for (const file of ["road_surface.geojson", "intersection_surface.geojson", "sidewalk_surface.geojson"]) {
|
||||
ensureFile(path.join(area.outputs.nativeRoadDir, "layers", file), `Native road layer ${file}`);
|
||||
}
|
||||
}
|
||||
|
||||
function nativeRoadRecords(area) {
|
||||
const root = path.join(area.outputs.nativeRoadDir, "layers");
|
||||
return {
|
||||
nativeRoadCompiled: fileRecord(path.join(area.outputs.nativeRoadDir, "compiled.json")),
|
||||
nativeRoadSurface: fileRecord(path.join(root, "road_surface.geojson")),
|
||||
nativeIntersectionSurface: fileRecord(path.join(root, "intersection_surface.geojson")),
|
||||
nativeSidewalkSurface: fileRecord(path.join(root, "sidewalk_surface.geojson")),
|
||||
};
|
||||
}
|
||||
|
||||
function nativeRoadFeatureCounts(area) {
|
||||
const root = path.join(area.outputs.nativeRoadDir, "layers");
|
||||
return {
|
||||
roadSurface: featureCount(path.join(root, "road_surface.geojson")),
|
||||
intersectionSurface: featureCount(path.join(root, "intersection_surface.geojson")),
|
||||
sidewalkSurface: featureCount(path.join(root, "sidewalk_surface.geojson")),
|
||||
};
|
||||
}
|
||||
|
||||
function exportCesium(area, roadProvider) {
|
||||
ensureFile(blenderExecutable(area), "Blender executable");
|
||||
ensureFile(area.outputs.blend, "Blend scene");
|
||||
ensureFile(path.join(repoRoot, "blender", "export_cesium.py"), "Cesium exporter");
|
||||
fs.rmSync(area.outputs.packageStagingDir, { recursive: true, force: true });
|
||||
fs.mkdirSync(path.dirname(area.outputs.glb), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(area.outputs.metadata), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(area.outputs.trafficSignalsDynamicGlb), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(area.outputs.trafficSignalsCountdown0Glb), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(area.outputs.trafficSignalsCountdown1Glb), { recursive: true });
|
||||
if (roadProvider === "osm2streets") {
|
||||
fs.mkdirSync(path.dirname(area.outputs.trafficSignalsDynamicGlb), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(area.outputs.trafficSignalsCountdown0Glb), { recursive: true });
|
||||
fs.mkdirSync(path.dirname(area.outputs.trafficSignalsCountdown1Glb), { recursive: true });
|
||||
}
|
||||
|
||||
console.log("Stage: cesium");
|
||||
const started = Date.now();
|
||||
const startedAt = new Date(started).toISOString();
|
||||
runCommand(blenderExecutable(area), [
|
||||
const exporterArgs = [
|
||||
"--background",
|
||||
"--factory-startup",
|
||||
"--python",
|
||||
@@ -297,18 +342,22 @@ function exportCesium(area) {
|
||||
area.outputs.blend,
|
||||
"--glb",
|
||||
area.outputs.glb,
|
||||
"--dynamic-glb",
|
||||
area.outputs.trafficSignalsDynamicGlb,
|
||||
"--countdown-0-glb",
|
||||
area.outputs.trafficSignalsCountdown0Glb,
|
||||
"--countdown-1-glb",
|
||||
area.outputs.trafficSignalsCountdown1Glb,
|
||||
"--metadata",
|
||||
area.outputs.metadata,
|
||||
], "cesium");
|
||||
ensureFile(area.outputs.trafficSignalsDynamicGlb, "Dynamic traffic signal GLB");
|
||||
ensureFile(area.outputs.trafficSignalsCountdown0Glb, "Traffic countdown group 0 GLB");
|
||||
ensureFile(area.outputs.trafficSignalsCountdown1Glb, "Traffic countdown group 1 GLB");
|
||||
];
|
||||
if (roadProvider === "osm2streets") {
|
||||
exporterArgs.splice(-2, 0,
|
||||
"--dynamic-glb", area.outputs.trafficSignalsDynamicGlb,
|
||||
"--countdown-0-glb", area.outputs.trafficSignalsCountdown0Glb,
|
||||
"--countdown-1-glb", area.outputs.trafficSignalsCountdown1Glb,
|
||||
);
|
||||
}
|
||||
runCommand(blenderExecutable(area), exporterArgs, "cesium");
|
||||
if (roadProvider === "osm2streets") {
|
||||
ensureFile(area.outputs.trafficSignalsDynamicGlb, "Dynamic traffic signal GLB");
|
||||
ensureFile(area.outputs.trafficSignalsCountdown0Glb, "Traffic countdown group 0 GLB");
|
||||
ensureFile(area.outputs.trafficSignalsCountdown1Glb, "Traffic countdown group 1 GLB");
|
||||
}
|
||||
const semanticAssets = semanticAssetRecords(area);
|
||||
const finished = Date.now();
|
||||
const digest = glbDigest(area.outputs.glb);
|
||||
@@ -325,12 +374,15 @@ function exportCesium(area) {
|
||||
outputs: {
|
||||
glb: fileRecord(area.outputs.glb),
|
||||
metadata: fileRecord(area.outputs.metadata),
|
||||
trafficSignalsDynamicGlb: fileRecord(area.outputs.trafficSignalsDynamicGlb),
|
||||
...(roadProvider === "osm2streets" ? {
|
||||
trafficSignalsDynamicGlb: fileRecord(area.outputs.trafficSignalsDynamicGlb),
|
||||
} : {}),
|
||||
semanticAssets,
|
||||
},
|
||||
summary: {
|
||||
glb: glbSummary(digest),
|
||||
budget: evaluateGlbBudget(digest, area.budget),
|
||||
roadProvider,
|
||||
},
|
||||
warnings: glbBudgetWarnings("Cesium", digest, area.budget),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user