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?