feat: solve direct edit constraints
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('assert/strict');
|
||||
const { resolveDirectEditConstraints, SOLVED_KINDS } = require('../src/compile/direct-edit-solver');
|
||||
const { resolveDirectEditConstraints } = require('../src/compile/direct-edit-solver');
|
||||
|
||||
const model = { roads: [], endpoints: [], connections: [], diagnostics: [] };
|
||||
|
||||
@@ -96,6 +96,69 @@ const anchoredHandle = anchoredResolution.handles.handles.find(
|
||||
);
|
||||
assert.equal(anchoredHandle.constraintId, 'edge-on-road-a');
|
||||
|
||||
const allKinds = [
|
||||
anchored,
|
||||
constraint({
|
||||
id: 'sidewalk-on-road-a',
|
||||
kind: 'road-sidewalk-width',
|
||||
anchor: { type: 'road-interval', roadId: 'road:a', startStation: 0.2, endStation: 0.8, side: 'left' },
|
||||
value: { widthMeters: 3, transition: 'linear' },
|
||||
}),
|
||||
constraint({
|
||||
id: 'divider-on-road-a',
|
||||
kind: 'road-lane-divider',
|
||||
anchor: { type: 'road-interval', roadId: 'road:a', startStation: 0.2, endStation: 0.8 },
|
||||
value: { boundaryIndex: 1, offsetMeters: 0.1, transition: 'smoothstep' },
|
||||
}),
|
||||
constraint({
|
||||
id: 'approach-width',
|
||||
kind: 'junction-approach-width',
|
||||
anchor: { type: 'junction-approach', nodeId: 'junction', segmentId: 'segment:a' },
|
||||
value: { widthMeters: 7 },
|
||||
}),
|
||||
constraint({
|
||||
id: 'approach-cutback',
|
||||
kind: 'junction-cutback',
|
||||
anchor: { type: 'junction-approach', nodeId: 'junction', segmentId: 'segment:a' },
|
||||
value: { cutbackMeters: 2 },
|
||||
}),
|
||||
constraint({
|
||||
id: 'corner-radius',
|
||||
kind: 'junction-corner-radius',
|
||||
anchor: { type: 'junction-corner', nodeId: 'junction', incomingRoadId: 'segment:a', outgoingRoadId: 'segment:b' },
|
||||
value: { radiusMeters: 4 },
|
||||
}),
|
||||
];
|
||||
const allKindsResolution = resolveDirectEditConstraints(junctionModel, document(allKinds));
|
||||
assert.deepEqual(
|
||||
allKindsResolution.constraintStates.map((state) => state.applied),
|
||||
[true, true, true, true, true, true],
|
||||
);
|
||||
assert.equal(allKindsResolution.roadProfiles.get('road:a').sidewalkWidths.left, 3);
|
||||
assert.equal(allKindsResolution.roadProfiles.get('road:a').laneDividerOffsets[1], 0.1);
|
||||
assert.equal(
|
||||
allKindsResolution.handles.handles.find((handle) => handle.kind === 'road-lane-divider').constraintId,
|
||||
'divider-on-road-a',
|
||||
);
|
||||
assert.equal(allKindsResolution.junctionPlans.get('junction').approaches['segment:a'].widthMeters, 7);
|
||||
assert.equal(allKindsResolution.junctionPlans.get('junction').approaches['segment:a'].cutbackMeters, 2);
|
||||
assert.equal(allKindsResolution.junctionPlans.get('junction').corners['segment:a->segment:b'], 4);
|
||||
|
||||
const invalidDivider = resolveDirectEditConstraints(
|
||||
junctionModel,
|
||||
document([
|
||||
constraint({
|
||||
kind: 'road-lane-divider',
|
||||
anchor: { type: 'road-interval', roadId: 'road:a', startStation: 0.2, endStation: 0.8 },
|
||||
value: { boundaryIndex: 3, offsetMeters: 0 },
|
||||
}),
|
||||
]),
|
||||
);
|
||||
assert.equal(invalidDivider.constraintStates[0].applied, false);
|
||||
assert.equal(invalidDivider.constraintStates[0].reason, 'invalid');
|
||||
assert.equal(invalidDivider.diagnostics[0].severity, 'error');
|
||||
assert.equal(invalidDivider.roadProfiles.size, 0);
|
||||
|
||||
// A disabled constraint is reported, never applied, and produces no diagnostic:
|
||||
// the user turned it off deliberately, so it is not a problem to report.
|
||||
const disabled = resolveDirectEditConstraints(model, document([constraint({ enabled: false })]));
|
||||
@@ -114,26 +177,23 @@ for (const status of ['pending', 'conflicted', 'stale']) {
|
||||
assert.deepEqual(resolution.diagnostics, []);
|
||||
}
|
||||
|
||||
// Until a kind is wired into the solver its constraints are reported as
|
||||
// unapplied with an explicit diagnostic, rather than approximated or ignored.
|
||||
// A solved kind with a missing road anchor is rejected as a blocking
|
||||
// constraint diagnostic rather than approximated or ignored.
|
||||
for (const status of ['exact', 'recheck']) {
|
||||
const resolution = resolveDirectEditConstraints(model, document([constraint({ status })]));
|
||||
const expected = SOLVED_KINDS.has('road-edge-offset')
|
||||
? { applied: true, reason: null, diagnostics: 0 }
|
||||
: { applied: false, reason: 'kind-not-solved', diagnostics: 1 };
|
||||
assert.deepEqual(resolution.constraintStates, [
|
||||
{
|
||||
constraintId: 'constraint-1',
|
||||
kind: 'road-edge-offset',
|
||||
status,
|
||||
applied: expected.applied,
|
||||
reason: expected.reason,
|
||||
applied: false,
|
||||
reason: 'invalid',
|
||||
},
|
||||
]);
|
||||
assert.equal(resolution.diagnostics.length, expected.diagnostics);
|
||||
assert.equal(resolution.diagnostics.length, 1);
|
||||
for (const item of resolution.diagnostics) {
|
||||
assert.equal(item.severity, 'info');
|
||||
assert.equal(item.rule, 'direct-edit-kind-not-solved');
|
||||
assert.equal(item.severity, 'error');
|
||||
assert.equal(item.rule, 'direct-edit-road-anchor-missing');
|
||||
assert.equal(item.subjectId, 'constraint:constraint-1');
|
||||
assert.equal(item.geometry, null);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user