Privacy zone modes — technical reference
This page describes the exact algorithm behind each privacy zone mode. For a non-technical overview, see Privacy & data control.
Privacy zones are evaluated server-side on every public data request. Each GPS point is tested against all configured zones (in order); the first matching zone determines the transformation. The original points are never modified — only the public-facing output is filtered.
Zone matching
A point (lat, lon) is inside a zone if the great-circle distance
to the zone centre is within the configured radius:
dist = haversine(point.lat, point.lon, zone.lat, zone.lon)
if dist <= zone.radius_m:
# point is inside this zone — apply the mode
Zones are evaluated in definition order. The first matching zone wins. If no zone matches, the point passes through unchanged.
Snap to center
Config value: "mode": "snap"
Algorithm. Every point inside the zone is replaced with the zone's centre coordinates. Timestamps, altitude, speed, and all other metadata are preserved.
modified = copy(point)
modified.lat = zone.lat
modified.lon = zone.lon
Effect. On the public map, all movement inside the zone collapses into a single dot at the zone centre. The polyline enters the dot, the user "stays" there for however long the visit lasted, then the polyline leaves.
Information preserved: arrival time, departure time, visit duration, altitude profile, speed (though the speed values at the centre point are from the original position — they may look odd).
Information destroyed: exact position within the zone. An observer cannot distinguish the front door from the back garden.
Threat model. Protects against casual observers and screenshot leaks. Does not protect against correlation attacks if the zone centre itself is identifiable (e.g. it sits on a single residential building). For that, combine with a larger radius or use Coarsen.
Remove points
Config value: "mode": "remove"
Algorithm. Every point inside the zone is dropped from the output entirely.
if point is inside zone:
skip # do not include in output
Effect. The polyline has a visible gap. The last point before the zone and the first point after create a discontinuity — the line simply stops and picks up later. Depending on the map renderer, this may show as a gap or a straight jump.
Information preserved: nothing inside the zone. Points before and after are untouched, so an observer knows the user entered the zone's general area and later left.
Information destroyed: all GPS data inside the zone — position, timing, dwell duration. The gap itself reveals that something was hidden, but not where or for how long (if the zone is large enough).
Threat model. Strong local privacy — no data is emitted. Weakness: the visible gap is a signal ("she hides something here"). For a version without the visible gap, see Route cloaking.
Random offset (jitter)
Config value: "mode": "jitter"
Algorithm. Each point inside the zone is displaced by a random
vector. The offset distance is drawn uniformly from [100, 500]
metres; the direction is drawn uniformly from [0, 2π).
angle = uniform(0, 2 * pi)
offset_m = uniform(100, 500)
dlat = (offset_m / 111_111) * cos(angle)
dlon = (offset_m / (111_111 * cos(radians(lat)))) * sin(angle)
modified.lat = lat + dlat
modified.lon = lon + dlon
The constant 111,111 converts metres to degrees latitude (one
degree ≈ 111.111 km). The longitude conversion accounts for latitude
by dividing by cos(lat), correcting for meridian convergence.
Effect. The polyline wanders randomly inside the zone — it looks like a real but imprecise GPS trace. The route stays continuous (no gaps), but the exact address cannot be determined from any single point.
Per-point independence. Each point gets its own random draw. Consecutive points are not correlated, so averaging two neighbours does not recover the true position. However, averaging many visits to the same zone over time will converge toward the true centre (law of large numbers).
Security properties.
| Property | Status |
|---|---|
| Single-point privacy | 100–500 m uncertainty ring |
| Temporal correlation | Independent per point — no drift pattern |
| Multi-visit convergence | Vulnerable: N visits → mean converges to true centre |
| k-anonymity | Not provided (randomisation, not generalisation) |
When to use. Best for zones where you want the route to look natural and continuous but don't need cryptographic guarantees. Typical use: city-centre apartment where the building is one of hundreds in the jitter radius.
Coarsen (~1 km grid)
Config value: "mode": "coarsen"
Algorithm. Coordinates are rounded to two decimal places, snapping them to a grid of approximately 1.11 km × 1.11 km cells (at the equator; cells narrow with latitude).
modified.lat = round(lat, 2) # e.g. 50.0812 → 50.08
modified.lon = round(lon, 2) # e.g. 14.4198 → 14.42
Grid cell size varies by latitude:
| Latitude | Cell width (E–W) | Cell height (N–S) |
|---|---|---|
| 0° (equator) | 1,111 m | 1,111 m |
| 30° | 963 m | 1,111 m |
| 50° (Prague) | 714 m | 1,111 m |
| 60° | 556 m | 1,111 m |
Effect. Points "snap" to grid corners. Multiple points along a street will all land on the same corner if they share the same rounded values. The polyline becomes a stepped path that hops between grid intersections.
k-anonymity. A single cell at 50°N covers approximately 714 × 1,111 = 793,254 m² ≈ 0.79 km². In a dense European city this typically contains hundreds to thousands of residential addresses, providing meaningful k-anonymity.
Security properties.
| Property | Status |
|---|---|
| Single-point privacy | Grid-cell level (~0.8 km² at 50°N) |
| Deterministic | Same input always produces same output — no randomness |
| Multi-visit convergence | Not applicable (already deterministic) |
| k-anonymity | Implicit via grid population density |
When to use. Best when you want a predictable, deterministic transformation. Good for regulatory compliance where "neighbourhood- level accuracy" is the stated requirement. This is the same approach used by Google Maps Timeline's privacy controls.
Delayed release
Config value: "mode": "delay"
Algorithm. Points inside the zone are suppressed if they are younger than the configured delay (default: 6 hours). Once the delay has elapsed, the points are released unmodified.
delay_hours = zone.delay_hours or 6
point_age = (now - point.timestamp) / 3600
if point_age < delay_hours:
skip # too recent — suppress
else:
emit(point) # old enough — release as-is
Effect. A live follower watching your map in real time cannot see you inside the zone. The zone appears empty, as if you haven't arrived yet. Hours later (after the delay window), the points appear retroactively and the full route becomes visible.
Information preserved: everything — eventually. After the delay window, all original points with full precision are visible. This mode trades temporal privacy for spatial completeness.
Information destroyed: real-time presence. An observer cannot determine whether you are currently inside the zone.
Security properties.
| Property | Status |
|---|---|
| Real-time protection | Full — no data emitted during delay window |
| Retrospective protection | None — all points eventually released |
| Live-tracking defence | Strong against casual followers |
| Forensic analysis | Not protected (data is just time-shifted) |
When to use. Ideal for live-tracking scenarios where followers should see your route after the fact but not know your current real-time location near sensitive places. Common setup: 6–24 hour delay around home/work.
Route cloaking
Config value: "mode": "cloak"
Algorithm. Identical to Remove points — all points inside the zone are dropped. The difference is visual: the map renderer naturally connects the last pre-zone point to the first post-zone point with a straight polyline segment, so the route looks like the user passed through the area without stopping.
if point is inside zone:
skip # same as "remove"
# The polyline renderer connects the gap automatically
Effect. The route line cuts straight through the zone without pausing. There is no visible gap (unlike Remove). An observer sees continuous travel but cannot determine the actual path taken inside the zone.
Information preserved: approximate entry and exit points (the last/first points outside the zone boundary). Travel continuity is implied.
Information destroyed: actual path inside the zone, dwell time, stops, speed, altitude. The straight-line segment carries no data about what actually happened.
Security properties.
| Property | Status |
|---|---|
| Visual gap | None — route appears continuous |
| Dwell-time leakage | Partially: timestamps on entry/exit points reveal total time spent in zone |
| Path leakage | None — real path replaced by straight line |
| Plausible deniability | Moderate: the straight line could be a real high-speed transit |
Limitation. An observer can compute entry and exit timestamps from the surrounding points, revealing how long you were inside the zone. If you need to hide dwell time too, consider Remove (which at least creates ambiguity about whether you stopped or just passed through quickly).
When to use. Best when you want no visible gap on the public map but want to hide the actual movements inside the zone. Good default for most users who just want "don't show my home street".
Combining modes
Each zone has exactly one mode. To layer protections, define overlapping zones with different modes and radii:
Zone "Home — coarse" : lat 50.08, lon 14.42, radius 2000m, mode: coarsen
Zone "Home — exact" : lat 50.08, lon 14.42, radius 300m, mode: remove
Because zones are matched in definition order, a point within 300 m of home is caught by the first matching zone. Order your zones from most specific (smallest radius, strongest protection) to broadest.
See also: Privacy & data control for the non-technical overview.
Need help? Contact support · Where Is Tereza?