feat: classify direct edit replay states
This commit is contained in:
@@ -14,7 +14,7 @@ const { LAYER_REGISTRY, manifestForArea, validatePublishedLayers } = require('./
|
||||
const { loadOrGenerate, runtime } = require('../native-traffic-signals');
|
||||
const { readAreaConfigSnapshot } = require('./road-revisions');
|
||||
const { loadEditDocument } = require('./native-road-edits');
|
||||
const { resolveDirectEditConstraints } = require('./direct-edit-solver');
|
||||
const { GEOMETRY_VERSION, resolveDirectEditConstraints } = require('./direct-edit-solver');
|
||||
|
||||
function compileInput(input) {
|
||||
validateInput(input);
|
||||
@@ -46,7 +46,9 @@ function compileInput(input) {
|
||||
// compileGeometry. An area with no v2 document resolves to an empty result,
|
||||
// which leaves the geometry byte-identical to a compile without this stage.
|
||||
const directEdits = input.editsFile ? loadEditDocument(input.editsFile) : null;
|
||||
const directEdit = resolveDirectEditConstraints(model, directEdits);
|
||||
const directEdit = resolveDirectEditConstraints(model, directEdits, {
|
||||
compilerGeometryVersion: GEOMETRY_VERSION,
|
||||
});
|
||||
const compiled = compileGeometry(model, overrides, {
|
||||
edgeLines: area.nativeRoad.edgeLines,
|
||||
junctionTemplates: area.nativeRoad.junctionTemplates,
|
||||
|
||||
@@ -33,6 +33,7 @@ const KINDS = [
|
||||
'junction-corner-radius',
|
||||
];
|
||||
const MIN_LANE_WIDTH_METERS = 2.4;
|
||||
const GEOMETRY_VERSION = 'native-road-package/v1.1';
|
||||
|
||||
function emptyHandleManifest(context) {
|
||||
return {
|
||||
@@ -163,6 +164,60 @@ function junctionReserves(model) {
|
||||
return { reserves, approaches };
|
||||
}
|
||||
|
||||
function snapshotCandidates(model, constraint) {
|
||||
const snapshot = constraint.anchorSnapshot;
|
||||
if (!snapshot || !Array.isArray(snapshot.coordinate)) return [];
|
||||
const roads = (model.roads || []).filter((road) => {
|
||||
if (!road.centerline?.length) return false;
|
||||
const nodeMatch = snapshot.osmNodeIds?.some((id) => road.sourceNodeIds?.includes(String(id)));
|
||||
const nearest = road.centerline.reduce(
|
||||
(best, point) => Math.min(best, haversineMeters(snapshot.coordinate, point)),
|
||||
Infinity,
|
||||
);
|
||||
return nodeMatch && nearest <= 50;
|
||||
});
|
||||
return roads;
|
||||
}
|
||||
|
||||
function reconcileConstraints(model, constraints, base, junctionData, context) {
|
||||
const versionChanged = Boolean(
|
||||
context.compilerGeometryVersion &&
|
||||
base?.compilerGeometryVersion &&
|
||||
context.compilerGeometryVersion !== base.compilerGeometryVersion,
|
||||
);
|
||||
return constraints.map((constraint) => {
|
||||
if (!constraint.enabled || !constraint.anchor || !['exact', 'recheck'].includes(constraint.status))
|
||||
return constraint;
|
||||
let matched = false;
|
||||
if (constraint.kind.startsWith('road-')) {
|
||||
const road = roadForAnchor(model, constraint.anchor.roadId);
|
||||
matched = Boolean(road);
|
||||
if (matched && constraint.kind === 'road-lane-divider')
|
||||
matched = Number.isInteger(constraint.value?.boundaryIndex) && constraint.value.boundaryIndex < road.laneCount;
|
||||
if (!matched) {
|
||||
const candidates = snapshotCandidates(model, constraint);
|
||||
if (candidates.length === 1) return { ...constraint, status: 'pending' };
|
||||
return { ...constraint, status: candidates.length > 1 ? 'conflicted' : 'stale' };
|
||||
}
|
||||
} else if (constraint.kind === 'junction-corner-radius') {
|
||||
const approaches = junctionData.approaches.get(String(constraint.anchor.nodeId));
|
||||
matched = Boolean(
|
||||
approaches?.some((item) => item.segmentId === constraint.anchor.incomingRoadId) &&
|
||||
approaches?.some((item) => item.segmentId === constraint.anchor.outgoingRoadId),
|
||||
);
|
||||
} else {
|
||||
matched = Boolean(
|
||||
junctionData.approaches
|
||||
.get(String(constraint.anchor.nodeId))
|
||||
?.some((item) => item.segmentId === constraint.anchor.segmentId),
|
||||
);
|
||||
}
|
||||
if (!matched) return { ...constraint, status: 'stale' };
|
||||
if (versionChanged) return { ...constraint, status: 'recheck' };
|
||||
return constraint;
|
||||
});
|
||||
}
|
||||
|
||||
function roadForAnchor(model, roadId) {
|
||||
return (model.roads || []).find(
|
||||
(road) => road.id === roadId || road.sourceRoadId === roadId || road.segmentId === roadId,
|
||||
@@ -616,11 +671,12 @@ function resolveDirectEditConstraints(model, editDocument = null, context = {})
|
||||
const handles = emptyHandleManifest(context);
|
||||
const groups = roadGroups(model || {});
|
||||
const junctionData = junctionReserves(model || {});
|
||||
const solved = solveConstraints(model || {}, constraints, junctionData, diagnostics);
|
||||
const reconciled = reconcileConstraints(model || {}, constraints, editDocument?.base, junctionData, context);
|
||||
const solved = solveConstraints(model || {}, reconciled, junctionData, diagnostics);
|
||||
handles.reserves = junctionData.reserves;
|
||||
handles.handles = [
|
||||
...makeRoadHandles(model || {}, groups, handles.reserves, constraints),
|
||||
...makeJunctionHandles(model || {}, junctionData, constraints),
|
||||
...makeRoadHandles(model || {}, groups, handles.reserves, reconciled),
|
||||
...makeJunctionHandles(model || {}, junctionData, reconciled),
|
||||
];
|
||||
return {
|
||||
schema: RESOLUTION_SCHEMA,
|
||||
@@ -638,5 +694,6 @@ module.exports = {
|
||||
ACTIVE_STATUSES,
|
||||
SOLVED_KINDS,
|
||||
KINDS,
|
||||
GEOMETRY_VERSION,
|
||||
resolveDirectEditConstraints,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user