Geo / Overlap Calculation
The overlap pipeline lives in src/core/elements/overlap/ and is responsible for:
- reconcile — derive
OverlapEntity[]from geometry and produce a set-diff patch the store applies atomically; - spatial index — RBush-based incremental R-tree, sub-millisecond queries at 50k entities;
- pair rules —
pairTable.tscentralises lane × secondary detection logic andObjectOverlapInfoemission; - polygon boolean —
polygon-clipping(Martinez) for preciselane corridor × secondary polygonregions feedingRegionOverlapInfo.
Public API (overlap/index.ts)
export { reconcileOverlaps, invalidateLaneCaches } from './reconcile';
export {
SpatialIndex,
bboxForEntity,
getSharedSpatialIndex,
resetSharedSpatialIndex,
} from './spatialIndex';
export { makeOverlapId, isDerivedOverlapId } from './overlapId';
export type { ReconcileMode, ReconcilePatch, BBox, IndexNode } from './types';2
3
4
5
6
7
8
9
External consumers — mapStore, the apolloIO worker, the recomputeOverlapsAsync worker bridge — should depend on ./index.ts only. Files not exported here are @internal.
Types (types.ts)
export interface BBox {
minX: number;
minY: number;
maxX: number;
maxY: number;
}
export interface IndexNode extends BBox {
id: string;
entityType: MapEntity['entityType'];
}
export type ReconcileMode =
| { mode: 'incremental'; dirtyIds: ReadonlySet<string> }
| { mode: 'full' };
export interface ReconcilePatch {
changes: Map<string, MapEntity>;
removedOverlapIds: Set<string>;
stats: {
pairsTested: number;
pairsMatched: number;
overlapsCreated: number;
overlapsRemoved: number;
durationMs: number;
};
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
reconcile.ts
export function reconcileOverlaps(
entities: ReadonlyMap<string, MapEntity>,
mode: ReconcileMode,
index?: SpatialIndex,
): ReconcilePatch;
export function invalidateLaneCaches(removedLaneIds: Iterable<string>): void;2
3
4
5
6
7
Phases:
- R-tree sync —
syncFromEntitiesformode = 'full',syncDirtyfor incremental. Skipped when the caller injects anindex. collectDirtyLanes— resolve dirty ids to a lane set, expanding secondary entities to their R-tree-neighbour lanes.- Pair scan — for each dirty lane, run
detectLaneLanePair(other lane neighbours) ordetectPairwith aPairRule. Pairs are deduped by sorted-participant id. diffWithExisting— set-diff against the existingOverlapEntityset, merging_userOverrides(isMerge, regionOverlaps), and write back each participant'soverlapIds.
invalidateLaneCaches clears the prefix-length cache used by computeLaneS when a lane is deleted.
spatialIndex.ts
export function bboxForEntity(entity: MapEntity): BBox | null;
export class SpatialIndex {
build(entities): void;
syncFromEntities(entities): void;
syncDirty(entities, dirtyIds): void;
insert(entity): void;
remove(id): void;
queryBBox(bbox): IndexNode[];
queryNeighbors(id): IndexNode[];
size(): number;
getBBox(id): BBox | null;
clear(): void;
}
export function getSharedSpatialIndex(): SpatialIndex;
export function resetSharedSpatialIndex(): void;2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Notes:
bboxForEntityfalls back: lane centerline → polygon → stop_line (withOVERLAP_STOPLINE_PROBE_DEGpadding) → polylines.- Internal
bboxSigstring is the geometric-change signature; immer freeze swaps that do not change geometry skip R-tree mutation entirely. - The shared singleton supports
resetso workers/tests can cycle between fresh trees.
pairTable.ts
export interface PairGeoHit {
intersects: boolean;
laneInterval?: { startS: number; endS: number };
isMerge?: boolean;
regionPolygon?: GeoPoint[];
}
export interface PairRule {
secondaryType: MapEntity['entityType'];
geometry: 'polygon' | 'stopLines' | 'polylines' | 'lane';
computeRegion?: boolean;
emitObjects(
lane: LaneEntity,
other: MapEntity,
hit: PairGeoHit,
opts?: { regionId?: string },
): ObjectOverlapInfo[];
}
export const PAIR_RULES: readonly PairRule[];
export function findPairRule(secondaryType: string): PairRule | null;
export function detectPair(lane, other, rule): PairGeoHit;
export function detectLaneLanePair(laneA, laneB): PairGeoHit;
export function emitLaneLaneObjects(laneA, laneB, hitForA, hitForB): ObjectOverlapInfo[];2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
PAIR_RULES covers: junction, crosswalk (with computeRegion), clearArea, parkingSpace, pncJunction, area, signal, stopSign, yieldSign, barrierGate, speedBump.
intersect.ts
export function bboxOfPoints(points, pad?): BBox | null;
export function bboxUnion(boxes): BBox | null;
export function bboxOverlap(a, b): boolean;
export function segmentsIntersect(a1, a2, b1, b2): GeoPoint | null;
export function pointInPolygon(point, polygon): boolean;
export function polylinesIntersect(a, b): boolean;
export function polylineIntersectsPolygon(line, polygon): boolean;
export interface SegmentParam {
segmentIndex: number;
t: number;
}
export function polylinePolygonCrossings(line, polygon): SegmentParam[];
export function endpointsCoincide(a, b, cosLat, toleranceM): boolean;
export function polylinePolylineCrossings(a, b): SegmentParam[];2
3
4
5
6
7
8
9
10
11
12
13
14
computeLaneS.ts
export function laneArcLength(lane: LaneEntity): number;
export function projectSegmentParam(lane, segmentIndex, t): number;
export function invalidateLaneArcLength(laneId: string): void;
export function clearLaneArcLengthCache(): void;2
3
4
laneCorridor.ts
export function laneCorridorPolygon(lane: LaneEntity): GeoPoint[];Returns a closed ring (first point repeated). Prefers Apollo leftBoundary / rightBoundary curves; falls back to centerline + sample widths via offsetPolylineDeg.
polyClip.ts
export function intersectPolygons(a, b): GeoPoint[][];
export function largestRing(rings): GeoPoint[] | null;2
Uses polygon-clipping for the boolean. Drops holes — region overlaps in the Apollo proto are simple rings. largestRing picks the largest piece by approximate metre² area (cosLat-corrected).
overlapId.ts / regionId.ts
export function makeOverlapId(participantIds): string;
export function isDerivedOverlapId(id): boolean;
export function makeRegionId(participantIds, slot?): string;
export function isDerivedRegionId(id): boolean;2
3
4
Formats: overlap_<sortedIds.join('_')> and region_<sortedIds.join('_')>__<slot> (slot=0 has no suffix).
geometryAdapters.ts
export function curveToPolyline(curve): GeoPoint[];
export function getCenterline(lane): GeoPoint[];
export function getPolygon(entity): GeoPoint[] | null;
export function getStopLines(entity): GeoPoint[][];
export function getPolylines(entity): GeoPoint[][];
export function isOverlapParticipant(entity): boolean;2
3
4
5
6
overridePaths.ts
export function laneIsMergeOverridePath(objectIndex): string;
export const REGION_OVERLAPS_OVERRIDE_PATH: 'regionOverlaps';
export function parseLaneIsMergeOverride(path): number | null;2
3