Estimated Accuracy β
When MeshMonitor draws a node at an estimated position, it also draws a dashed accuracy circle around it. This page explains exactly how that circle β the node's uncertaintyKm β is computed, so you can read it with confidence and know when to trust an estimate.
In one sentence
The estimate is an SNR- and time-weighted centroid of every anchor that heard the node; the circle radius is the weighted spread of those anchors, shrunk by how many effective observations you really have. One anchor β a big "somewhere within radio range" circle; many converging anchors β a small, confident circle.
For what position estimation is, how to turn it on, and the settings that control it, see Position Estimation. This page is the deep-dive on the accuracy math.
The pipeline at a glance β
traceroutes β ββ weight each observation ββ
β observations β w = time-decay Γ SNR β weighted
neighbor ββββΌβββΆ (anchor pos, ββββββββββββββββββββββββββββΆβββΆ centroid ββΆ (lat, lon)
info β SNR, time) β β β
ββββββββββββββββββββββββββββββ βΌ
uncertainty radius
(spread Γ· βeffective-N,
blended w/ radio range)Every estimate is a pure function of a node's observations. An observation is "an anchor (a node whose position we already know) heard this node, at this SNR, at this time." Two data sources produce them:
| Source | Observation it creates | SNR used |
|---|---|---|
| Traceroute | each A β X β B hop anchors the middle node X toward its positioned path-neighbors | per-hop SNR (raw Γ· 4 β dB) |
| NeighborInfo | a direct-RF pair anchors the unpositioned side to the positioned side | link SNR (already dB) |
Step 1 β Weight each observation β
Every observation gets a single scalar weight, the product of two independent factors:
weight = time_decay Γ snr_weightSNR weight β stronger signal pulls harder β
SNR is converted to linear signal power:
snr_weight = 10 ^ (SNR_dB / 10)This is the physically correct mapping: a higher (less-negative) SNR means the anchor is more likely to be near the node, so it should pull the estimate toward itself harder. Weak signals barely move the estimate.
SNR (dB) snr_weight = 10^(SNR/10) relative pull
βββββββ βββββββββββββββββββββββ βββββββββββββββββββββββββ
+15 31.6 ββββββββββββββββββββββββ very strong
+10 10.0 ββββββββββββ strong
+5 3.16 ββββ good
0 1.0 β reference
β6 0.251 β weak
β12 0.063 Β· very weak (poor link)
β20 0.01 Β· near noise floorA β12 dB anchor barely counts
At β12 dB an anchor carries ~0.06 of the weight of a 0 dB anchor and ~1/160th of a +10 dB anchor. That asymmetry is the heart of the accuracy math below β and the reason a single weak anchor must never produce a confident estimate.
Time-decay weight β newer observations count more β
Observations lose half their weight every 24 hours:
time_decay = 0.5 ^ (age_hours / 24) (exponential, half-life = 24h) age time_decay
ββββ ββββββββββ
now 1.00
12 h 0.71
24 h 0.50
48 h 0.25
72 h 0.125
7 days 0.008So a fresh β6 dB report can outweigh a three-day-old +0 dB report. The lookback window sets how far back observations are even considered.
Step 2 β Weighted centroid (the position) β
The estimated location is the weight-weighted average of all anchor positions:
Ξ£ (wα΅’ Β· latα΅’) Ξ£ (wα΅’ Β· lonα΅’)
lat = βββββββββββββ lon = βββββββββββββ
Ξ£ wα΅’ Ξ£ wα΅’For the simplest case β a single traceroute segment with two anchors β this reduces exactly to the classic SNR-weighted midpoint. With many anchors heard from many directions, the centroid converges on the node's true location:
Anchor B (SNR +8, wβ6)
β
\ β
= weighted centroid (the estimate)
\ β pulls toward the strong/near anchors,
β
\ barely toward the weak/far ones
ββββββββββ Anchor A (SNR 0, w=1)
β
β Anchor C (SNR β10, wβ0.1) β far + weak β almost no pullStep 3 β The accuracy radius (uncertaintyKm) β
This is the circle you see. It is built from three ingredients.
3a. Weighted RMS spread β
How far the anchors sit from the centroid, weighted:
βββββββββββββββββββββββββββ
rms_km = β Ξ£ (wα΅’ Β· distα΅’Β²) / Ξ£ wα΅’
βββββββββββββββββββββββββββ (square root of the weighted mean
squared distance to the centroid)3b. Effective sample size (Kish) β
Counting anchors naively is misleading when one anchor dominates the weights, so we use the Kish effective sample size:
(Ξ£ wα΅’)Β²
n_eff = ββββββββ
Ξ£ wα΅’Β²n_eff answers "how many balanced observations is this really worth?"
- Two equally-weighted anchors β
n_eff = 2. - One anchor at weight 10 plus one at weight 0.06 β
n_eff β 1.01β i.e. effectively a single observation, even though two anchors exist.
3c. Confidence blend β
A raw "spread Γ· βn_eff" statistic looks dangerously confident when n_eff is barely above 1 (the dominated case above). So the final radius blends a conservative radio-range default toward the statistical estimate, using a confidence factor derived from n_eff:
statistical_km = rms_km / βn_eff (clamped to β₯ 0.05 km)
confidence = clamp(n_eff β 1, 0, 1) (0 at n_eff=1 β¦ 1 at n_effβ₯2)
uncertainty_km = 5 km Γ (1 β confidence) + statistical_km Γ confidence n_eff confidence what the circle reflects
βββββ ββββββββββ ββββββββββββββββββββββββββββββββββββββββββββ
1.0 0.00 pure 5 km radio-range default (single / dominated)
1.25 0.25 mostly default, a little statistics
1.5 0.50 half-and-half
1.75 0.75 mostly statistics
β₯2.0 1.00 pure statistical radius (balanced multi-anchor)- A lone anchor can only say "within radio range," so it gets the full ~5 km default.
- A balanced multi-anchor solve (
n_eff β₯ 2) trusts the geometry fully and can report a sub-kilometre radius. - Anything in between is blended, so a near-single solve can never sneak out a falsely tight circle.
Why the blend exists (issue #3616)
Before this refinement, the radius was just rms_km / βn_eff. With one strong anchor and one weak/far anchor, the centroid collapses onto the strong anchor, the weak anchor contributes almost nothing to rms_km, and n_eff lands at ~1.01 β just above the single-anchor cut-off. The result was a tiny, falsely confident circle for what was effectively one observation (a node heard once at β12 dB appearing pinned, with confidence, inside the one house that heard it). Blending on n_eff closes that gap without changing the correct weight model or any genuinely balanced estimate.
Worked examples β
All three assume fresh observations (time_decay β 1) so we can focus on SNR and geometry.
Example A β single anchor (one house heard it once) β
Observations: 1 Β· anchor at the listener, SNR β12 dB
wSum = 0.063 wΒ²Sum = 0.063Β² n_eff = 0.063Β² / 0.063Β² = 1.00
centroid = the anchor itself β rms_km = 0
confidence = clamp(1 β 1) = 0
uncertainty = 5 km Γ 1 + 0.05 km Γ 0 = 5 kmβ‘ Estimate sits at the anchor, circle = 5 km. Correctly screams "I only know it's within radio range of this one node."
Example B β one strong + one weak/far anchor (the #3616 case) β
Anchor 1: SNR +10 (w = 10) at the reporter's house
Anchor 2: SNR β12 (w = 0.063) ~4 km away
wSum = 10.063 wΒ²Sum = 100.004 n_eff = 10.063Β² / 100.004 β 1.013
centroid β 0.025 km from anchor 1 (the weak anchor barely tugs it)
rms_km β 0.32 km statistical_km β 0.31 km
confidence = clamp(1.013 β 1) = 0.013
uncertainty = 5 km Γ 0.987 + 0.31 km Γ 0.013 β 4.94 kmβ‘ Estimate near the strong anchor, but circle β 4.94 km β honestly unreliable. (Before the blend fix this reported β 0.31 km β confident and wrong.)
Example C β balanced four-anchor solve β
Four anchors, comparable SNR (w β 1 each), ~1 km from the node on four sides
wSum = 4 wΒ²Sum = 4 n_eff = 16 / 4 = 4 β confidence = 1
rms_km β 1 km statistical_km = 1 / β4 = 0.5 km
uncertainty = 5 km Γ 0 + 0.5 km Γ 1 = 0.5 kmβ‘ Tight 0.5 km circle β the geometry is trusted because four independent directions genuinely constrain the node.
Reading the circle on the map β
| Circle | Meaning | Typical cause |
|---|---|---|
| Large (~5 km) | low confidence β "within radio range of one node" | single anchor, or one dominant anchor |
| Medium (1β3 km) | partial triangulation | a few anchors, uneven SNR |
| Small (< 1 km) | well-triangulated, trustworthy | several balanced anchors from different directions |
Show or hide circles with the Show Accuracy toggle in the Map Features panel; the marker itself follows Show Estimated Positions. A circle is only ever drawn together with its marker.
Hiding the loose ones automatically β
Because the biggest circles are the honest ~5 km single-anchor estimates, you can keep the map clean with Global Settings β Position Estimation β Maximum acceptable accuracy: any estimate whose uncertaintyKm exceeds your cutoff is discarded instead of stored. A value of 2β3 km keeps well-triangulated nodes and drops the "somewhere out there" guesses. 0 means no limit. See Choosing a maximum accuracy.
Properties worth knowing β
- Deterministic. Same observations in the lookback window β same estimate and same circle, every run. There is no randomness.
- Meshtastic-only, global. Observations are pooled across all Meshtastic sources (including the embedded MQTT broker and bridges) into one estimate per physical node. MeshCore sources are excluded.
- Self-correcting. As soon as a node reports a real GPS position it leaves the estimate set and instead becomes an anchor for everyone else.
- Floored, not zeroed. The radius can never drop below 0.05 km, so a multi-anchor estimate never claims absurd precision.
Reference β the formulas β
weight wα΅’ = 0.5^(age_h / 24) Β· 10^(SNR_dB / 10)
position lat,lon = Ξ£(wα΅’Β·posα΅’) / Ξ£wα΅’ (weighted centroid)
spread rms = β( Ξ£(wα΅’Β·distα΅’Β²) / Ξ£wα΅’ )
effective N n_eff = (Ξ£wα΅’)Β² / Ξ£wα΅’Β² (Kish)
statistical stat = max(0.05, rms / βn_eff)
confidence c = clamp(n_eff β 1, 0, 1)
accuracy circle unc_km = max(0.05, 5Β·(1βc) + statΒ·c)Implementation: observationWeight() and solveNodePosition() in src/server/services/positionEstimationService.ts.
Related β
- Position Estimation β enabling it, settings, the map toggles
- Interactive Maps
- Map Analysis
- Link Quality & Smart Hops