100 lines
5.0 KiB
JavaScript
100 lines
5.0 KiB
JavaScript
#!/usr/bin/env node
|
|
"use strict";
|
|
|
|
const fs = require("fs");
|
|
const path = require("path");
|
|
const { readAreaConfig } = require("./lib/area-config");
|
|
const { compileRoadModel, compileGeometry, loadOverrides, validateOverrides, writeJsonAtomic } = require("./lib/native-road");
|
|
|
|
const repoRoot = path.resolve(__dirname, "..");
|
|
|
|
function parseArgs(argv) {
|
|
const result = {};
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
if (!argv[index].startsWith("--")) continue;
|
|
const key = argv[index].slice(2).replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
|
|
result[key] = argv[index + 1] && !argv[index + 1].startsWith("--") ? argv[++index] : "true";
|
|
}
|
|
return result;
|
|
}
|
|
|
|
function compileArea(configPath) {
|
|
const area = readAreaConfig(configPath, { repoRoot });
|
|
const overrides = loadOverrides(area.outputs.nativeRoadOverrides);
|
|
const model = compileRoadModel(fs.readFileSync(area.input, "utf8"), overrides);
|
|
validateOverrides(overrides, model);
|
|
fs.mkdirSync(area.outputs.pipelineDir, { recursive: true });
|
|
const compiled = compileGeometry(model, overrides);
|
|
const staging = fs.mkdtempSync(path.join(area.outputs.pipelineDir, "native-road-"));
|
|
try {
|
|
const result = {
|
|
schema: "native-road-compiled/v1",
|
|
areaId: area.id,
|
|
source: { osm: area.input, overrides: area.outputs.nativeRoadOverrides },
|
|
model: { roads: model.roads, endpoints: model.endpoints, connections: model.connections },
|
|
movements: compiled.movements,
|
|
diagnostics: compiled.diagnostics,
|
|
layers: { roadSurface: "layers/road_surface.geojson", intersectionSurface: "layers/intersection_surface.geojson", laneCenterlines: "layers/lane_centerlines.geojson", connectors: "layers/connectors.geojson" },
|
|
};
|
|
const comparison = compareOsm2Streets(area, result.model, compiled);
|
|
writeJsonAtomic(path.join(staging, "compiled.json"), result);
|
|
writeJsonAtomic(path.join(staging, "diagnostics.json"), { schema: "native-road-diagnostics/v1", diagnostics: compiled.diagnostics });
|
|
writeJsonAtomic(path.join(staging, "comparison.json"), comparison);
|
|
writeJsonAtomic(path.join(staging, "layers", "road_surface.geojson"), compiled.roadSurface);
|
|
writeJsonAtomic(path.join(staging, "layers", "intersection_surface.geojson"), compiled.intersectionSurface);
|
|
writeJsonAtomic(path.join(staging, "layers", "lane_centerlines.geojson"), compiled.laneCenterlines);
|
|
writeJsonAtomic(path.join(staging, "layers", "connectors.geojson"), compiled.connectors);
|
|
fs.rmSync(area.outputs.nativeRoadDir, { recursive: true, force: true });
|
|
fs.renameSync(staging, area.outputs.nativeRoadDir);
|
|
return { area, result, comparison };
|
|
} catch (error) {
|
|
fs.rmSync(staging, { recursive: true, force: true });
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
function compareOsm2Streets(area, model, compiled) {
|
|
const source = path.join(area.outputs.geojsonDir, "road_surface.geojson");
|
|
let featureCount = null;
|
|
if (fs.existsSync(source)) {
|
|
const collection = JSON.parse(fs.readFileSync(source, "utf8"));
|
|
featureCount = Array.isArray(collection.features) ? collection.features.length : null;
|
|
}
|
|
const diagnosticsBySeverity = {};
|
|
const diagnosticsByRule = {};
|
|
for (const item of compiled.diagnostics) {
|
|
diagnosticsBySeverity[item.severity] = (diagnosticsBySeverity[item.severity] || 0) + 1;
|
|
diagnosticsByRule[item.rule] = (diagnosticsByRule[item.rule] || 0) + 1;
|
|
}
|
|
const dangling = compiled.diagnostics.filter((item) => item.rule === "unconnected-interior-road-end");
|
|
return {
|
|
schema: "native-road-comparison/v2",
|
|
nativeRoadCount: model.roads.length,
|
|
nativeRoadSurfaceFeatures: compiled.roadSurface.features.length,
|
|
nativeJunctionSurfaceFeatures: compiled.intersectionSurface.features.length,
|
|
nativeLaneCenterlineFeatures: compiled.laneCenterlines.features.length,
|
|
nativeConnectorFeatures: compiled.connectors.features.length,
|
|
nativeMovementCount: compiled.movements.length,
|
|
nativePublishedMovementCount: compiled.movements.filter((movement) => movement.geometryPublished).length,
|
|
nativeConnectionCount: model.connections.length,
|
|
unconnectedInteriorRoadEnds: dangling.length,
|
|
unconnectedEndsWithManualCandidates: dangling.filter((item) => item.manualCandidates?.length).length,
|
|
diagnosticsBySeverity,
|
|
diagnosticsByRule,
|
|
osm2streetsRoadSurfaceFeatures: featureCount,
|
|
osm2streetsAvailable: featureCount !== null,
|
|
note: "Counts are coverage evidence only; geometry quality requires diagnostic and visual review.",
|
|
};
|
|
}
|
|
|
|
function main() {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const configPath = path.resolve(args.config || path.join(repoRoot, "config", "areas", "nantaizi-lake-innovation-valley.json"));
|
|
const { area, result, comparison } = compileArea(configPath);
|
|
console.log(`NATIVE_ROAD_COMPILE_DONE ${JSON.stringify({ areaId: area.id, roads: result.model.roads.length, endpoints: result.model.endpoints.length, diagnostics: result.diagnostics.length, output: area.outputs.nativeRoadDir, comparison })}`);
|
|
}
|
|
|
|
if (require.main === module) main();
|
|
|
|
module.exports = { compileArea, parseArgs };
|