From Trilateration to Weighted Centroid: Choosing Robustness Over Precision
Part 2 of 2 — A simpler estimator that gives up mathematical accuracy in exchange for bounded behavior under noise, and why that turned out…

From Trilateration to Weighted Centroid: Choosing Robustness Over Precision
Part 2 of 2 — A simpler estimator that gives up mathematical accuracy in exchange for bounded behavior under noise, and why that turned out to be the right trade.
Part 1 covered why a textbook trilateration implementation failed in production: FTM distance measurements have a noise floor of roughly ±30–50 cm in real indoor environments, and the closed-form three-circle intersection is ill-conditioned in exactly that regime. The math was correct; the inputs the math assumed didn’t exist.
This post is about the replacement. It’s a worse algorithm by every textbook criterion. It also, in practice, worked far better.
The replacement: a weighted centroid
The simplest possible idea: if a beacon claims to be close to a particular anchor, that anchor’s position is a better guess for the beacon’s location than an anchor the beacon claims to be far from. Weight each anchor by the inverse of its measured distance, and take the average.
private func calculateTrilateration(
measurements: [FTMMeasurementDTO],
anchors: [Anchor]
) -> PreciseLocationDTO {
var totalX: Double = 0
var totalY: Double = 0
var totalWeight: Double = 0
for measurement in measurements.prefix(3) {
if let anchor = anchors.first(where: { $0.macAddress == measurement.anchorMac }) {
// Closer anchors get more weight. The +0.1 prevents a zero-distance
// reading from blowing the average to infinity.
let weight = 1.0 / (measurement.distanceMeters + 0.1)
totalX += anchor.positionX * weight
totalY += anchor.positionY * weight
totalWeight += weight
}
}
return PreciseLocationDTO(
x: totalX / totalWeight,
y: totalY / totalWeight,
z: nil
)
}
This is not trilateration. It’s a weighted centroid — a one-line formula with no notion of circle intersection, no linear system, no determinant. It treats each anchor as a “hint” about the beacon’s location and produces a position by mixing those hints in proportion to the inverse of the measured distance.
Mathematically, it has a property that turns out to matter more than any of its weaknesses: the output is always inside the convex hull of the anchors. The estimate is bounded by the geometry of the network itself.
What the centroid sacrifices, and what it preserves
The centroid gives up the kind of precision trilateration can deliver under clean inputs. A beacon truly standing at an exact anchor location will not be reported at that anchor — it will be biased toward the average of the other anchors it can “see.” A beacon at the geometric center of three equidistant anchors will be reported correctly, but a beacon in the corner of an asymmetric anchor arrangement will be reported shifted toward the middle by some bounded amount.
In return, three failure modes from Part 1 simply cannot occur:
- Coordinates outside the building. The convex-hull guarantee makes this geometrically impossible. The estimate is locked to a region defined by anchor positions, which the network operator controls.
- NaN or infinite outputs. The only division is by
totalWeight, which is a sum of strictly positive numbers as long as at least one measurement matched an anchor. The+ 0.1in the weight expression guarantees the denominator can never collapse. - Catastrophic amplification of input noise. Noise on individual distance measurements perturbs the weights but cannot dominate them. Each anchor’s contribution is bounded by its position vector times a finite weight; the sum is bounded by the maximum anchor coordinate.
The cost is precision under ideal inputs. The benefit is bounded worst-case behavior under arbitrary inputs. For the use case of indoor patient tracking — where the underlying question is usually “is the patient in their assigned ward” rather than “where is the patient to the nearest centimeter” — that trade favored the centroid heavily.
Precision and robustness, more generally
The choice between trilateration and weighted centroid is a specific instance of a broader trade-off that applies to any system fusing noisy sensor input into an estimate.
Precision is how close the estimator gets to the true value when the inputs are accurate. Closed-form solvers, maximum-likelihood estimators, and exact geometric methods are usually high-precision: they extract every available bit of information from clean inputs.
Robustness is how badly the estimator misbehaves when the inputs are inaccurate. It includes properties like bias under noise (does the estimator stay close to the true value on average?), variance (how much does it jump frame-to-frame?), and worst-case bounds (can it return physically impossible values?).
In a closed-form world, these properties are usually in tension. The same mathematical machinery that extracts maximum information from clean inputs amplifies whatever error is present in dirty inputs. You can chase precision by adding model complexity (nonlinear least squares, particle filters, Kalman variants), but each layer adds tuning surface and failure modes.
The cheaper move is to ask which property the application actually rewards.
For surveying instruments operating in line-of-sight outdoor conditions: precision wins. The noise floor is low enough that closed-form solvers are stable, and the application demands centimeter accuracy.
For consumer indoor positioning on Wi-Fi hardware: robustness wins. The noise floor is high, the application tolerates meter-scale error, and the cost of an occasional impossible coordinate is much higher than the cost of being consistently slightly biased toward the middle of the room.
The general principle: match the estimator’s failure modes to the application’s tolerance for those failures, not to the textbook’s notion of “the right answer.”
Implementation notes worth keeping in mind
A few details from the production implementation that aren’t obvious from the snippet above:
The + 0.1 in the weight expression. This is a numerical guard. FTM occasionally reports a distance very near zero — sometimes from genuine proximity, sometimes from a bad calibration frame. Without the offset, a single near-zero distance reading would dominate the weighted average and snap the estimate onto that anchor's exact position, defeating the smoothing effect. The offset is chosen to be small relative to typical room-scale distances (meters) but large enough to prevent any single measurement from receiving more than ~10× the weight of a measurement at a typical distance.
**measurements.prefix(3).** The implementation caps at three measurements even when more are available. There's no deep reason for this beyond consistency with the original trilateration interface; using all available measurements would slightly improve the estimate. This is a small piece of latent improvement waiting in the codebase.
The function’s return type is non-optional. Unlike the trilateration version, which had to return PreciseLocationDTO? to signal degenerate-geometry failures, the centroid implementation cannot fail in any geometric sense. The only way it returns garbage is if zero measurements match any anchor — and that case is gated upstream by the measurements.count >= 3check at the call site.
var preciseLocation: PreciseLocationDTO?
if measurements.count >= 3 {
preciseLocation = calculateTrilateration(measurements: measurements, anchors: anchors)
} else {
// Fall back to nearest anchor for fewer-than-3-measurement frames.
preciseLocation = PreciseLocationDTO(
x: nearestAnchor.positionX,
y: nearestAnchor.positionY,
z: nearestAnchor.positionZ
)
}
The fallback for fewer than three measurements snaps to the nearest anchor by RSSI/distance ranking. That’s its own approximation, intentionally coarser, and it accepts that frames with only one or two anchor responses can’t produce a useful 2D position regardless of which formula is used.
A note on the function name
The function is called calculateTrilateration. It does not compute trilateration. It computes a weighted centroid.
The naming is technical debt, and it was a deliberate choice. The function name was already part of the internal API — referenced from LocationService, decoded by the iOS client through its response DTO, mentioned in the OpenAPI spec consumed by the embedded team. Renaming would have rippled across three repos and several developers' assumptions. The cost-benefit didn't favor it during the integration crunch.
What it does mean is that the contract is misleading by name, and any new engineer reading the codebase will form an incorrect mental model unless something stops them. The mitigation is a comment at the call site explaining the actual implementation and pointing at the commit history where the original geometric attempt lives:
// When we have 3+ measurements, we compute a weighted centroid
// (not true trilateration — see commit history for the geometric
// attempt and why we threw it out). Robust to noisy FTM readings
// at the cost of bounding the estimate inside the anchor convex hull.
This is not a substitute for renaming. It’s an acknowledgement that the rename is owed. If I were starting fresh, I’d name the function estimatePositionFromMeasurements or computeWeightedCentroid and let trilateration mean trilateration.
The general lesson here: when a function’s name and implementation diverge, the cost of the divergence compounds.Every reader who trusts the name builds a slightly wrong model of the system. Comments help, but only at the spot where the comment lives — and refactors driven by the misleading name will silently propagate the misunderstanding outward. If the cost-benefit ever shifts toward renaming, the rename should happen.
What I’d recommend to anyone designing a similar system
A few practices that would have saved time on this project, and that generalize beyond indoor positioning:
Test against real sensor data as early as possible. Synthetic test fixtures will validate algorithms that have no chance of working in production. A reasonable first integration test for a sensor-fusion algorithm involves the actual sensor — even just one of them, in a controlled environment — well before the algorithm is considered “done.”
Profile the noise distribution of the inputs before choosing the estimator. What does the distance reading distribution look like when the beacon is stationary at a known location? What’s the standard deviation? Is the distribution Gaussian or heavy-tailed? Are there systematic biases as a function of distance or RSSI? Choices about which estimator to use should be informed by the answers, not by which formula looks most impressive on a whiteboard.
Prefer estimators with bounded worst-case behavior in safety-relevant applications. “On average it’s accurate to half a meter” is a different guarantee from “it will never return a coordinate outside the building.” When the downstream consumer is a UI that draws a dot on a floor map, the second guarantee is often more useful than the first.
Write down trade-offs at the point of decision. A comment at the call site, a paragraph in the PR description, an entry in an ADR (architecture decision record) — any of these is better than relying on collective memory. The version of you that has to defend this design choice in six months will not remember why you made it. The version that has to change it in eighteen months really won’t.
The system that shipped reports beacon positions roughly within a meter of their true location, with bounded behavior at the edges of the anchor network, and no observed cases of reporting a beacon outside the building. The math is, by textbook standards, primitive. The behavior is, by user-experience standards, dramatically better than what the textbook math produced.
Indoor positioning isn’t really a geometry problem. It’s a statistics problem dressed up in geometric clothes — and the estimator that wins is usually not the one with the most elegant derivation, but the one whose failure modes are the least bad when the inputs aren’t what the derivation assumed.
메타데이터
- post_id
- 1d7b4b4aaecb
- slug
- from-trilateration-to-weighted-centroid-choosing-robustness-over-precision-1d7b4b4aaecb
- url
- https://medium.com/@euijjang97/from-trilateration-to-weighted-centroid-choosing-robustness-over-precision-1d7b4b4aaecb
- canonical_url
- https://medium.com/@euijjang97/from-trilateration-to-weighted-centroid-choosing-robustness-over-precision-1d7b4b4aaecb
- author_url
- https://medium.com/@euijjang97
- status
- ok
- fetched_at
- 2026-07-11 01:06:15