# Demo — Bearings-Only Tracker

One aircraft, one radar-warning receiver, and a pop-up emitter nobody put on the laydown. Every few seconds the RWR reports a bearing — a direction, never a distance — and an extended Kalman filter (EKF) folds it into its estimate of where the emitter is. This demo runs that filter over a five-minute leg and shows you what the bearings can and cannot tell it: the error ellipse collapses when the geometry lets it, and stays a long sliver along the line of sight when it does not.

## The model

The own-ship flies East at 250 m/s from the origin for 300 s, either on a **straight leg** or on a **dogleg** that turns by a chosen angle at a chosen time. The emitter is either **stationary** or **moving** at a constant 15 m/s, and starts either **off the route** (40 km North, 55 km East) or **dead ahead** on the own-ship's track (120 km East). Intel's planning prior puts it 8 km short of its true position along the first line of sight and 4 km to the right, with $\sigma = 8$ km on each axis (and $0 \pm 0.05$ km/s on each velocity component for the moving emitter).

Each bearing is the true direction plus Gaussian noise of standard deviation $\sigma_\theta$:

$$
z = \text{atan2}(E - E_o,\; N - N_o) + v, \qquad v \sim \mathcal N(0, \sigma_\theta^2).
$$

The filter predicts with $F = I$ for a stationary emitter and a constant-velocity $F$ for a moving one ($Q = 0$ in both, because the emitter really does behave that way), then updates with L33's bearing row evaluated at its own estimate:

$$
H = \frac{[\,-\Delta E \quad \Delta N\,]}{R^2}, \quad
S = HP^-H^\top + \sigma_\theta^2, \quad
K = P^-H^\top/S, \quad
\hat{\mathbf x} = \hat{\mathbf x}^- + K\nu, \quad
P = (I - KH)P^- .
$$

The whole run is computed first, from the scenario and the random seed. **Run**, **Step** and the scrubber only reveal it — so the six tiles, which report the end of the run, never change while it animates. A notional 40 km threat ring around the emitter shows what each maneuver costs: the red stretch of the track, and the red band on the chart, are the time spent inside it.

## Interactive demo

<a class="demo-fullscreen" href="../_static/demos/BearingsOnlyTracker.html" target="_blank" rel="noopener">Open in full screen</a>

<div class="demo-wrap">
<iframe src="../_static/demos/BearingsOnlyTracker.html"
        title="Interactive bearings-only EKF tracker demo"
        width="100%"
        loading="lazy">
</iframe>
</div>

## Walkthrough

The four preset chips tell the lesson's story in order. The numbers below are for the defaults — $\sigma_\theta = 1^\circ$, one bearing every 5 s (60 bearings), seed 1; **Re-roll noise** changes them a little, never the story.

1. **Stationary.** Press **Run**. The intel prior starts as an 8 km circle ($\sqrt{\operatorname{tr} P} = 11.3$ km). The first bearings squash it across the line of sight; then, as the own-ship flies past and the bearing swings, it shrinks along the line of sight too. By the end $\sqrt{\operatorname{tr} P} = 0.28$ km, and the estimate is 0.15 km from the truth. Turn on **Zoom ×5** to see an ellipse that small. No maneuver was needed: the leg itself was the baseline.
2. **Dead ahead.** Now the emitter sits on the own-ship's track, and the bearing never changes. The ellipse becomes a needle along the track — 7.89 × 0.14 km, its long axis 0° off the line of sight — and the estimate is still 7.6 km out in range, exactly where the prior left it. Every line of position is the same line.
3. **Moving.** Back off the route, but the emitter now moves at constant velocity while the own-ship flies straight and steady. The ellipse stays long along the line of sight (4.48 × 0.27 km) and the $\sqrt{\operatorname{tr} P}$ curve on the chart flattens instead of diving. Slide the update rate to **1 Hz**: five times the bearings bring the thin axis down to 0.13 km and leave the long one where it was. A scaled "ghost" of the emitter's track fits every bearing just as well, so better or faster bearings cannot find range.
4. **Moving + dogleg.** The own-ship turns 90° left at 90 s. Watch the chart: the $\sqrt{\operatorname{tr} P}$ curve leaves the straight-leg plateau right after the turn line and ends at 0.63 km. Now read the **Exposure** tile: 143 s inside the 40 km ring, starting at 157 s — the red band on the chart. That is the price of the fix.
5. **Turn the other way.** Drag **Turn angle** to 90° right. The exposure drops to zero and $\sqrt{\operatorname{tr} P}$ climbs to 12.8 km: turning away from the emitter opens the range and barely changes the bearing rate, so it buys almost nothing. Try the angles in between, and a later or earlier turn, and watch the two tiles trade against each other.
6. **The turn you did not need.** Pick **Stationary** and switch to **Dogleg**. The ellipse ends at 0.26 km — no better than the straight leg's 0.28 — and the aircraft spent 143 s in the ring to get it. For an emitter that was already observable, the maneuver was pure cost.

Two failure modes are worth seeing once. With the moving emitter on a straight leg, drive $\sigma_\theta$ down to 0.2° and the rate to 1 Hz: the **Error** tile turns red because the actual error (6.4 km) has run well past three times the filter's claimed $\sqrt{\operatorname{tr} P}$ (2.1 km). Linearizing at a wrong estimate has let the EKF invent range information the geometry never supplied — on the chart, the error curve rides above the $\sqrt{\operatorname{tr} P}$ curve. And a turn that points the own-ship straight down the line of sight (for the stationary emitter, 45° left at 60 s) flies over the emitter; when the estimate comes within 1 km of the own-ship the filter skips those updates, and the map says so.

## Key observations

- **A bearing measures direction.** Each update shrinks the ellipse across the line of sight only; range has to come from a change in geometry.
- **A stationary emitter off the route is observable from a straight leg** — the leg is the baseline. Dead ahead, it is not.
- **A moving emitter seen from a straight, steady own-ship has unobservable range**, however accurate or frequent the bearings. A maneuver breaks the ambiguity.
- **Seeing costs exposure.** The turns that collapse the ellipse fastest swing the line of sight and close the range — and those are the turns that spend time inside the threat's ring.
- **Check the filter against the truth.** A small $\sqrt{\operatorname{tr} P}$ on a geometry that should not have produced one is the EKF fooling itself.

## Source

<a class="matlab-link" href="../_static/downloads/ECE%20495%20EW%20%E2%80%93%20Code.zip#code/L35_BearingsOnlyEKF.m" download title="Downloads the full course code bundle (.zip). This lesson&#39;s file: code/L35_BearingsOnlyEKF.m"><svg viewBox="0 0 22 22" width="14" height="14" aria-hidden="true" style="vertical-align:-2px;margin-right:6px;"><rect width="22" height="22" rx="3" fill="#e87722"/><text x="11" y="15.5" text-anchor="middle" font-family="'Inter',sans-serif" font-size="9" font-weight="800" fill="#fff" letter-spacing="-0.04em">MAT</text></svg><span class="ml-text">MATLAB bundle · L35_BearingsOnlyEKF.m</span><span class="ml-arrow">↓</span></a>

The companion script runs the same scenario and the same filter, line for line, in three cases — a stationary emitter from a straight leg, the moving emitter from a straight leg, and the moving emitter with the 90° dogleg at 90 s — and plots $\sqrt{\operatorname{tr} P}$ against time for all three. It draws its own noise with `rng(35)`, so its numbers differ slightly from the demo's seed 1 (0.28, 4.26 and 0.62 km), and it ends by printing the exposure each leg costs inside the 40 km ring: 0 s straight, 143 s with the dogleg.
