I build Wimemo, an app that turns your camera roll into a map of everywhere you've been — it plots your photos and draws the route of each trip. That afternoon I was staring at a hike through Shennongjia, a mountain reserve in central China. The trail line hugged the actual switchbacks on the map, beautifully correct. Every cluster of photos from that same hike floated in the forest, exactly 500 meters to the west of the trail.
I had seen 500 meters before. In China, 500 meters has a name.
The 500-meter country
Maps in mainland China don't use the GPS coordinates the rest of the world uses. By law, map tiles are rendered in an obfuscated coordinate system called GCJ-02 — the "true" WGS-84 latitude/longitude is shifted by a classified, nonlinear offset before anything is drawn. Your iPhone's camera, meanwhile, stamps EXIF photos in plain WGS-84. Put a WGS-84 photo onto a GCJ-02 map tile without converting it, and it lands a few hundred meters from where you stood. Around 500, give or take.
So the first bug report made perfect sense: "my photos are ~200m off to the left on the map." Classic missing WGS→GCJ correction. I added the conversion to the coordinate I hand the map, the pins snapped onto the streets, and that was that. A clean fix.
Then a different report came in, and this is where I got clever.
The inference
"The coordinates feel unstable — some photos are right, some are wrong."
I didn't have a reproduction. I had a hypothesis. China has a lot of photos that never came from a camera: images saved out of WeChat, Xiaohongshu, screenshots of other people's maps. What if some of those arrive with EXIF that's already in GCJ-02? Then my new correction would convert an already-converted coordinate — a double shift, off in the other direction. Mixed sources. That would feel "unstable."
It was a plausible story. So I built a detector for it. The one signal I had was the filename: photos from those apps carry tell-tale prefixes — mmexport, wx_camera, and so on. So:
// Illustrative — skip correction for photos that "look" already-GCJ.
if filename.hasGCJSourcePrefix { // mmexport / wx_camera / xhs / …
return rawCoordinate // assume already GCJ, don't shift
}
return convertWGS84ToGCJ02(rawCoordinate)
It shipped. It looked smart. It was the bug.
The crash
Back to Shennongjia. Why was the route right and the photos wrong, when they started from the same resolved points?
Because the route had a safety net the photos didn't. To draw a walking line, Wimemo asks MKDirections for the path between points — and MKDirections, on a Chinese map, snaps that path onto the GCJ-02 road network. The route comes back already in the map's coordinate system, correct by construction. It never needed my conversion at all.
The photos had no road network to snap to. They depended entirely on my correction — and my clever filename heuristic had looked at these camera originals, decided some of them "looked already-GCJ," and skipped the conversion. Skipped photos fell back to raw WGS-84. Raw WGS-84 on a GCJ-02 map is exactly 500 meters west.
My heuristic, built to fix a rare "double shift" I had never actually observed, had reintroduced the common, glaring "everything is shifted" bug I had already killed. It made things worse on the 99% case to maybe help a 1% case that might not even exist.
The fix was a delete
Inside mainland China, convert every photo. Unconditionally. No filename guessing.
The justification took thirty seconds once I stopped defending the hypothesis. WeChat strips GPS from photos on send by default — so a photo that has usable coordinates at all is, overwhelmingly, a camera original in WGS-84. A filename prefix is not evidence about a coordinate system; it's evidence about an app. I was using an unreliable signal to branch on a case that barely happens, and paying for it on the case that always happens.
I deleted the heuristic. Net change: −26 lines. Now the photos and the route — the route already validated as correct by being snapped to the road network — are locked to the same coordinate basis. They agree because they can't disagree.
What I'd tell past me
One ground truth beats ten inferences. The corrected route was sitting right there the whole time: a thing I knew was in the right place, because the road network had snapped it there. The photos floating to its left weren't a mystery to theorize about — they were a measurement against a reference. The moment I treated the route as ground truth, the diagnosis was instant: "the photo branch didn't get corrected." When you design a system, go out of your way to keep one component you can trust absolutely. It turns debugging from speculation into comparison.
And the principle I keep taped to the inside of my skull now:
A heuristic is a liability. Using an unreliable signal to handle a rare case buys you a guaranteed risk on the common one.
When a single unconditional rule covers 99% of cases at acceptable cost, do the unconditional rule. "Cleverly" branching on a bad signal isn't sophistication — it's a second bug waiting for a worse day to surface. Mine waited for a mountain.
Two more things that fell out of it:
- Falsify "it's unstable" before you act on it. I built a double-shift fix without ever holding a single double-shifted photo. One demand for a counter-example screenshot would have shown there was no instability at all — only "the correction didn't run." A bug report is a symptom, not a diagnosis.
- A constant offset is a coordinate mismatch; random scatter is real position error. And direction is data: everything shifted west means longitude came out too small — under-corrected, not over-corrected. The bug was telling me exactly which branch failed. I just had to read it.
The smartest thing I did all day was take code out.
I write these from building Wimemo, a privacy-first travel memory app — your photos stay on your device, and the map is yours. If you've ever re-broken your own fix by being too clever, I'd love to hear the story.