feat: add direct edit preview APIs
This commit is contained in:
@@ -8,7 +8,7 @@
|
||||
"road-compiler": "bin/road-compiler.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node test/index.js && node test/native-road-edits.js && node test/road-revisions.js && node test/direct-edit-solver.js && node test/fixtures.js",
|
||||
"test": "node test/index.js && node test/native-road-edits.js && node test/road-revisions.js && node test/direct-edit-solver.js && node test/workbench-edit-api.js && node test/fixtures.js",
|
||||
"test:fixtures:update-baseline": "node test/update-fixture-baselines.js",
|
||||
"road:workbench": "node bin/road-workbench.js",
|
||||
"road:export": "node bin/road-compiler.js",
|
||||
|
||||
61
test/workbench-edit-api.js
Normal file
61
test/workbench-edit-api.js
Normal file
@@ -0,0 +1,61 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert/strict');
|
||||
const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { compileInput } = require('../src/compile/compiler');
|
||||
const { ensureRevisionStore } = require('../src/compile/road-revisions');
|
||||
const { editState, editPreview } = require('../workbench/server');
|
||||
|
||||
const workspace = fs.mkdtempSync(path.join(os.tmpdir(), 'workbench-edit-api-'));
|
||||
const input = {
|
||||
areaId: 'edit-api-test',
|
||||
osmFile: path.join(workspace, 'source.osm'),
|
||||
outDir: path.join(workspace, 'outputs', 'native-road'),
|
||||
stagingDir: path.join(workspace, 'outputs', '_pipeline'),
|
||||
overridesFile: path.join(workspace, 'native-road-overrides.json'),
|
||||
trafficSignalsFile: path.join(workspace, 'native-traffic-signals.json'),
|
||||
options: { edgeLines: false, junctionTemplates: { enabled: false, references: [] } },
|
||||
};
|
||||
fs.copyFileSync(path.join(__dirname, 'fixtures', 'fengshu-er-road.osm'), input.osmFile);
|
||||
fs.writeFileSync(input.overridesFile, `${JSON.stringify({ schema: 'native-road-overrides/v1', overrides: [] })}\n`);
|
||||
fs.writeFileSync(
|
||||
input.trafficSignalsFile,
|
||||
`${JSON.stringify({ schema: 'native-traffic-signals/v1', provenance: 'empty', assemblies: { type: 'FeatureCollection', features: [] } })}\n`,
|
||||
);
|
||||
const initialized = ensureRevisionStore(workspace, input.options);
|
||||
const area = compileInput({
|
||||
...input,
|
||||
areaConfigSnapshotFile: initialized.paths.activeAreaConfig,
|
||||
editsFile: initialized.paths.activeEdits,
|
||||
}).area;
|
||||
|
||||
const state = editState(area);
|
||||
assert.equal(state.active, true);
|
||||
assert.equal(state.documentVersion, 0);
|
||||
assert.equal(state.handles.schema, 'road-edit-handles/v1');
|
||||
assert.equal(state.activeRevisionId, 'rev-0001');
|
||||
|
||||
const trackedFiles = [
|
||||
initialized.paths.activeEdits,
|
||||
initialized.paths.activeState,
|
||||
initialized.paths.activeAreaConfig,
|
||||
].map((file) => ({
|
||||
file,
|
||||
bytes: fs.readFileSync(file),
|
||||
mtimeMs: fs.statSync(file).mtimeMs,
|
||||
}));
|
||||
const preview = editPreview(area, { previewSeq: 17, document: state.document });
|
||||
assert.equal(preview.ok, true);
|
||||
assert.equal(preview.previewSeq, 17);
|
||||
assert.equal(preview.degraded, false);
|
||||
assert.equal(preview.handles.schema, 'road-edit-handles/v1');
|
||||
assert.ok(preview.layers.roadSurface);
|
||||
for (const tracked of trackedFiles) {
|
||||
assert.deepEqual(fs.readFileSync(tracked.file), tracked.bytes, `${tracked.file} bytes unchanged by preview`);
|
||||
assert.equal(fs.statSync(tracked.file).mtimeMs, tracked.mtimeMs, `${tracked.file} mtime unchanged by preview`);
|
||||
}
|
||||
|
||||
fs.rmSync(workspace, { recursive: true, force: true });
|
||||
console.log('workbench edit API tests passed');
|
||||
@@ -4,12 +4,20 @@
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const path = require('path');
|
||||
const { loadOverrides, validateOverrides, writeJsonAtomic } = require('../src/compile/native-road');
|
||||
const {
|
||||
compileRoadModel,
|
||||
compileGeometry,
|
||||
loadOverrides,
|
||||
validateOverrides,
|
||||
writeJsonAtomic,
|
||||
} = require('../src/compile/native-road');
|
||||
const { generate, validateDocument, runtime } = require('../src/native-traffic-signals');
|
||||
const { convertGeoJson } = require('../src/reference/gaode');
|
||||
const { exportNativeRoadPackage } = require('../src/export/native-road-package');
|
||||
const { compileInput } = require('../src/compile/compiler');
|
||||
const { ensureRevisionStore, setActiveAreaConfig } = require('../src/compile/road-revisions');
|
||||
const { GEOMETRY_VERSION, resolveDirectEditConstraints } = require('../src/compile/direct-edit-solver');
|
||||
const { loadEditDocument, validateEditDocument } = require('../src/compile/native-road-edits');
|
||||
|
||||
function startWorkbench({
|
||||
area = null,
|
||||
@@ -69,6 +77,19 @@ function handle(request, response, session) {
|
||||
return area
|
||||
? sendJson(response, 200, state(area, junctionReference, debug))
|
||||
: sendJson(response, 200, { active: false });
|
||||
if (request.method === 'GET' && url.pathname === '/api/edit-state')
|
||||
return Promise.resolve()
|
||||
.then(() => (area ? editState(area) : { active: false }))
|
||||
.then((value) => sendJson(response, 200, value))
|
||||
.catch((error) => sendJson(response, 400, { ok: false, error: error.message }));
|
||||
if (request.method === 'POST' && url.pathname === '/api/edit-preview')
|
||||
return readBody(request)
|
||||
.then((body) => {
|
||||
if (!area) throw new Error('请先导入 OSM 文件。');
|
||||
return editPreview(area, body);
|
||||
})
|
||||
.then((value) => sendJson(response, 200, value))
|
||||
.catch((error) => sendJson(response, 400, { ok: false, error: error.message }));
|
||||
if (request.method === 'GET' && url.pathname === '/api/session')
|
||||
return sendJson(response, 200, { active: Boolean(session.area), areaId: session.area?.id || null });
|
||||
if (request.method === 'POST' && url.pathname === '/api/import')
|
||||
@@ -210,6 +231,83 @@ function state(area, junctionReference = null, debug = false) {
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function editState(area) {
|
||||
const workspace = path.dirname(area.input);
|
||||
const initialized = ensureRevisionStore(workspace, area.nativeRoad);
|
||||
const directEdits = loadEditDocument(initialized.paths.activeEdits);
|
||||
const model = compileRoadModel(fs.readFileSync(area.input, 'utf8'), loadOverrides(area.outputs.nativeRoadOverrides));
|
||||
const resolution = resolveDirectEditConstraints(model, directEdits, {
|
||||
revisionId: initialized.active.activeRevisionId,
|
||||
compilerGeometryVersion: GEOMETRY_VERSION,
|
||||
});
|
||||
return {
|
||||
active: true,
|
||||
areaId: area.id,
|
||||
activeRevisionId: initialized.active.activeRevisionId,
|
||||
documentVersion: directEdits.documentVersion,
|
||||
document: directEdits,
|
||||
constraintStates: resolution.constraintStates,
|
||||
diagnostics: resolution.diagnostics,
|
||||
handles: resolution.handles,
|
||||
};
|
||||
}
|
||||
|
||||
function editPreview(area, body = {}) {
|
||||
const startedAt = Date.now();
|
||||
const workspace = path.dirname(area.input);
|
||||
const activeEditsFile = path.join(workspace, 'active', 'native-road-edits.json');
|
||||
const activeStateFile = path.join(workspace, 'active', 'state.json');
|
||||
if (!fs.existsSync(activeEditsFile) || !fs.existsSync(activeStateFile))
|
||||
throw new Error('编辑工作区尚未初始化,请先重新导入 OSM 文件。');
|
||||
const activeEdits = loadEditDocument(activeEditsFile);
|
||||
const draft = body.document
|
||||
? validateEditDocument(body.document)
|
||||
: validateEditDocument({
|
||||
...activeEdits,
|
||||
...(Array.isArray(body.constraints) ? { constraints: body.constraints } : {}),
|
||||
...(Array.isArray(body.operations) ? { operations: body.operations } : {}),
|
||||
});
|
||||
const activeState = readJson(activeStateFile);
|
||||
const model = compileRoadModel(fs.readFileSync(area.input, 'utf8'), loadOverrides(area.outputs.nativeRoadOverrides));
|
||||
const previewSeq = Number.isInteger(body.previewSeq) ? body.previewSeq : 0;
|
||||
const resolution = resolveDirectEditConstraints(model, draft, {
|
||||
revisionId: activeState.activeRevisionId,
|
||||
previewSeq,
|
||||
compilerGeometryVersion: GEOMETRY_VERSION,
|
||||
});
|
||||
const compiled = compileGeometry(model, loadOverrides(area.outputs.nativeRoadOverrides), {
|
||||
edgeLines: area.nativeRoad.edgeLines,
|
||||
junctionTemplates: area.nativeRoad.junctionTemplates,
|
||||
directEdit: resolution,
|
||||
});
|
||||
const diagnostics = [...compiled.diagnostics, ...resolution.diagnostics];
|
||||
const degraded = Date.now() - startedAt > 300;
|
||||
return {
|
||||
ok: true,
|
||||
previewSeq,
|
||||
degraded,
|
||||
revisionId: activeState.activeRevisionId,
|
||||
documentVersion: activeEdits.documentVersion,
|
||||
constraintStates: resolution.constraintStates,
|
||||
diagnostics,
|
||||
handles: resolution.handles,
|
||||
layers: {
|
||||
roadSurface: compiled.roadSurface,
|
||||
edgeLines: compiled.edgeLines,
|
||||
sidewalkSurface: compiled.sidewalkSurface,
|
||||
intersectionSurface: compiled.intersectionSurface,
|
||||
laneCenterlines: compiled.laneCenterlines,
|
||||
laneSeparators: compiled.laneSeparators,
|
||||
centerLines: compiled.centerLines,
|
||||
directionArrows: compiled.directionArrows,
|
||||
turnArrows: compiled.turnArrows,
|
||||
crosswalks: compiled.crosswalks,
|
||||
vehicleStopLines: compiled.vehicleStopLines,
|
||||
connectors: compiled.connectors,
|
||||
},
|
||||
};
|
||||
}
|
||||
// The compiler reports candidates as advisory diagnostics. Lift them into their
|
||||
// own payload with a stable index so the map can label them "#1, #2, ..." and
|
||||
// the inspector can offer a ready-to-paste cluster配置.
|
||||
@@ -427,4 +525,4 @@ function sendJson(response, status, value) {
|
||||
response.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8', 'Cache-Control': 'no-store' });
|
||||
response.end(`${JSON.stringify(value)}\n`);
|
||||
}
|
||||
module.exports = { startWorkbench, readMultipart };
|
||||
module.exports = { startWorkbench, readMultipart, editState, editPreview };
|
||||
|
||||
Reference in New Issue
Block a user