Trajectory Simplification & Smoothing

Within the Local Spatial Processing Patterns guide, this page covers what a gateway does with a sequence of positions rather than a single one. A GNSS receiver reporting at one hertz produces 86 400 fixes a day per asset, most of which describe a straight line the previous fix already described, plus a metre or two of noise the receiver invented. Simplification removes the redundancy; smoothing removes the noise. They are different operations with different failure modes, and running them in the wrong order or at the wrong stage is one of the most common quiet data-quality faults in field telemetry.

The stakes are concrete. Unsimplified tracks dominate the byte budget analysed in bandwidth and async sync optimization; unsmoothed tracks produce phantom geofence crossings, inflated distance-travelled totals and jittering map displays. Both are solvable on a Cortex-A gateway with bounded memory, and both go wrong in ways that are invisible until someone compares a computed total against a known ground truth.

Where smoothing and simplification sit in the fix pipeline A pipeline from receiver to sync. Raw fixes are first gated on quality — HDOP, satellite count and a plausible-velocity test — then smoothed by a filter that estimates position from the fix and the previous state, then used for real-time decisions such as geofence tests, and only afterwards simplified for storage and transmission. A crossed-out path shows the common mistake of simplifying before the geofence test, which discards exactly the points near a boundary that the test needed. Smooth for decisions, simplify for storage — in that order raw fix · 1 Hz NMEA, ±2.5 m quality gate HDOP, sats, velocity smooth Kalman or α-β decisions geofence, dwell simplify + store Douglas–Peucker, spool simplifying before the decision stage discards the points nearest a boundary — exactly the ones the crossing test needed Smoothing changes where a point is. Simplification changes which points exist. Only the first is allowed to happen before a decision.
The order is not a preference. Simplification is lossy about membership, and the decision stage is the one consumer that cannot tolerate that.

Constraint mapping

Constraint Edge reality Direct effect on trajectory processing
RAM ceiling A day of 1 Hz fixes is 86 400 points Rules out whole-track algorithms that need the full sequence in memory; forces windowed or streaming variants
CPU Shared core, thermal envelope Recursive simplification with unbounded depth is unacceptable; iterative with a fixed stack is required
Latency Decisions have a deadline Smoothing must be causal — no filter that needs future samples can sit before the decision stage
Precision Coordinates are quantised for storage Simplification tolerances must exceed the quantisation step or they operate on rounding artefacts
Power Duty-cycled nodes sample sparsely At one fix per 15 minutes there is nothing to simplify and everything to interpolate — the whole pipeline inverts
Link budget Every retained point costs bytes The tolerance is a bandwidth dial, and the most effective one available

The last row explains why this section pays for itself. Simplifying a 1 Hz vehicle track at a 5 m tolerance typically retains 4–8% of the points with a maximum path deviation under the receiver’s own error — an order-of-magnitude reduction that costs nothing in fidelity anyone can measure.

Core technique 1: causal smoothing with an α-β filter

A full Kalman filter is the right answer when the motion model matters and the tuning effort is available; the walkthrough for that is in Kalman filtering noisy GPS fixes on a gateway. For a large share of asset-tracking work the α-β filter — a fixed-gain simplification of the same idea — delivers most of the benefit in a dozen lines with no matrix arithmetic at all.

# alpha_beta.py — fixed-gain causal smoother for a position/velocity state.
# Constant memory, no allocation, no history. Safe in a hot loop and on an MCU.
class AlphaBeta:
    __slots__ = ("alpha", "beta", "x", "v", "t", "primed")

    def __init__(self, alpha: float = 0.35, beta: float = 0.05):
        # alpha weights the position correction, beta the velocity correction.
        # Larger alpha tracks manoeuvres faster and admits more noise.
        self.alpha, self.beta = alpha, beta
        self.x = self.v = 0.0
        self.t = 0.0
        self.primed = False

    def update(self, measurement: float, t: float) -> float:
        if not self.primed:
            self.x, self.t, self.primed = measurement, t, True
            return measurement
        dt = t - self.t
        if dt <= 0.0 or dt > 30.0:
            # Clock step or a long gap: re-prime rather than extrapolate wildly.
            self.x, self.v, self.t = measurement, 0.0, t
            return measurement
        self.t = t
        predicted = self.x + self.v * dt          # constant-velocity prediction
        residual = measurement - predicted
        self.x = predicted + self.alpha * residual
        self.v = self.v + (self.beta * residual) / dt
        return self.x

Run one instance per axis on projected coordinates, never on raw latitude and longitude — a degree of longitude is a different distance at every latitude, and a filter tuned in degrees is tuned for exactly one place. The projected frame comes from the transform described in coordinate reference systems at the edge.

The dt guard is what makes this deployable. A duty-cycled node produces gaps of minutes, and a constant-velocity prediction across a fifteen-minute gap places the asset kilometres from reality. Re-priming on a long gap is the honest behaviour: the filter has no information about that interval and should not pretend otherwise.

Core technique 2: bounded simplification

Douglas–Peucker is the standard simplification algorithm and its textbook form is recursive, which on a track with a pathological shape can recurse as deeply as the point count. On an embedded target that is a stack overflow waiting for the right input. The iterative form with an explicit fixed-size stack has identical output and a bounded footprint; it is written out in full in Douglas–Peucker on a fixed-size stack.

For streaming use there is a cheaper option that needs no window at all. The dead-band rule retains a fix only when it deviates from the line between the last retained fix and the current one by more than a tolerance — a single-pass, constant-memory approximation that keeps around twice as many points as Douglas–Peucker for the same maximum error, and needs no buffer whatsoever.

Points retained and error introduced at four tolerances A 3 600-point vehicle track simplified at four tolerances. At 1 metre, 612 points are kept and the maximum deviation is 0.9 metres. At 5 metres, 214 points and 4.6. At 15 metres, 96 points and 13.8. At 50 metres, 38 points and 47, at which point corners visibly cut. A band marks the receiver's own 2.5 metre error, showing that tolerances below it are simplifying noise rather than shape. 3 600 fixes in — how many come out below the receiver's own error 1 m5 m15 m50 m 612 points kept · max deviation 0.9 m214 · 4.6 m96 · 13.8 m38 · 47 m — corners visibly cut The useful band is 3–15 m for vehicles and 1–3 m for pedestrians: above the receiver's noise, below the smallest shape that matters.
A tolerance under the receiver's error spends bytes preserving noise. One far above it discards corners a route reconstruction needs.

Core technique 3: filling the gaps

Removing points is only half the problem; a track also has holes, from an urban canyon, a tunnel, or a duty-cycled node that was asleep. The device has three options: leave the gap explicit, interpolate across it, or extrapolate through it with dead reckoning.

Leaving it explicit is almost always right for storage. A consumer that receives a track with a marked gap can decide what to do; one that receives an interpolated straight line through a tunnel cannot tell it from a measured one. Interpolation belongs at the presentation layer, not the acquisition layer.

Extrapolation with an inertial sensor is different, because it produces new information rather than an assumption. The accuracy and the honest time limits are set out in dead reckoning gap fill during GNSS outages — and the essential discipline is that extrapolated positions carry a flag and a growing uncertainty, so nothing downstream mistakes them for fixes.

Choosing tolerances from the job, not from the algorithm

Every parameter in this section — the filter gain, the simplification tolerance, the gap policy — is usually inherited from whatever example the implementer read first, and every one of them should come from a statement about the job instead. Three questions produce all of them.

What is the smallest movement anyone will act on? For a delivery fleet, arriving at a stop is a movement of a few metres and the answer is around 5 m. For a rail wagon in a yard, being on one siding rather than another is tens of metres. For a container tracked across an ocean, nothing under a kilometre changes a decision. The simplification tolerance should sit just below that figure — close enough to preserve every actionable movement, large enough to discard everything else.

How accurate is the position actually? The receiver’s own error sets the floor. A tolerance below it preserves noise at full price, and a filter tuned to track motion smaller than it will chase that noise faithfully. Read the accuracy the receiver reports rather than the number on the datasheet: an antenna under a metal roof reports honestly, and the honest number is often five times the specification.

How long can a decision wait? This sets the smoothing gain. A geofence that triggers a physical interlock cannot wait three fixes for the filter to settle, so it needs a responsive filter and a wider hysteresis band to compensate. A daily utilisation report can afford a heavily smoothed track, because nothing is decided in the moment.

Those three answers interact in a way that is worth stating explicitly, because it is the source of most bad configurations: the tolerance must exceed the position error, and the hysteresis band must exceed both. A geofence band narrower than the simplification tolerance will see crossings appear and disappear depending on which points survived simplification — a failure that presents as intermittent, unreproducible event duplication and takes weeks to attribute correctly.

What each stage is allowed to change

It is worth being pedantic about which stage may alter what, because the pipeline’s correctness depends on it and the constraints are easy to violate accidentally during a refactor.

The quality gate may reject a fix entirely. It must not modify one — a fix with poor geometry is either good enough to use or it is not, and a “corrected” fix with an optimistic accuracy figure attached poisons everything downstream that reads that figure.

The smoother may move a position and must not remove one. Its output is the same sequence at the same timestamps, with each position adjusted toward what the motion model considers plausible. It may also add information — an uncertainty estimate, a velocity — and downstream stages should use both.

The decision stage may not modify the sequence at all. It reads positions and emits events. Any stage that both decides and edits is one that will eventually decide based on its own edits.

The simplifier may remove positions and must not move the ones it keeps. A simplifier that also snaps points to a road network is doing two things, and the map-matched output is no longer a record of where the asset was measured to be — which matters the first time somebody disputes a report.

Holding to those four rules costs nothing and makes the pipeline auditable: given a raw track and the parameters, every downstream artefact is reproducible, and a discrepancy points at exactly one stage.

What each pipeline stage may and may not do to the sequence Four stages with their permissions. The quality gate may reject a fix but not modify one. The smoother may move a position and add uncertainty and velocity, but may not remove any. The decision stage may read and emit events but may not alter the sequence. The simplifier may remove positions but must not move the ones it retains. A note records that keeping to these rules makes every downstream artefact reproducible from the raw track plus the parameters. Four stages, four permissions, no overlap quality gate may reject a fix must not modify one — a "corrected" fix carries a dishonest accuracy figure smoother may move a position, may add σ and velocity must not remove any decision stage may read and emit events must not alter the sequence it is deciding from simplifier may remove positions must not move the ones it keeps — map matching is a separate, labelled output
Every field dispute about a track eventually asks which stage changed what. These four rules are the answer, decided before the dispute rather than during it.

Operational considerations

Track the retention ratio — points out over points in — as a first-class metric. It is a compact summary of both the receiver’s behaviour and the tolerance’s suitability. A ratio that jumps from 6% to 40% means the receiver has become noisy (blocked antenna, poor sky view) or the asset started behaving differently (stop-start traffic instead of a motorway). Either way it is visible days before the resulting bandwidth increase becomes a bill.

Watch the smoother’s residual too: the mean absolute difference between the raw measurement and the smoothed estimate. A residual that grows steadily indicates the filter’s motion model no longer matches reality — the classic case being a filter tuned for a vehicle being fitted to a slow-moving asset, where it lags every manoeuvre.

Reporting distance travelled, and why it is contentious

Almost every fleet eventually computes distance travelled from a track, and almost every fleet eventually finds that two systems disagree about it by ten to thirty percent. The disagreement is not a bug in either system; it is the direct consequence of which stage of this pipeline each one measured.

Summing the straight-line distances between raw fixes over-reports, sometimes badly. Receiver noise adds a random walk on top of the real motion, and summing the magnitudes of those small random displacements adds them all in. A vehicle parked for eight hours with a live receiver can accumulate several kilometres of purely fictional travel this way.

Summing over smoothed positions removes most of that, because the filter has already rejected the motion the model considers implausible. It is the figure most operational reports should use, and it should say so.

Summing over the simplified track under-reports, because the retained points are chords across the removed detail. At a 5 m tolerance the shortfall is typically one to three percent on a road route and considerably more on a track with tight curves.

None of the three is wrong; they answer different questions. The failure is leaving the choice implicit, so that a report built from the spooled track disagrees with a dashboard built from the live stream, and nobody can say which is right because nobody wrote down which stage each one measured.

Three rules keep it manageable. Compute the total on the device, at the smoothed stage, and transmit it as a counter alongside the positions — that way the authoritative figure travels with the data and does not depend on which subset survived. Include the stage in the field name, so the number’s meaning is self-documenting. And when a stationary period is detected, add nothing at all: a filter clamped to zero velocity should contribute zero distance, which is the single change that removes most of the phantom mileage fleets discover in their first month.

The same reasoning applies to any derived quantity — time in motion, average speed, idle duration. Each of them depends on which stage produced its input, and each of them will eventually be compared against a figure from a different system. Deciding the stage explicitly, once, is cheaper than reconciling them later.

Failure modes and recovery

Failure mode How it presents Detection Safe recovery
Simplification ahead of the geofence test Missed crossings near boundaries, no errors anywhere Compare crossing counts against raw replay Move simplification after the decision stage; it is a storage concern only
Filter tuned in degrees Behaves correctly at one latitude, badly elsewhere Residual metric varies by deployment region Filter in projected metres; re-tune once
Constant-velocity prediction across a gap Asset appears kilometres off after a tunnel dt guard fires; log the re-prime Re-prime rather than extrapolate; flag the gap
Recursive simplification stack overflow Segfault or exception on one specific track Fuzz with pathological shapes on the bench Iterative form with a bounded stack
Tolerance below the quantisation step Retention ratio near 100%, no size reduction Retention metric Raise the tolerance above the coordinate step size
Smoother masking a genuine jump A real teleport (recovered fix after an outage) gets averaged in Residual spike detector Re-prime on residuals beyond a plausibility threshold

Where this leaves the pipeline

Assembled, the stages produce a device that emits three distinct products from one fix stream, each correct for its consumer. A live position — smoothed, uncertainty-tagged, updated per fix — drives the display and the decision logic. A stream of events — crossings, dwells, alarms — is emitted by the decision stage from that live position and never regenerated afterwards. And a stored track — simplified, quantised, spooled — carries the history at whatever resolution the byte budget allows.

Keeping those three separate is what makes the whole thing debuggable. When a report disagrees with what an operator remembers seeing, the question is which of the three products the report was built from, and each one has a defined provenance. When an event is missing, the decision stage’s inputs are recoverable from the stored track only if the track kept the relevant points — which is why simplification comes last, and why the event stream is emitted independently rather than derived later.

The recurring theme across every technique here is that lossy operations are fine as long as the loss is chosen, bounded and recorded. A track simplified at a stated tolerance is honest data. The same track simplified by whatever the buffer happened to drop is not, and the two are indistinguishable to anyone downstream unless the device says which it produced.