“The pin is on the wrong side of the street.” Four different bugs produce that sentence, and they need different fixes. The quickest way to tell them apart is whether the error is random or consistent.
The symptom
A driver’s app shows the stop across the road from the house. Navigation announces “destination on your left” when it is on the right. A proof-of-delivery geofence never fires because the expected point is 12 m away on the far kerb. On a map the point sits inside the neighbour-opposite’s garden.
Start by triaging on one question: is it wrong every time, or about half the time? Consistent, systematic errors — every address on every street offset the same way — are a sign or a units bug in your code, and they are the easy ones. Errors that look random per address are a data problem in the geocoder’s output. Errors that appear only on east–west streets, or only on north–south ones, are the degree-space bug in section three.
Cause 1: the point is on the centreline
Most street-level geocoding is address-range interpolation, not a rooftop lookup. The road network stores, per segment, the house-number ranges on each side — in US TIGER/Line the fields are literally LFROMADD, LTOADD, RFROMADD and RTOADD for left-from, left-to, right-from, right-to. To geocode “120 High Street”, the geocoder finds the segment whose range contains 120 and interpolates a fraction along it.
Interpolating along the segment gives you a point on the centreline. It has no side at all. Any code that then asks “which side is this on?” is asking about a point that was constructed to be exactly between the two answers, and rounding noise decides. This is the cause when the errors look random.
The information you need is already in the data and is being discarded: the parity. House number 120 falls in whichever of the left or right ranges contains it, and that is the side. Do not assume even numbers are always on the right — the convention is per-segment and is recorded in the range fields precisely because it is not universal. Read the parity from the segment, not from a rule of thumb. This is a geocoding accuracy level question underneath: a parcel-centroid or rooftop match has a real side and an interpolated match does not.
Cause 2: the offset was computed in degrees
Having chosen a side, code offsets the point perpendicular to the street by a set-back distance — say 8 m. If that offset is applied in degrees, it is applied to two axes with different scales:
intended set-back: 8 m
naive: offset = 8 / 111320 = 0.0000719 degrees, applied on both axes
at latitude 51.5 (cos = 0.6224):
north-south component 0.0000719 deg -> 8.0 m correct
east-west component 0.0000719 deg -> 4.98 m 38% short
if the code instead divides only by cos and forgets it for latitude:
east-west component becomes 8 / 0.6224 = 12.9 m 61% too far
Either way the offset is elliptical rather than circular, so the error is worst on streets running in the direction the code got wrong and invisible on streets running the other way. A 12.9 m offset on a 10 m-wide street overshoots the pavement and lands in the building line; a 5 m offset on the same street does not clear the carriageway, so the point stays ambiguously in the road. Both read to a driver as “wrong side”.
The fix is to do the geometry in metres. Project the segment and the point into a local metric coordinate reference system — the appropriate UTM zone or the national grid — offset there, and convert back. This is the same root cause as a distance calculation that comes out 20% wrong, which has the full projection argument.
Cause 3: the cross product is mirrored
“Which side” is the sign of a two-dimensional cross product. For a segment from A to B and a point P:
cross = (Bx - Ax) * (Py - Ay) - (By - Ay) * (Px - Ax)
cross > 0 -> P is to the LEFT of the direction A -> B
cross < 0 -> P is to the RIGHT
cross = 0 -> P is on the line
That is correct only if x is easting and y is northing. Feed it latitude as x and longitude as y and you have applied a reflection of the plane, which reverses the sign of every cross product. The result is not noisy: every point goes to the wrong side, on every street, always. That total consistency is the diagnostic — if flipping your sign fixes the whole dataset, this was the bug.
It happens because the ecosystem does not agree on order. GeoJSON, as specified in RFC 7946, fixes a position as longitude then latitude. PostGIS ST_MakePoint takes x then y, so longitude then latitude. Most consumer mapping APIs, and most humans, say latitude then longitude. A pipeline that reads GeoJSON, passes through a library expecting (lat, lon) tuples and writes back out will transpose silently, because both orderings are valid pairs of numbers and nothing throws.
Cause 4: no heading, so no left or right
“Left” is meaningless without a direction of travel, and on a two-way street the same kerb is left or right depending on which way the van is going. Map matching hands you the segment but not the direction — that is a separate output, and the HMM matcher only recovers it from the sequence.
The available signal is course over ground, and it has a specific limitation: it is derived from successive position fixes, so it is undefined at rest. A stationary receiver reports a heading that wanders through all 360 degrees as the fix jitters. Gate on speed — ignore heading below roughly 3 m/s — and compute the bearing from consecutive fixes rather than trusting a per-fix heading field. In an urban canyon, multipath adds a further problem: signals reflected off one side of the street bias fixes systematically toward that side, so the error is not zero-mean and averaging does not remove it.
The fix, in order
- Establish which failure you have. Offset a hundred known addresses and count how many land on the wrong side. Near 100% is the handedness bug; near 50% is centreline interpolation; a pattern by street orientation is the degree-space bug.
- Project to a metric CRS before any geometry. Do the perpendicular offset, the cross product and the set-back in metres, and convert back to WGS84 only for storage and display.
- Take the side from the address-range parity on the matched segment, not from a guess about odd and even. If the geocoder does not expose which range matched, treat the side as unknown.
- Offset perpendicular by a set-back that clears the carriageway — half the street width plus a couple of metres, so 8 to 12 m on a typical residential street. Compute the unit normal as
(−uy, ux)from the unit directionu, and apply the sign from step 3. - Validate against something with an actual footprint. If you have parcel or building polygons, check the offset point falls inside the right one; if it does not, keep the centreline point and flag the record rather than shipping a confidently wrong pin.
- Carry an explicit “side unknown” state through to the driver app. A pin the app knows is uncertain can be drawn as a circle over the street; a wrong pin drawn as a precise dot costs a redelivery. The related problem of the pin being on the right side but the wrong door is entrance snapping.
Top comments (0)