Geo-Indistinguishability & the Planar Laplace Mechanism

Geo-indistinguishability is differential privacy rewritten for a metric space: instead of protecting the presence of one record in a database, it guarantees that two locations rr metres apart produce report distributions no further than a factor of eεre^{\varepsilon r} apart. The planar Laplace mechanism is the standard way to realize it, and it is the right tool whenever the thing you publish is a perturbed point rather than an aggregate.

When Geo-Indistinguishability Is the Right Guarantee permalink

Central differential privacy for location data protects a record’s membership in a dataset. That is exactly what you want for a count query, and exactly the wrong shape for a single user asking “what is near me?”. There is no dataset in that interaction and no neighbouring dataset to quantify over — there is one person, one coordinate, and one request that must not reveal where they are standing.

Geo-indistinguishability fixes the mismatch by replacing the neighbouring-dataset relation with a metric. Two locations count as “neighbours” to the degree that they are close together, and the indistinguishability requirement relaxes smoothly with distance. A café and the flat above it are almost perfectly indistinguishable; the same café and one in the next city are not, and that asymmetry is what makes the released point still useful for finding nearby restaurants.

Three properties follow directly and are worth stating before any code:

It composes like differential privacy, because it is differential privacy. The definition is a differential-privacy guarantee on the metric d(x,x)d(x, x') rather than the Hamming metric, so every composition theorem carries over. Repeated queries from the same device spend budget exactly as they do under privacy budget allocation for spatial queries, which is what makes it a formal guarantee rather than a masking heuristic.

It needs no trusted curator. The perturbation happens on the device, before anything is transmitted, which places it in the same family as local differential privacy for mobile clients. The server never sees the true coordinate, so a server compromise reveals only what was already published.

Its parameter has units. ε\varepsilon here is a privacy rate per metre, not a dimensionless budget. This trips people up constantly and is the single most common source of misconfiguration in a geo-indistinguishability deployment, because a number that looks reasonable at ε=1\varepsilon = 1 in the central model is catastrophic at ε=1\varepsilon = 1 per metre.

Algorithmic Specification permalink

The definition permalink

A mechanism MM mapping a true location xx to a reported location zz satisfies ε\varepsilon-geo-indistinguishability if for every pair of true locations x,xx, x' and every measurable output set SS:

Pr[M(x)S]eεd(x,x)Pr[M(x)S]\Pr[M(x) \in S] \le e^{\varepsilon\, d(x, x')} \cdot \Pr[M(x') \in S]

where dd is Euclidean distance in a projected metric CRS. Setting d(x,x)=1d(x,x') = 1 for all distinct pairs recovers ordinary ε\varepsilon-differential privacy, so this is a strict generalization rather than a different framework.

The quantity εr\varepsilon \cdot r is what an adversary’s posterior can shift by when comparing two hypotheses rr metres apart. Practitioners therefore speak of a privacy radius rr^{*}: the distance within which locations remain acceptably confusable, defined by fixing a tolerable factor \ell and solving

r=lnεr^{*} = \frac{\ln \ell}{\varepsilon}

A common design choice is =e\ell = e, giving r=1/εr^{*} = 1/\varepsilon — the radius at which the adversary’s belief ratio reaches e2.72e \approx 2.72.

The planar Laplace mechanism permalink

The canonical mechanism draws a displacement whose density decays exponentially in distance, normalized over the plane:

p(zx)=ε22πeεzxp(z \mid x) = \frac{\varepsilon^{2}}{2\pi}\, e^{-\varepsilon\, \lVert z - x \rVert}

Sampling is done in polar coordinates. The angle θ\theta is uniform on [0,2π)[0, 2\pi), and the radius follows a Gamma(2, 1/ε1/\varepsilon) distribution, which can be drawn by inverting its CDF using the Lambert WW function:

r=1ε(W1 ⁣(p1e)+1),pUniform(0,1)r = -\frac{1}{\varepsilon}\left(W_{-1}\!\left(\frac{p - 1}{e}\right) + 1\right), \qquad p \sim \mathrm{Uniform}(0,1)

The W1W_{-1} branch is the one that yields non-negative radii. Getting this branch wrong is the classic implementation bug and produces a mechanism that hugs the origin far too tightly — it looks like it is working and provides a fraction of the intended protection.

The expected displacement of the planar Laplace mechanism is

E[r]=2ε\mathbb{E}[r] = \frac{2}{\varepsilon}

which is a useful sanity check: at ε=0.005m1\varepsilon = 0.005\,\mathrm{m}^{-1}, points move 400 m on average.

Parameter reference permalink

Parameter Symbol Typical range Meaning
Privacy rate ε\varepsilon 10310^{-3}10210^{-2} m⁻¹ Privacy loss per metre of separation
Privacy radius r=ln/εr^{*} = \ln \ell / \varepsilon 100 – 1 000 m Distance within which locations stay confusable
Tolerable factor \ell ee to e2e^{2} The posterior shift you are willing to grant
Mean displacement 2/ε2/\varepsilon 200 – 2 000 m Expected distance a report moves
Queries per budget window nn 1 – 50 Reports before the cumulative rate must be re-checked
CRS local UTM Must be metric; degrees make ε meaningless

Prerequisites & Data Requirements permalink

  • A projected metric CRS. Every quantity above is in metres. Sampling the displacement in EPSG:4326 degrees applies a privacy rate roughly 10510^{5} times weaker than intended, and the error is silent because both units are plain floats. Project first, perturb, then reproject only for transport.
  • A stable per-device budget window. Because the guarantee composes, a device reporting continuously needs a policy: a total rate per hour or per day, and a rule for what happens when it is exhausted. Without one, the guarantee decays exactly as described under local differential privacy for mobile clients.
  • A high-quality random source. The mechanism is only as good as the entropy behind the uniform draw. A seeded generator that reaches production allows an adversary who observes two reports to subtract the noise, which removes the guarantee entirely rather than weakening it.
  • A plausibility post-filter, applied deliberately. Reports can land in the sea or outside the service area. Truncating or resampling is post-processing and does not break the guarantee, but it does bias the distribution, and the bias must be documented.
  • Python dependencies. numpy for sampling, scipy.special.lambertw for the radius inversion, pyproj or geopandas for the projection step, and shapely if you apply a plausibility filter.

Step-by-Step Implementation permalink

Step 1 — Fix the privacy radius, then derive ε permalink

Do not choose ε\varepsilon directly. Decide the distance within which two locations must remain confusable — the block, the neighbourhood, the town — and invert. This keeps the conversation with reviewers in units everyone understands.

import math

def epsilon_from_radius(privacy_radius_m: float, tolerable_factor: float = math.e) -> float:
    """Privacy rate (per metre) that keeps two points within `privacy_radius_m`
    indistinguishable up to `tolerable_factor`."""
    if privacy_radius_m <= 0:
        raise ValueError("privacy radius must be positive")
    return math.log(tolerable_factor) / privacy_radius_m

# A 200 m radius at a factor of e:
eps = epsilon_from_radius(200.0)      # 0.005 per metre
mean_shift = 2.0 / eps                # 400 m expected displacement

The mean displacement is always twice the privacy radius at =e\ell = e. If 400 m of typical error is unacceptable for the application, the honest response is that the application cannot have a 200 m privacy radius — not that ε should be quietly raised.

Step 2 — Project into metres permalink

import geopandas as gpd

def to_metric(gdf: gpd.GeoDataFrame) -> tuple[gpd.GeoDataFrame, int]:
    """Reproject WGS84 points into the local UTM zone so displacement is metric."""
    if gdf.crs is None:
        raise ValueError("input has no CRS")
    utm = gdf.estimate_utm_crs()
    return gdf.to_crs(utm), utm.to_epsg()

Step 3 — Sample the planar Laplace displacement permalink

import numpy as np
from scipy.special import lambertw

def planar_laplace(xs: np.ndarray, ys: np.ndarray, eps: float,
                   rng: np.random.Generator) -> tuple[np.ndarray, np.ndarray]:
    """Perturb projected coordinates under epsilon-geo-indistinguishability."""
    n = xs.size
    theta = rng.uniform(0.0, 2.0 * np.pi, n)
    p = rng.uniform(0.0, 1.0, n)
    # The W_{-1} branch is required; W_0 yields radii clustered at zero.
    w = lambertw((p - 1.0) / np.e, k=-1).real
    r = -(w + 1.0) / eps
    return xs + r * np.cos(theta), ys + r * np.sin(theta)

Step 4 — Apply a plausibility filter and record its effect permalink

from shapely.geometry import Point

def clamp_to_service_area(x: float, y: float, area, eps: float,
                          rng: np.random.Generator, max_tries: int = 8):
    """Resample until the report lands inside the service area. Post-processing,
    so the guarantee holds — but it biases displacement inward, so count it."""
    for _ in range(max_tries):
        if area.contains(Point(x, y)):
            return x, y, False
        x, y = planar_laplace(np.array([x]), np.array([y]), eps, rng)
        x, y = float(x[0]), float(y[0])
    return x, y, True      # exhausted: flag for the audit record

Step 5 — Track the cumulative rate per device permalink

from dataclasses import dataclass, field

@dataclass
class GeoIndBudget:
    """Sequential composition over one device's reporting window."""
    rate_ceiling: float                     # total epsilon-metres allowed
    spent: float = field(default=0.0)

    def charge(self, eps: float, radius_m: float) -> None:
        cost = eps * radius_m
        if self.spent + cost > self.rate_ceiling:
            raise RuntimeError("geo-indistinguishability budget exhausted")
        self.spent += cost

Validation & Re-identification Testing permalink

The first check is distributional: the empirical displacement should match the Gamma(2, 1/ε1/\varepsilon) shape, with mean 2/ε2/\varepsilon and a long tail. A histogram that peaks at zero indicates the Lambert branch bug; one with a hard outer edge indicates that a clamp was applied without being recorded.

def verify_planar_laplace(dx: np.ndarray, dy: np.ndarray, eps: float) -> dict:
    r = np.hypot(dx, dy)
    return {
        "mean_m": float(r.mean()),
        "expected_mean_m": 2.0 / eps,
        "median_m": float(np.median(r)),
        "frac_under_10m": float((r < 10.0).mean()),   # should be small
        "max_m": float(r.max()),
    }

The second check is adversarial and matters more. Simulate a Bayesian attacker who holds a prior over plausible true locations — a population raster, a building-footprint layer — and computes a posterior from the reported point. The quantity to report is the attacker’s expected error: the distance between their maximum-a-posteriori guess and the truth. A mechanism can be correctly implemented and still leave a small expected error if the prior is concentrated, which is the situation a re-identification risk assessment exists to surface.

The third check is repetition. Report the same true location nn times and measure how fast the attacker’s expected error falls. Because the noise is zero-mean and independent, averaging shrinks the error as 1/n1/\sqrt{n}, and the budget accounting has to make that visible before it happens in production.

Common Failure Modes & Gotchas permalink

Treating ε as dimensionless. A value of 0.5 is a sensible central-model budget and an absurd privacy rate: at ε=0.5m1\varepsilon = 0.5\,\mathrm{m}^{-1} the privacy radius is 2 m. Always carry the unit in the variable name and the config key.

The wrong Lambert branch. lambertw(z) defaults to the principal branch, which returns radii concentrated near zero. The resulting mechanism looks superficially correct — points move, the mean is finite — while delivering a small fraction of the intended displacement.

Perturbing in degrees. The most expensive version of the CRS mistake on this site, because the guarantee is stated in metres and the failure is silent. Assert crs.is_projected immediately before the sampling call.

Ignoring repetition. A single report is protected; a device reporting every minute is not. Geo-indistinguishability composes, so a continuous stream needs either memoization of the report for a stationary user or an explicit budget window.

Assuming the released point is inside the service area. Roughly a third of reports at a 200 m radius land more than 400 m away, which is enough to leave a small municipality. Handling this is required, and the handling biases the output.

Comparing ε across mechanisms. An ε\varepsilon from this page and an ε\varepsilon from Laplace and Gaussian noise for coordinate data are different quantities with different units. Never sum them into one ledger column without converting through a common ρ\rho under zero-concentrated accounting.

Compliance Alignment permalink

Control Satisfied by
GDPR Art. 5(1)© data minimisation The device transmits a perturbed point; the true coordinate never leaves it
GDPR Art. 25 privacy by design Perturbation is applied at collection, not as a downstream transform
GDPR Art. 35 impact assessment The privacy radius, the rate ceiling and the composition window are the three parameters the assessment records
CCPA §1798.100 minimisation expectations No raw location is collected, so there is none to disclose or delete
NIST SP 800-226 §4 mechanism selection Planar Laplace is a named metric-DP mechanism with a stated sensitivity analysis

The documentation burden is smaller than for a central-model release because there is no curator holding raw coordinates. What replaces it is a client-side attestation problem: the assessment has to describe how you know the shipped client actually applies the mechanism, and how a tampered client would be detected.

FAQ permalink

Is geo-indistinguishability the same as differential privacy?

It is differential privacy instantiated on a metric other than the Hamming distance between datasets. Every theorem — post-processing immunity, sequential composition, the conversion to zero-concentrated accounting — carries over unchanged. The practical differences are that ε has units of inverse metres and that the guarantee is about one user’s location rather than one record’s membership.

How do I pick ε without guessing?

Pick the privacy radius instead. Decide the distance within which two locations must stay confusable, choose a tolerable posterior factor (e is a defensible default), and compute ε=ln/r\varepsilon = \ln \ell / r^{*}. This makes the parameter reviewable by people who do not work in privacy, because it is stated as a distance.

Why planar Laplace rather than adding Laplace noise to each axis independently?

Per-axis Laplace noise is not rotationally symmetric — it produces a diamond-shaped density that leaks the axis alignment and gives different protection along a road than across it. The planar mechanism’s density depends only on radial distance, which is what the metric definition requires.

Does clamping reports to land or to a service area break the guarantee?

No. Any function of the mechanism’s output is post-processing and cannot weaken the bound. It does bias the reported distribution toward the interior of the allowed region, so record the resampled fraction and account for the bias in any utility metric computed downstream.

← Back to Differential Privacy for Location Data