Change Detection & Map Maintenance

An HD map starts decaying the day it ships. Road markings are repainted, junctions are rebuilt, lanes are closed for works, and every one of those changes makes the map slightly wrong somewhere. The fleet drives through all of it and, unlike a survey vehicle, does so continuously — which makes fleet telemetry the only source of change signal with the coverage the problem needs.

The difficulty is not detecting disagreement. Disagreement is everywhere: sensor noise, weather, dynamic objects, and localization error all produce it. The difficulty is separating the disagreements that mean the world changed from the ones that mean the observation was poor, and doing so precisely enough that a surveyor queue stays workable.

The four things that produce a map-observation disagreement, and how each is distinguished:

Four Causes of Disagreement and the Test That Rejects Each Four rows naming a cause, its signature, and the filter that removes it, with only the real change passing all three filters. causesignature rejected bysurvives? dynamic object a lorry, a pedestrian gone on the next pass persistence no localization failure the pose was wrong global, directionally consistent spatial structure test no sensor fault one vehicle's extrinsic drifted one vehicle, many passes cross-vehicle agreement no real world change the road was rebuilt persistent · local · agreed nothing — it is the signal yes

Two constraints shape everything below and are worth stating before the mechanism. The first is bandwidth: the fleet produces orders of magnitude more sensor data than any uplink can carry, so detection has to happen onboard and only a description can travel. The second is surveyor time, which is the scarce resource that decides whether the whole loop is worth running. Together they push the design toward doing as much narrowing as possible as early as possible, and toward optimising precision rather than recall — a change found a cycle late is a cost, and a queue nobody works through is a failure.

The third constraint is subtler and shapes the safety argument. Fleet evidence is abundant and weak: many observations, each of which could be a sensor fault, a localization failure or weather. Survey evidence is scarce and strong. A maintenance loop that treats the two as interchangeable — promoting a well-agreed fleet observation to the same status as a survey — has quietly moved a safety-relevant claim from an accountable process to an automated one. Keeping the distinction explicit is what makes it possible to automate part of the loop without automating the part that matters.

Finally, the loop has to be closable in both directions. A change that is detected, surveyed and applied is only half the story; a change that is detected, applied automatically and turns out to have been wrong has to be revertible on the same signal that produced it, without a human noticing first. That symmetry — the fleet as both the trigger and the verifier — is what makes any automation in this loop defensible.

Detection Strategy Comparison #

Strategy Detects False-positive rate Latency Cost per vehicle
Localization residual monitoring Anything geometric High Immediate Negligible
Semantic feature comparison Markings, signs, poles Medium One pass Moderate
Occupancy differencing Added or removed structure Medium Several passes Moderate
Explicit re-survey Everything, definitively None Weeks Very high

Residual monitoring is nearly free because the localizer already computes it, which makes it the right trigger and the wrong decision: it fires on weather, on dynamic objects and on its own failures. The production pattern is residual monitoring as a cheap filter over the whole fleet, semantic comparison on the regions it flags, and a survey only for candidates that survive scoring.

Stage-by-Stage Implementation #

Stage 1 — Trigger on localization residual structure #

The constraint: the trigger must be nearly free, because it runs on every frame of every vehicle. The localizer's residual field already exists, and its structure is what distinguishes a change from a pose error.

python
import numpy as np

def residual_is_local(residuals: np.ndarray, xy: np.ndarray,
                      frac: float = 0.15, ratio: float = 4.0) -> bool:
    """True when large residuals form a compact cluster rather than a global shift."""
    hi = residuals > np.percentile(residuals, 100 * (1 - frac))
    if hi.sum() < 20:
        return False
    spread_hi = np.linalg.norm(xy[hi].std(axis=0))
    spread_all = np.linalg.norm(xy.std(axis=0))
    return spread_hi < spread_all / ratio

A pose error makes every residual large, so the high-residual set has the same spatial spread as the frame; a real change confines them, so the spread collapses. Expected output: a boolean per frame, computed from arrays the localizer already has.

Stage 2 — Compare semantics in the flagged region #

The constraint: a geometric residual says something differs, not what. Comparing extracted semantic features — marking lines, poles, kerbs — against the map's own features turns a region into a described candidate: the lane boundary here is 0.6 m north of where the map has it.

This reuses the extraction from extracting lane boundaries from point cloud data, run on a small region rather than a tile, which is what makes it affordable onboard.

Stage 3 — Score by independent agreement #

The constraint: promotion requires evidence that a single vehicle cannot manufacture. Agreement is counted over vehicles and over passes, and the two are not interchangeable — twenty passes by one vehicle with a drifting extrinsic all agree and are all wrong.

python
def confidence(observations) -> float:
    """Agreement-weighted confidence, saturating in passes and in vehicles."""
    vehicles = len({o.vehicle_id for o in observations})
    passes = len(observations)
    agree = np.mean([o.agrees_with_consensus for o in observations])
    return float(agree * (1 - np.exp(-vehicles / 2.0)) * (1 - np.exp(-passes / 4.0)))

Both saturating terms are needed: the vehicle term is what stops one faulty rig from promoting anything, and the pass term is what stops a single unlucky frame from doing so.

How the two terms behave, and why one alone is not enough:

Confidence from Vehicles and Passes Together Three evidence combinations with their vehicle term, pass term, product and promotion outcome against a threshold. evidencevehicle term pass termconfidencepromoted? 20 passes, 1 vehicle a drifting extrinsic agrees with itself 0.39 0.99 0.39 no 2 passes, 2 vehicles independent but thin 0.63 0.39 0.25 no 6 passes, 3 vehicles independent and repeated 0.78 0.78 0.61 yes promotion threshold 0.60 — neither term alone reaches it, which is the whole design

Stage 4 — Route by what the change would do #

The constraint: automation may narrow the map, never widen it. A change that removes drivable space or a connection has an unambiguous safe interpretation and can be applied automatically; one that adds either is a claim only a survey can make.

That asymmetry is the entire safety argument for automating any part of this loop, and it is worth stating in the release record rather than leaving implicit in the code.

Validation & QC Automation #

python
def assert_change_pipeline(candidates, labelled) -> None:
    tp = sum(1 for c in candidates if c.id in labelled.real)
    fp = len(candidates) - tp
    precision = tp / max(len(candidates), 1)
    recall = tp / max(len(labelled.real), 1)
    assert precision >= 0.80, f"precision {precision:.2f} — the queue will be abandoned"
    assert recall >= 0.60, f"recall {recall:.2f} — too many changes found by vehicles"

    for c in candidates:
        if c.auto_applied:
            assert c.kind in {"remove_lane", "remove_connection", "narrow_lane"}, \
                f"{c.id}: automation widened the map"

The enforced thresholds: precision ≥0.80, because a queue below that stops being worked; recall ≥0.60, accepting that some changes arrive late; and zero automatic applications outside the narrowing set, which is a structural rule rather than a statistical one.

Edge Cases & Failure Patterns #

Seasonal foliage flags a lane every autumn. The occupancy above the carriageway changes and the road does not. Restrict the comparison to the drivable surface and its immediate margin.

A newly resurfaced road flags as changed everywhere. It genuinely is — the surface features the map holds no longer exist. This is a real change of the kind that needs a re-survey, and the detector is right; the queue triage is what stops it from being twenty separate candidates.

A construction phase flips a lane closed and open weekly. Persistence over a fixed window mistakes each flip for a change. Track the candidate's history rather than its current state, and mark the region as volatile rather than re-detecting it.

One vehicle drives a route nobody else does. Cross-vehicle agreement is unreachable there, so genuine changes never promote. Route a survey vehicle rather than lowering the threshold — the alternative promotes single-vehicle evidence everywhere.

Candidates cluster at tile boundaries. The comparison is running against a stitched region whose seam is misaligned, which is a tiling problem rather than a change. Validate the seam as described in managing map tile boundaries in ROS2.

What each stage costs per vehicle per day, which is what decides where it runs:

Cost per Vehicle per Day, by Stage Four rows pairing a change-detection stage with its per-vehicle daily cost and where it must run. one vehicle, one day of driving residual structure test every frame arrays the localizer already has effectively free clustering 1% of frames a DBSCAN over a few thousand points seconds semantic comparison flagged regions only boundary extraction on small regions minutes uplink described candidates only ~200 kB against 2.6 TB kept onboard the binding constraint every stage after the first is affordable only because the first rejects 99% and costs nothing to run

Performance & Scale Notes #

The trigger is free — it reads arrays the localizer already produced — and that is what makes fleet-wide coverage possible. Semantic comparison is the expensive stage and runs only on triggered regions, which on a healthy map is a fraction of a percent of driving.

Uplink is the real constraint. A candidate is a small structured record — a region, a described difference, a confidence — measured in kilobytes, and it must stay that way: uploading the point cloud that produced it is what turns a change-detection system into a bandwidth project. Keep the evidence onboard, keyed by candidate, and fetch it only for candidates a surveyor actually opens.

FAQ #

How do you tell a map change from a localization failure? #

By whether the disagreement is local or global. A real change is confined to a region and the rest of the frame still matches the map; a localization failure shifts everything at once. Testing the residual field for spatial structure separates them cheaply: a change produces a compact cluster of large residuals surrounded by small ones, while a pose error produces a residual that is large everywhere and consistent in direction.

How many observations does a change need before it is acted on? #

Enough independent ones that a single vehicle's fault cannot promote it — in practice several passes by at least two vehicles, with agreement on what changed rather than merely that something did. Requiring independence matters more than requiring volume: twenty passes by one vehicle with a drifting extrinsic all agree with each other and are all wrong, while three passes by three vehicles agreeing on the same new kerb line is strong evidence.

Can map updates be applied automatically? #

Some can. A change that only removes something — a lane closed by cones — can be applied automatically because the safe interpretation is unambiguous. A change that adds drivable space or alters connectivity cannot, because the automation would be asserting that a vehicle may now go somewhere it previously could not, which is precisely the class of claim a survey exists to make. Routing by that distinction is what makes any automation acceptable.

What is the cost of a false positive? #

Mostly surveyor time, which is the scarcest resource in map maintenance and the reason precision matters more than recall here. A detector that reports every construction cone as a map change produces a queue nobody works through, and the genuine changes buried in it are then found by vehicles rather than by the process. Tuning for precision and accepting that some changes arrive a cycle late is the trade almost every fleet ends up making.

Up one level: Sensor Fusion & Spatial Data Alignment — the pipeline whose observations this stage turns back into map updates.