GPS Positioning — Geometry and DOP#

This demo solves the GPS positioning problem from raw pseudorange measurements on a 2D toy constellation. It is a black-box receiver model — given \(N\) pseudoranges from satellites at known positions, run a Gauss-Newton nonlinear least-squares fit and report the receiver position estimate plus a single uncertainty number (HDOP). No prior, no IMU, no Kalman filter. This is the architecture of every standalone GPS receiver and the framing that Block 7 will later refine into a properly Kalman-fused (tightly coupled) solution.

The headline lesson is that geometry — not noise — controls accuracy. Two scenarios with the same number of satellites and the same per-pseudorange noise can produce position accuracy that differs by an order of magnitude or more, purely because the satellites are arranged differently in the sky. The demo lets you drag satellites around the orbit and watch the position uncertainty inflate or collapse in real time.

The math#

Each satellite contributes one pseudorange equation:

\[ \tilde{\rho}_i = \underbrace{\lVert \mathbf{r}_u - \mathbf{r}^{(i)} \rVert}_{\text{true range}} + b + \epsilon_i, \]

where \(\mathbf{r}_u = (p_x, p_y)\) is the receiver position, \(\mathbf{r}^{(i)}\) is the \(i\)-th satellite position, \(b\) is the unknown receiver clock bias (in meters of equivalent range), and \(\epsilon_i \sim \mathcal{N}(0, \sigma_\rho^2)\) is independent measurement noise. With three or more satellites in 2D (or four or more in 3D), the system is over-determined and we can solve nonlinear least squares for the unknowns \(\boldsymbol{\beta} = [p_x, p_y, b]^{\top}\):

\[ \hat{\boldsymbol{\beta}} = \arg\min_{\boldsymbol{\beta}} \sum_i \left( \tilde{\rho}_i - h_i(\boldsymbol{\beta}) \right)^2, \qquad h_i(\boldsymbol{\beta}) = \lVert \mathbf{r}_u - \mathbf{r}^{(i)} \rVert + b. \]

The function \(h_i\) is nonlinear in position because of the Euclidean norm; this is what makes GPS a nonlinear estimation problem in the first place. The demo solves it iteratively with Gauss-Newton: linearize \(h_i\) around the current estimate, take a step, repeat until convergence.

After convergence, the position covariance follows from the linearized Jacobian \(\mathbf{H}\):

\[ \mathbf{P}_{\text{pos}} = \sigma_\rho^2 (\mathbf{H}^{\top}\mathbf{H})^{-1}\bigg|_{\text{position block}}, \qquad \mathrm{HDOP} = \frac{\sqrt{\mathrm{tr}(\mathbf{P}_{\text{pos}})}}{\sigma_\rho} = \sqrt{[(\mathbf{H}^{\top}\mathbf{H})^{-1}]_{11} + [(\mathbf{H}^{\top}\mathbf{H})^{-1}]_{22}}. \]

HDOP depends only on the satellite directions, not on the noise. Receiver hardware controls \(\sigma_\rho\). The constellation controls HDOP. The product \(\sigma_{\text{pos}} = \mathrm{HDOP} \times \sigma_\rho\) is the operationally useful decomposition.

Interactive demo#

Open in full screen

The demo has two coupled views. The constellation overview (left) shows the satellites on their orbit with lines of sight to the receiver; satellites are draggable around the orbit to change geometry. The receiver-area zoom (right) shows the same scenario at meter scale, with each satellite’s pseudorange band drawn perpendicular to its line of sight. Where the bands intersect is where the receiver could be; the red dashed 95% covariance ellipse summarizes the analytical uncertainty.

A note on the bands: each one represents “constant distance from a single satellite” — at the receiver-area scale, that’s a tangent line to the (very large) range circle, oriented perpendicular to the satellite’s line of sight. The band is not a ray pointing at the satellite. The same way an isobar runs perpendicular to the pressure gradient, a pseudorange band runs perpendicular to the radial direction.

Walkthrough#

The demo opens at the Even spread preset: 4 satellites at 90° intervals, \(\sigma_\rho = 3\) m, true clock bias \(b = 17\) m. Try the following:

  1. Read the headline metrics. HDOP ≈ 0.7–1.0, position error a few meters, clock-bias estimate within a meter or two of truth. The bands cross at sharp angles in the zoom view; the 95% ellipse is small and roughly circular. This is what good geometry looks like.

  2. Click “Clustered (bad).” All four satellites snap into a 30° arc at the upper-right of the orbit. HDOP jumps to roughly 30+ (orange or red on the stat card), the zoom panel auto-rescales by 10–50×, and the ellipse balloons into a long elongated shape along the line-of-sight direction toward the cluster. Same noise, same number of satellites, ~30× worse precision — purely from geometry.

  3. Notice the ellipse orientation. The major axis of the bad-geometry ellipse points along the cluster’s LoS, not perpendicular to it. This is the clock-bias coupling: when satellites are clustered, shifting the estimated clock bias \(\hat{b}\) shifts every pseudorange band by nearly the same amount in nearly the same direction (the cluster LoS), creating a near-rank-1 ambiguity in the joint \((p_x, p_y, b)\) solve. The position uncertainty inherits that ambiguity.

  4. Drag a single satellite away from the cluster. Pick the leftmost satellite and drag it down to the lower-left (around 220° on the orbit). HDOP drops dramatically — sometimes from 30+ to 3 or less. One satellite at a different angle is worth more than three more at the cluster’s angle. This is why GPS receivers prefer constellations spread across the sky and why elevation masks (cutting off low-elevation satellites) often help by removing the bands most likely to alias the clock-bias direction.

  5. Try “Half sky.” Satellites span a semicircle. Geometry is markedly better than clustered but slightly worse than full spread, and the ellipse skews toward the missing half. This is a realistic scenario in canyons, near tall buildings, or with one of multiple constellations occluded.

  6. Slide the true clock bias anywhere in \([-50, +50]\) m. The estimated \(\hat{b}\) tracks within a few meters; HDOP and σ_pos do not change. Clock bias is a fourth (here third, because we’re in 2D) unknown that the solver pulls out cleanly without degrading the geometry — that’s the whole point of inverting for it as part of the LS fit rather than trying to know it ahead of time.

  7. Slide \(\sigma_\rho\) up or down while leaving the geometry fixed. The ellipse scales linearly with \(\sigma_\rho\) at fixed HDOP, exactly per \(\sigma_{\text{pos}} = \mathrm{HDOP} \times \sigma_\rho\). Receiver-side improvements (better antennas, multi-frequency, code-phase tracking) attack \(\sigma_\rho\). Constellation-side improvements (multi-GNSS, optimized satellite scheduling) attack HDOP. They multiply.

  8. Reseed. New noise realization, same geometry. The estimate jumps around (within the ellipse) but the ellipse itself doesn’t change. The ellipse is a property of the geometry; the dot inside it is a single noisy realization.

Key observations#

  • GPS positioning is a nonlinear least-squares problem. Pseudorange is nonlinear in user position because of the Euclidean norm. The receiver runs Gauss-Newton (or an equivalent iterative method), not a closed-form formula.

  • The clock bias is just another unknown. Treating \(b\) as a state variable rather than trying to know it ahead of time is what makes GPS practical. The cost is requiring one extra satellite (3 in 2D, 4 in 3D); the benefit is robustness to receiver clocks that are millions of times less stable than satellite clocks.

  • Each pseudorange constrains the receiver to a circle, not a ray. The band drawn at the receiver scale is a tangent line to that circle, perpendicular to the line-of-sight. Bands tell you about radial distance to a satellite, not direction to it.

  • Geometry is the dominant lever for accuracy. A 30° cluster will produce a position error 10–30× worse than evenly spread satellites with the same hardware and the same pseudorange noise. The numbers behind the rule of thumb HDOP < 2 (good) versus HDOP > 5 (degraded) come directly from this geometry.

  • The clock-bias-induced ambiguity dominates the ellipse shape under bad geometry. With clustered satellites, the dominant ambiguity is along the cluster’s line-of-sight (because that’s the direction along which a \(\Delta b\) shift maps to a position shift in (px, py)). Spread the satellites and the rank-1 ambiguity disappears.

From “black-box” to fused#

This demo treats the receiver as a self-contained box: pseudoranges in, position estimate out, no other inputs. That is the loose-coupling architecture, where a navigation filter consumes the receiver’s position estimate as if it were any other position sensor (this is exactly what the 4D Kalman filter from Block 5 did). It is simple and works fine when the receiver always has good geometry and clean signals.

When that breaks down — bad geometry, multipath, occlusion, jamming — a tightly-coupled architecture wins. Tightly-coupled means feeding the raw pseudoranges into a Kalman filter alongside the IMU mechanization, letting the filter weigh each pseudorange by its individual variance and ride through brief outages. Block 7 builds exactly that: an EKF whose measurement model linearizes the same nonlinear \(h_i\) used here, but at every step rather than only at convergence of a single batch.

Source#

MATLAB · code/GPSPositioningDemo.m