Global Navigation Satellite Systems (GPS, GLONASS, Galileo, BeiDou) all run the same geometric trick. Your phone's receiver measures the arrival time of radio signals from at least four satellites, converts each delay into a pseudorange, an estimated distance to that satellite, and solves a system of equations for three-dimensional position plus a clock-bias correction. Under open sky the horizontal result is often 3–8 m at the 95th percentile. That will find you a restaurant, but it will not find your car on level B3 of a concrete parking garage, and no map tile can patch the gap. The failure is physics: signal attenuation, multipath reflection, and geometry collapse.
A ruler made of time
Every satellite broadcasts its orbital position and a timestamp disciplined by atomic clocks on board. The receiver notes when each signal arrives and forms a pseudorange ρ = c·(t_rx − t_tx). Light covers about 0.3 m per nanosecond. A timing error of 1 ns therefore costs 30 cm of range, and the quartz oscillator in your phone is millions of times less stable than the cesium and rubidium clocks upstairs. The solver copes by treating the receiver clock offset as a fourth unknown next to x, y, and z. Four unknowns need four equations. That is where the four-satellite minimum comes from, and extra satellites over-determine the system so the receiver can average noise down.
The whole solve rests on one assumption: the signal traveled in a straight line at a known speed. Standard models correct for the ionosphere and the troposphere, which slow and bend the wave by predictable amounts. Nothing in the model accounts for a signal that bounced off a concrete wall on the way in, or for one that arrived 100 times weaker than expected. Underground, both happen at once.
Concrete eats microwaves
GPS transmits on the L1 band around 1575 MHz, microwave radio that behaves roughly like light at architectural scales. It casts shadows, and a parking structure is one enormous shadow. Reinforced concrete attenuates the signal by 20–30 dB or more per pass; 20 dB is a factor of 100 in power. The signal reaches the ground at roughly −130 dBm to begin with, already below the thermal noise floor and recoverable only through correlation gain. One or two slabs push it past anything a correlator can dig out. Two levels underground, the receiver may see zero usable satellites. Or it sees something worse: reflections leaking through the partial sky view at the ramp entrance, delayed and distorted.
A receiver holding fewer than four healthy satellite signals cannot solve for position reliably. Some phones then fall back to stale dead-reckoning estimates or Wi-Fi positioning, which drifts or jumps without warning. The blue dot keeps moving regardless. The phone is guessing from degraded inputs and refreshing that guess every second.
Multipath, the error that looks like motion
Some signal does penetrate near ramp mouths, on open-air upper decks, and in garages with skylights. There, multipath dominates the error budget. The direct signal from a satellite arrives at your antenna along with a delayed copy reflected off a concrete wall, a metal roof, or another car. The receiver's correlation engine tracks the strongest peak, which may be a composite of the direct and reflected paths carrying an extra path length Δd. That extra length becomes a pseudorange error, and the pseudorange error becomes a position error on the order of Δd itself. A bounce across a parking aisle and back adds tens of meters of path without difficulty.
Stand still and watch the dot orbit 10–30 m around you as satellite geometry shifts and different reflections take over. We measured this properly once. Bench note: 3 phones (one iPhone, one Pixel, one mid-range Samsung), 20 minutes static at a B1 ramp mouth, GNSS fixes logged at 1 Hz. The reported horizontal accuracy never exceeded 15 m. The actual scatter of the fixes spanned more than 40 m. None of this is incompetent map software. The receiver honestly reports the best estimate it can extract from ambiguous measurements; the optimism lives in the accuracy figure attached to it.
The vertical axis is worse
Horizontal error collects the complaints. Vertical error is typically 1.5–3 times worse than horizontal even in good conditions, because the satellites are mostly above you. Seen from the receiver, the constellation is nearly flat, so the geometry constrains altitude weakly and the vertical dilution-of-precision term inflates. This is why GPS cannot reliably answer "which floor am I on?" even when latitude and longitude look reasonable at the garage entrance.
The floor question has a better instrument. A garage level is about 3.2 m of height, and air pressure falls by roughly 12 Pa per metre of ascent near sea level. The hydrostatic relation Δh = ΔP/(ρg) converts a pressure change straight into a height change, so one floor is a step of about 38 Pa against a phone barometer noise floor of 0.3–1 Pa RMS. That is a clean signal in exactly the place where GPS altitude offers noise. Wheria's barometer floor hints exist because of this asymmetry; the derivation and the failure cases live in the barometer parking math post.
Other ways the pin goes wrong
Underground is the extreme case; the same physics has cousins on the surface. Urban canyons between tall buildings run the same multipath physics as the garage, with glass facades reflecting signals into pseudorange biases of tens of meters while half the sky stays blocked. Cheap RF jammers, illegal but real, flood the band with noise; atuan once logged a 40 s dropout of every constellation at once under clear sky, the classic signature of a jammer driving past rather than anything atmospheric. Faulty electronics achieve the same result by accident with broadband interference. Each mode leaves a different fingerprint in the raw measurements, and every one of them produces the identical user experience: the pin is not where you are.
What we tried before giving up on the dot
We did not jump straight to hiding GPS indoors. First attempt: average fixes over 30 s at the garage entrance and pin the entry portal. That part works and Wheria still does it, since the entrance sits under open sky long enough for a decent fix. Second attempt: keep the fused location provider running underground and low-pass its output. Discarded after a week, because smoothing a measurement whose error is 10 times the quantity of interest yields a smooth wrong answer. Third attempt: Wi-Fi round-trip-time ranging, which is genuinely good hardware when present; in the garages we tested around District 1 and Bình Thạnh, access points supporting RTT were close to nonexistent. Discarded for coverage. What survived is the gate below.
Gating: when Wheria trusts the fix
Wheria treats a GNSS fix as one witness whose story needs corroboration. The reported accuracy must be tight, the satellite count healthy, the implied motion plausible for a person walking near a car, and the barometer must agree that you are still above ground. Any failed check hides the dot.
fix = gnss.latestFix()
trustworthy =
fix.satelliteCount at least 4
and fix.horizontalAccuracy under 20 m
and fix.jumpSinceLastFix under 10 m # walking pace, no teleports
and barometer.floorEstimate() at or above ground
if trustworthy:
map.showDot(fix, radius = fix.horizontalAccuracy)
else:
map.hideDot()
path.extend(stepOdometry.delta()) # IMU takes over
When the dot disappears, the walked path replaces it. Wheria reconstructs your route from step detection and heading fusion and draws it with honest uncertainty bands, so the screen shows where you actually went rather than where a corrupted satellite solution floats. The full indoor pipeline of IMU dead reckoning, barometer floor hints, and adaptive compass fusion is documented in the indoor navigation post. Step odometry details live in the IMU post.
The afternoon this became an app
Landmark 81, Ho Chi Minh City, level B3. I parked, took a photo of the pillar number, walked to the elevator, and thought nothing of it. On the way back, GPS showed my car floating in the Saigon River, 400 m east of reality. I walked two full laps of the floor before finding it, comparing the map dot against concrete reality and losing every time.
That evening the decision was simple: stop showing a lying blue dot underground and show the path actually walked. GPS still works fine when you park outdoors, and Wheria uses it there without fuss. We just stopped pretending it works where physics says it cannot.
Hệ định vị vệ tinh toàn cầu (GPS, GLONASS, Galileo, BeiDou) đều chơi cùng một trò hình học. Receiver trên điện thoại đo thời điểm tín hiệu radio từ ít nhất bốn vệ tinh tới nơi, đổi mỗi độ trễ thành pseudorange, tức khoảng cách ước lượng tới vệ tinh đó, rồi giải hệ phương trình ra vị trí ba chiều cộng hiệu chỉnh lệch đồng hồ. Trời quang, sai số ngang thường 3–8 m ở percentile 95. Đủ để tìm quán ăn. Tìm xe ở tầng B3 hầm bê tông thì không. Tile bản đồ đẹp mấy cũng không vá nổi, vì lỗi nằm ở vật lý: suy hao tín hiệu, phản xạ multipath và geometry sụp đổ.
Cây thước làm bằng thời gian
Mỗi vệ tinh phát vị trí quỹ đạo của nó kèm timestamp, giữ nhịp bằng đồng hồ nguyên tử trên khoang. Receiver ghi thời điểm tín hiệu tới rồi lập pseudorange ρ = c·(t_rx − t_tx). Ánh sáng đi khoảng 0,3 m mỗi nano giây. Sai 1 ns là lệch 30 cm khoảng cách. Đồng hồ thạch anh trong điện thoại kém ổn định hơn đồng hồ cesium với rubidium trên vệ tinh hàng triệu lần, nên bộ giải coi độ lệch đồng hồ receiver là ẩn số thứ tư bên cạnh x, y, z. Bốn ẩn cần bốn phương trình. Tối thiểu bốn vệ tinh là từ đó ra. Thấy nhiều vệ tinh hơn thì hệ dư định, trung bình bớt được nhiễu.
Toàn bộ phép giải đứng trên một giả định: tín hiệu bay thẳng với tốc độ đã biết. Mô hình chuẩn có hiệu chỉnh tầng điện ly cùng tầng đối lưu, hai thứ làm chậm và bẻ cong sóng theo lượng đoán trước được. Còn tín hiệu nảy qua tường bê tông trước khi vào anten, hay tín hiệu yếu đi 100 lần so với kỳ vọng, mô hình không có chỗ cho chúng. Dưới hầm, cả hai xảy ra cùng lúc.
Bê tông nuốt vi sóng
GPS phát ở băng L1 quanh 1575 MHz, radio vi sóng cư xử gần như ánh sáng ở scale kiến trúc. Nó tạo bóng đổ. Một tòa hầm xe là một cái bóng khổng lồ. Bê tông cốt thép làm suy hao 20–30 dB hoặc hơn mỗi lần xuyên; 20 dB là gấp 100 lần công suất. Tín hiệu chạm mặt đất vốn đã yếu, quanh −130 dBm, nằm dưới cả sàn nhiễu nhiệt, chỉ khôi phục được nhờ gain tương quan. Thêm một hai lớp sàn là hết cứu. Xuống hai tầng, receiver có thể không thấy vệ tinh dùng được nào. Hoặc tệ hơn: chỉ thấy phản xạ lọt qua khoảng trời hở ở cửa ramp, vừa trễ vừa méo.
Cầm chưa đủ bốn tín hiệu vệ tinh khỏe thì không giải vị trí tin cậy được. Nhiều máy fallback sang ước lượng dead reckoning cũ hoặc định vị Wi-Fi, loại vừa trôi vừa nhảy không báo trước. Chấm xanh vẫn cứ chạy. Máy đang đoán từ input hỏng, mỗi giây làm mới cái đoán đó một lần.
Multipath: lỗi trông như chuyển động
Gần cửa ramp, tầng thượng lộ thiên hay hầm có giếng trời, tín hiệu vẫn lọt được một ít. Ở đó multipath chiếm trọn error budget. Tín hiệu trực tiếp từ vệ tinh tới anten cùng lúc với bản sao trễ, dội từ tường bê tông, mái kim loại hoặc xe bên cạnh. Engine tương quan của receiver bám peak mạnh nhất, mà peak này có thể là tổng hợp của đường thẳng với đường dội, dài thêm Δd. Δd đó thành lỗi pseudorange, rồi thành lỗi vị trí cỡ chính Δd. Một cú dội ngang lối đỗ xe rồi quay lại cộng thêm vài chục mét đường đi dễ dàng.
Đứng yên mà xem: chấm quay quanh bạn 10–30 m khi geometry vệ tinh đổi và phản xạ khác lên ngôi. Bọn mình từng đo tử tế. Ghi chú bench: 3 điện thoại (một iPhone, một Pixel, một Samsung tầm trung), đứng yên 20 phút ở cửa ramp B1, log fix GNSS 1 Hz. Accuracy ngang máy tự báo chưa bao giờ vượt 15 m. Scatter thật của đám fix trải hơn 40 m. Không phải software bản đồ kém. Receiver báo trung thực ước lượng tốt nhất rút ra từ phép đo mơ hồ; phần lạc quan nằm ở con số accuracy đính kèm.
Trục dọc còn tệ hơn
Lỗi ngang hứng hết lời than. Nhưng lỗi dọc thường tệ hơn ngang 1,5–3 lần ngay cả điều kiện tốt, vì vệ tinh chủ yếu nằm trên đầu bạn. Nhìn từ receiver, chòm sao gần như dẹt, geometry ràng độ cao rất lỏng, số DOP trục dọc phình to. Nên GPS không trả lời nổi "đang ở tầng nào?" dù lat/lon trông ổn ở cửa hầm.
Câu hỏi tầng có công cụ tốt hơn. Một tầng hầm cao khoảng 3,2 m. Áp suất không khí giảm cỡ 12 Pa mỗi mét lên cao gần mực nước biển. Công thức thủy tĩnh Δh = ΔP/(ρg) đổi thẳng biến thiên áp suất ra biến thiên độ cao, nên một tầng là bậc nhảy chừng 38 Pa, so với sàn nhiễu barometer điện thoại 0,3–1 Pa RMS. Tín hiệu sạch ở đúng chỗ độ cao GPS chỉ cho ra nhiễu. Gợi ý tầng bằng áp kế của Wheria tồn tại nhờ chính chênh lệch này; phần suy diễn cùng các ca fail nằm trong bài toán áp kế hầm xe.
Những kiểu sai khác của cái pin
Dưới hầm là ca cực đoan; cùng thứ vật lý còn nhiều bà con trên mặt đất. Hẻm phố giữa các tòa nhà cao tạo hiệu ứng canyon giống multipath ngầm: mặt kính dội tín hiệu, bias pseudorange vài chục mét trong khi nửa bầu trời bị chắn. Jammer RF giá rẻ, phạm pháp nhưng có thật, nhấn chìm cả băng tần trong nhiễu; atuan từng log một cú rớt 40 s của mọi chòm sao cùng lúc giữa trời quang, chữ ký kinh điển của jammer chạy ngang qua chứ chẳng phải khí quyển gì. Thiết bị điện tử hỏng thì vô tình gây ra đúng chuyện đó bằng nhiễu broadband. Mỗi kiểu để lại vân tay khác nhau trong phép đo thô, còn trải nghiệm user thì giống hệt: pin không nằm chỗ bạn đứng.
Thử rồi bỏ
Bọn mình không nhảy thẳng tới việc giấu GPS trong nhà. Thử thứ nhất: trung bình fix trong 30 s ở cổng hầm để ghim điểm vào. Phần này chạy tốt, Wheria vẫn dùng, vì cổng hầm còn thấy trời đủ lâu cho một fix tử tế. Thử thứ hai: giữ fused location provider chạy dưới hầm rồi low-pass output của nó. Bỏ sau một tuần test, vì làm mượt một phép đo có sai số gấp 10 lần đại lượng cần đo chỉ cho ra câu trả lời sai một cách mượt mà. Thử thứ ba: Wi-Fi RTT ranging, phần cứng xịn thật khi có mặt; khổ nỗi trong các hầm bọn mình test quanh Quận 1 với Bình Thạnh, access point hỗ trợ RTT gần như không tồn tại. Bỏ vì độ phủ. Cái sống sót là cái cổng gác bên dưới.
Cổng gác: khi nào Wheria tin fix
Wheria coi fix GNSS như một nhân chứng cần đối chất. Accuracy máy báo phải chặt, số vệ tinh phải đủ, chuyển động suy ra phải hợp lý với người đi bộ quanh xe, barometer phải xác nhận bạn còn trên mặt đất. Trượt bất kỳ điều kiện nào, chấm biến mất.
fix = gnss.latestFix()
trustworthy =
fix.satelliteCount toi thieu 4
and fix.horizontalAccuracy duoi 20 m
and fix.jumpSinceLastFix duoi 10 m # toc do di bo, khong teleport
and barometer.floorEstimate() tu mat dat tro len
if trustworthy:
map.showDot(fix, radius = fix.horizontalAccuracy)
else:
map.hideDot()
path.extend(stepOdometry.delta()) # IMU tiep quan
Chấm tắt thì đường đi thay chỗ. Wheria dựng lại lộ trình từ phát hiện bước và fusion heading, vẽ kèm dải uncertainty thật thà, để màn hình cho thấy bạn đã đi đâu thay vì một nghiệm vệ tinh hỏng đang trôi. Pipeline indoor đầy đủ, dead reckoning IMU cộng gợi ý tầng barometer cộng fusion la bàn thích ứng, mô tả trong bài indoor navigation. Chi tiết odometry bước chân ở bài IMU.
Buổi chiều mọi thứ thành app
Landmark 81, TP.HCM, tầng B3. Đỗ xe, chụp ảnh số cột, đi ra thang máy, chẳng nghĩ ngợi gì. Lúc quay lại, GPS bảo xe đang nổi trên sông Sài Gòn, lệch 400 m về phía đông so với thực tế. Mình đi hai vòng trọn tầng mới thấy xe, mỗi lần so chấm bản đồ với bê tông là mỗi lần thua.
Tối đó quyết định rất gọn: thôi hiện chấm xanh nói dối dưới hầm, hiện đường thật sự đã đi. GPS vẫn ngon khi đỗ ngoài trời, Wheria vẫn dùng ở đó không lăn tăn. Chỉ là ngừng giả vờ nó chạy được ở nơi vật lý bảo không thể.