SD System Design In Depth
0%
lesson system-designgeospatialgeometry

Geofencing: point in polygon

Module 5.3: Geospatial Indexing & Proximity Search Updated Sep 18, 2026
📄 Cheat Sheet
Proximity Search & Geospatial Indexes Explained
Hello Interview Proximity Search & Geospatial Indexes Explained Watch on YouTube ↗

No strong dedicated geofencing video exists; this is the clearest explanation of the bounding-box and R-tree candidate filtering that makes geofencing scale, before the exact point-in-polygon test.

0:00 / --:--

Geofencing and the Point-in-Polygon Problem

Geofencing involves creating virtual boundaries (polygons) around real-world geographical areas. When a user or device enters, exits, or lingers within these boundaries, the system triggers an event. Applications range from targeted advertising and location-based reminders to fleet management and airport surge pricing zones.

The Core Algorithm

At the mathematical heart of geofencing is the "Point-in-Polygon" (PIP) problem. Given a coordinate (a point) and a set of vertices (a polygon), how do we determine if the point is inside the polygon?

Optimizing Geofencing at Scale

Evaluating the PIP algorithm for every user against every polygon is computationally impossible at scale. System design requires multi-stage filtering:

  1. Bounding Box Filtering: Every polygon has a minimum bounding box (MBR). PostGIS commonly uses a GiST spatial index with bounding-box operators to find candidate polygons before the exact predicate. This reduces the candidate polygons from millions to a handful.
  2. Exact PIP Calculation: Only for the polygons that pass the bounding box filter, the system runs an exact point-in-polygon test (for example ray casting, O(V) per polygon; libraries such as GEOS/PostGIS use prepared geometries with edge indexes for repeated tests) to confirm if the point is truly inside the precise polygon boundaries.

System Architecture

A stream processor can index versioned polygons and consume location updates, but point membership alone does not define an enter/exit event. Persist the last evaluated polygon version and membership per device. For example, GPS jitter around an airport boundary can alternate inside/outside every second; require hysteresis, consecutive observations, or a dwell interval before emitting a transition. Give transitions stable IDs so replay after a crash does not send duplicate notifications, and define how a polygon edit re-evaluates devices already nearby.

Worked example: delivery zones

Assume 50,000 polygons (delivery zones and airport pickup areas) averaging 200 vertices, and 200,000 location updates per second.

  • Naive: 200,000 x 50,000 = 10 billion polygon tests per second. Impossible.
  • R-tree on bounding boxes: each point overlaps on average about 1.5 polygon bounding boxes. That is 300,000 exact tests per second, each O(V) with 200 edges, about 60 million edge checks per second: a handful of CPU cores.
  • H3 pre-classification: fill each polygon with H3 resolution 9 cells (about 0.1 square km). Cells fully inside the polygon are marked interior, cells crossing the border are marked boundary. A point in an interior cell is inside with no geometry work; only points in boundary cells (often under 10 percent) need the exact test.

Indexing strategies

StrategyLookupUpdate cost when a polygon changesNotes
Brute force within a cityTest every polygon in the cityNoneUber's early geofence service did this after first finding the city; fine for small counts
R-tree over bounding boxes (PostGIS GiST)O(log n) plus exact testsReinsert one boxGeneral purpose default
Cell map (H3 or S2) with interior and boundary cellsOne hash lookup, exact test only on boundary cellsRecompute the polygon's cellsFastest at very high point rates
Prepared geometry cacheExact test in about O(log V) using an edge indexRebuild per polygon versionHelps when polygons have thousands of vertices

Event pipeline

flowchart LR L["Location updates"] --> P["Partition by device id"] P --> C["Candidate polygons from index"] C --> X["Exact point in polygon test"] X --> S["Compare with stored membership per device"] S --> H["Hysteresis and dwell rules"] H --> E["Enter or exit event with stable id"]
  • Hysteresis example: enter when the point is at least 20 m inside the boundary, exit only when at least 30 m outside, or require 3 consecutive fixes.
  • Idempotency: event ID = hash(device, polygon, polygon_version, transition_sequence), so replay after a crash produces the same ID and consumers deduplicate.
  • Partitioning by device keeps each device's membership state on one worker and preserves ordering of its fixes.
Check yourself Question 1 of 3
Flapping at the boundary recall
From: Geofencing and the Point-in-Polygon Problem

A driver waiting at an airport pickup boundary produces GPS fixes that alternate inside and outside every second. What must the system add so it does not fire a stream of enter and exit events?

Ray Casting: Point in Polygon

Cast a ray from the point to the right and count how many edges it crosses: odd means inside, even means outside. Each edge covers the half-open range [lower y, upper y), so a ray through a vertex is never double-counted. A cheap bounding-box check rejects far-away points first. Clicks snap to a 10-unit grid so vertex hits are easy to make.

Key Takeaways

Geofencing triggers events based on location boundaries.
Use a supported spatial index for bounding-box candidate filtering, then run the exact boundary-aware geometry predicate.
Stream processing engines are ideal for evaluating continuous location updates against geofences.
Was this lesson helpful? Spot an issue or have a suggestion? Let us know on GitHub.
Report an issue