Real-Time Geofencing and Location Tracking with SQL

Real-Time Geofencing and Location Tracking with SQL

Real-time geofencing with SQL means evaluating GPS coordinates against virtual boundary zones as each location update arrives, triggering alerts in milliseconds instead of polling on a schedule. This tutorial builds a complete geofencing system using RisingWave, a PostgreSQL-compatible streaming database: ingest vehicle GPS data from Kafka, apply the Haversine formula, and maintain continuously updated entry/exit alerts using standard SQL materialized views.

Why Geofencing Belongs in a Streaming Database

Traditional geofencing systems poll a database on a fixed schedule. A fleet management system running queries every 30 seconds misses the moment a truck enters a restricted area at second 15. By the time the next poll fires, the truck has already been inside the zone for 29 seconds, a silent violation with no alert.

The alternative is event-driven geofencing: evaluate every GPS update against every geofence boundary the instant it arrives. This eliminates the polling gap entirely and delivers sub-second alerts regardless of how frequently devices transmit their location.

RisingWave makes this straightforward. You define geofencing logic once in SQL, and the database continuously evaluates each incoming GPS ping against your rules. No custom stream processing code, no JVM cluster to manage, no polling loops. The materialized views stay current automatically as new location data flows in from Kafka.

Architecture Overview

The geofencing system has four layers:

  1. Ingestion - GPS events stream in from Kafka (vehicles, drones, assets) and land in a RisingWave source table
  2. Distance computation - A materialized view applies the Haversine formula to calculate the exact distance from each device to each geofence center
  3. Status tracking - A second materialized view tracks the latest inside/outside status per device per geofence
  4. Alert generation - A third materialized view filters for violations: unauthorized entries into restricted zones and departures from depots
graph LR
    A[GPS Devices] -->|Kafka topic| B[geo_gps_events_enriched]
    C[geo_geofences] -->|equi-join| D[geo_distance_to_fence MV]
    B --> D
    D --> E[geo_device_status MV]
    E --> F[geo_fence_violations MV]
    G[geo_latest_positions MV] --> H[Fleet Dashboard]
    B --> G
    F -->|Kafka Sink| I[Alert Service / PagerDuty]

RisingWave handles every layer after Kafka ingestion using standard SQL. No separate stream processing framework is needed for the distance math, the state management, or the alert logic.

Data Model

Reference Tables

Start with two static reference tables: one for devices and one for geofence zones.

CREATE TABLE geo_devices (
    device_id   VARCHAR PRIMARY KEY,
    device_name VARCHAR,
    device_type VARCHAR,    -- vehicle, drone, asset, person
    owner       VARCHAR
);

CREATE TABLE geo_geofences (
    fence_id      INT PRIMARY KEY,
    fence_name    VARCHAR,
    center_lat    DOUBLE PRECISION,
    center_lon    DOUBLE PRECISION,
    radius_meters DOUBLE PRECISION,
    fence_type    VARCHAR    -- depot, restricted, delivery_zone, campus
);

Insert a representative set of devices and geofences. The scenario covers a logistics fleet operating across San Francisco, with four zones: a main depot, a downtown restricted area, a delivery zone, and a corporate campus.

INSERT INTO geo_devices VALUES
    ('veh-001', 'Delivery Truck Alpha', 'vehicle', 'LogiCo Fleet'),
    ('veh-002', 'Delivery Truck Beta',  'vehicle', 'LogiCo Fleet'),
    ('veh-003', 'Service Van Gamma',    'vehicle', 'LogiCo Fleet'),
    ('drn-001', 'Inspection Drone 1',   'drone',   'Site Ops'),
    ('ast-001', 'High-Value Asset A',   'asset',   'Warehouse Team');

INSERT INTO geo_geofences VALUES
    (1, 'Main Depot',          37.7749, -122.4194, 500.0,  'depot'),
    (2, 'Downtown Restricted', 37.7858, -122.4065, 300.0,  'restricted'),
    (3, 'Zone A Delivery',     37.7680, -122.4312, 400.0,  'delivery_zone'),
    (4, 'Corporate Campus',    37.7920, -122.3900, 600.0,  'campus');

GPS Events Source Table

In production, this table is backed by a Kafka source connector. The schema includes a fence_id column, which represents upstream enrichment: either the GPS producer tags each reading with the relevant fence IDs, or an enrichment step fans out each GPS event into one row per geofence it needs to be evaluated against. This is a standard pattern for high-scale geofencing, since it converts the cross-product join into a simple equi-join that RisingWave can stream efficiently.

-- In production, use connector = 'kafka' in the WITH clause
CREATE TABLE geo_gps_events_enriched (
    event_id  BIGINT,
    device_id VARCHAR,
    fence_id  INT,              -- which fence this reading is evaluated against
    latitude  DOUBLE PRECISION,
    longitude DOUBLE PRECISION,
    speed_kmh DOUBLE PRECISION,
    heading   DOUBLE PRECISION,
    event_ts  TIMESTAMPTZ
);

A production Kafka source definition looks like this:

CREATE TABLE geo_gps_events_enriched (
    event_id  BIGINT,
    device_id VARCHAR,
    fence_id  INT,
    latitude  DOUBLE PRECISION,
    longitude DOUBLE PRECISION,
    speed_kmh DOUBLE PRECISION,
    heading   DOUBLE PRECISION,
    event_ts  TIMESTAMPTZ
) WITH (
    connector = 'kafka',
    topic = 'gps-events-enriched',
    properties.bootstrap.server = 'broker:9092',
    scan.startup.mode = 'earliest'
) FORMAT PLAIN ENCODE JSON;

Computing Geofence Distances with Haversine

The Haversine formula calculates the great-circle distance between two points on a sphere, accounting for Earth's curvature. For geofencing, this is significantly more accurate than Euclidean distance, especially over distances greater than a few kilometers.

The formula in SQL:

distance_meters = 6371000 * 2 * ASIN(SQRT(
    SIN((lat2 - lat1) / 2)^2 +
    COS(lat1) * COS(lat2) * SIN((lon2 - lon1) / 2)^2
))

Where latitude and longitude values are converted from degrees to radians using RADIANS().

A device is inside a geofence when its distance to the center is less than or equal to radius_meters.

Distance Materialized View

This materialized view joins GPS events to their corresponding geofence and computes the Haversine distance continuously as new GPS pings arrive:

CREATE MATERIALIZED VIEW geo_distance_to_fence AS
SELECT
    g.event_id,
    g.device_id,
    d.device_name,
    d.device_type,
    g.latitude,
    g.longitude,
    g.speed_kmh,
    g.event_ts,
    f.fence_id,
    f.fence_name,
    f.fence_type,
    f.radius_meters,
    ROUND((6371000.0 * 2.0 * ASIN(SQRT(
        POWER(SIN((RADIANS(g.latitude)  - RADIANS(f.center_lat)) / 2.0), 2) +
        COS(RADIANS(g.latitude)) * COS(RADIANS(f.center_lat)) *
        POWER(SIN((RADIANS(g.longitude) - RADIANS(f.center_lon)) / 2.0), 2)
    )))::NUMERIC, 1) AS distance_to_center_m,
    CASE WHEN 6371000.0 * 2.0 * ASIN(SQRT(
        POWER(SIN((RADIANS(g.latitude)  - RADIANS(f.center_lat)) / 2.0), 2) +
        COS(RADIANS(g.latitude)) * COS(RADIANS(f.center_lat)) *
        POWER(SIN((RADIANS(g.longitude) - RADIANS(f.center_lon)) / 2.0), 2)
    )) <= f.radius_meters THEN true ELSE false END AS is_inside
FROM geo_gps_events_enriched g
JOIN geo_devices   d ON g.device_id = d.device_id
JOIN geo_geofences f ON g.fence_id  = f.fence_id;

Every GPS event tagged with a fence_id is immediately joined to the fence definition and evaluated. The result set grows as new pings arrive: the view is never re-scanned from scratch, only updated incrementally with the new events.

Tracking Current Device Status

The geo_distance_to_fence view contains one row per GPS ping per fence. For monitoring and dashboards, you need the current status: the latest evaluated position for each device-fence combination. A second materialized view uses DISTINCT ON to keep only the most recent event:

CREATE MATERIALIZED VIEW geo_device_status AS
SELECT DISTINCT ON (device_id, fence_id)
    device_id,
    device_name,
    device_type,
    fence_id,
    fence_name,
    fence_type,
    latitude,
    longitude,
    speed_kmh,
    distance_to_center_m,
    radius_meters,
    is_inside,
    event_ts AS last_seen
FROM geo_distance_to_fence
ORDER BY device_id, fence_id, event_ts DESC;

Query this view to see the full geofence status for every device:

SELECT device_id, device_name, fence_name, fence_type,
       distance_to_center_m, radius_meters, is_inside, last_seen
FROM geo_device_status
ORDER BY device_id, fence_id;

Verified on RisingWave 2.8.0:

 device_id |     device_name      |     fence_name      |  fence_type   | distance_to_center_m | radius_meters | is_inside |         last_seen
-----------+----------------------+---------------------+---------------+----------------------+---------------+-----------+---------------------------
 ast-001   | High-Value Asset A   | Main Depot          | depot         |                 67.3 |           500 | t         | 2026-04-01 11:00:00+00:00
 ast-001   | High-Value Asset A   | Downtown Restricted | restricted    |               1617.7 |           300 | f         | 2026-04-01 11:00:00+00:00
 ast-001   | High-Value Asset A   | Zone A Delivery     | delivery_zone |               1324.0 |           400 | f         | 2026-04-01 11:00:00+00:00
 ast-001   | High-Value Asset A   | Corporate Campus    | campus        |               3176.0 |           600 | f         | 2026-04-01 11:00:00+00:00
 drn-001   | Inspection Drone 1   | Main Depot          | depot         |               3208.0 |           500 | f         | 2026-04-01 10:06:00+00:00
 drn-001   | Inspection Drone 1   | Downtown Restricted | restricted    |               1605.5 |           300 | f         | 2026-04-01 10:06:00+00:00
 drn-001   | Inspection Drone 1   | Zone A Delivery     | delivery_zone |               4498.1 |           400 | f         | 2026-04-01 10:06:00+00:00
 drn-001   | Inspection Drone 1   | Corporate Campus    | campus        |                  0.0 |           600 | t         | 2026-04-01 10:06:00+00:00
 veh-001   | Delivery Truck Alpha | Main Depot          | depot         |                  0.0 |           500 | t         | 2026-04-01 10:00:00+00:00
 veh-001   | Delivery Truck Alpha | Downtown Restricted | restricted    |               1659.6 |           300 | f         | 2026-04-01 10:00:00+00:00
 veh-001   | Delivery Truck Alpha | Zone A Delivery     | delivery_zone |               1290.1 |           400 | f         | 2026-04-01 10:00:00+00:00
 veh-001   | Delivery Truck Alpha | Corporate Campus    | campus        |               3208.0 |           600 | f         | 2026-04-01 10:00:00+00:00
 veh-002   | Delivery Truck Beta  | Main Depot          | depot         |               1399.9 |           500 | f         | 2026-04-01 08:40:00+00:00
 veh-002   | Delivery Truck Beta  | Downtown Restricted | restricted    |               3049.7 |           300 | f         | 2026-04-01 08:40:00+00:00
 veh-002   | Delivery Truck Beta  | Zone A Delivery     | delivery_zone |                113.4 |           400 | t         | 2026-04-01 08:40:00+00:00
 veh-002   | Delivery Truck Beta  | Corporate Campus    | campus        |               4607.5 |           600 | f         | 2026-04-01 08:40:00+00:00
 veh-003   | Service Van Gamma    | Main Depot          | depot         |               1900.8 |           500 | f         | 2026-04-01 08:10:00+00:00
 veh-003   | Service Van Gamma    | Downtown Restricted | restricted    |               3535.0 |           300 | f         | 2026-04-01 08:10:00+00:00
 veh-003   | Service Van Gamma    | Zone A Delivery     | delivery_zone |                895.8 |           400 | f         | 2026-04-01 08:10:00+00:00
 veh-003   | Service Van Gamma    | Corporate Campus    | campus        |               5002.0 |           600 | f         | 2026-04-01 08:10:00+00:00

Reading the output: ast-001 (High-Value Asset A) is inside the Main Depot (67.3m from center, radius 500m). drn-001 (Inspection Drone 1) is at the exact center of the Corporate Campus. veh-002 is actively working inside Zone A Delivery. veh-003 is outside all geofences, roaming freely.

Generating Entry and Exit Alerts

With current device status available in a materialized view, alert generation is a filter query. The geo_fence_violations view applies business rules and continuously filters for active violations:

CREATE MATERIALIZED VIEW geo_fence_violations AS
SELECT
    device_id,
    device_name,
    device_type,
    fence_id,
    fence_name,
    fence_type,
    latitude,
    longitude,
    distance_to_center_m,
    radius_meters,
    CASE
        WHEN fence_type = 'restricted' AND is_inside = true
            THEN 'UNAUTHORIZED_ENTRY'
        WHEN fence_type = 'depot'      AND is_inside = false
            THEN 'LEFT_DEPOT'
        ELSE 'OK'
    END AS alert_type,
    last_seen
FROM geo_device_status
WHERE
    (fence_type = 'restricted' AND is_inside = true)
    OR (fence_type = 'depot'   AND is_inside = false);

Two rules are encoded here:

  • Any device inside a restricted zone triggers UNAUTHORIZED_ENTRY
  • Any device outside a depot zone triggers LEFT_DEPOT

The WHERE clause filters to only rows that satisfy at least one rule. Devices in acceptable positions produce no rows in this view.

Query the current violations:

SELECT device_id, device_name, fence_name, fence_type,
       distance_to_center_m, alert_type, last_seen
FROM geo_fence_violations
ORDER BY alert_type, device_id;

Verified on RisingWave 2.8.0:

 device_id |     device_name     |     fence_name      | fence_type | distance_to_center_m |     alert_type     |         last_seen
-----------+---------------------+---------------------+------------+----------------------+--------------------+---------------------------
 drn-001   | Inspection Drone 1  | Main Depot          | depot      |               3208.0 | LEFT_DEPOT         | 2026-04-01 10:06:00+00:00
 veh-002   | Delivery Truck Beta | Main Depot          | depot      |               1399.9 | LEFT_DEPOT         | 2026-04-01 08:40:00+00:00
 veh-003   | Service Van Gamma   | Main Depot          | depot      |               1900.8 | LEFT_DEPOT         | 2026-04-01 08:10:00+00:00

The three LEFT_DEPOT alerts are legitimate: those vehicles have left the depot to make deliveries or complete service calls. veh-001 (Delivery Truck Alpha) is back at the depot (distance 0.0m) so it produces no alert. Earlier in the day, veh-001 entered the Downtown Restricted zone and generated an UNAUTHORIZED_ENTRY alert, which automatically cleared when the vehicle returned to the depot.

Watching Alerts Clear in Real Time

The materialized view chain updates incrementally with every new GPS event. Simulating veh-001 returning to the depot requires a single INSERT:

-- veh-001 returns to the Main Depot (lat/lon matches depot center exactly)
-- Insert one row per fence for the enriched table (fan-out at source)
INSERT INTO geo_gps_events_enriched VALUES
    (21, 'veh-001', 1, 37.7749, -122.4194, 0.0, 0.0, '2026-04-01 10:00:00+00'),
    (21, 'veh-001', 2, 37.7749, -122.4194, 0.0, 0.0, '2026-04-01 10:00:00+00'),
    (21, 'veh-001', 3, 37.7749, -122.4194, 0.0, 0.0, '2026-04-01 10:00:00+00'),
    (21, 'veh-001', 4, 37.7749, -122.4194, 0.0, 0.0, '2026-04-01 10:00:00+00');

Within seconds, the materialized view chain propagates the update automatically:

SELECT device_id, fence_name, fence_type, distance_to_center_m, is_inside, last_seen
FROM geo_device_status
WHERE device_id = 'veh-001'
ORDER BY fence_id;
 device_id |     fence_name      |  fence_type   | distance_to_center_m | is_inside |         last_seen
-----------+---------------------+---------------+----------------------+-----------+---------------------------
 veh-001   | Main Depot          | depot         |                  0.0 |         t | 2026-04-01 10:00:00+00:00
 veh-001   | Downtown Restricted | restricted    |               1659.6 |         f | 2026-04-01 10:00:00+00:00
 veh-001   | Zone A Delivery     | delivery_zone |               1290.1 |         f | 2026-04-01 10:00:00+00:00
 veh-001   | Corporate Campus    | campus        |               3208.0 |         f | 2026-04-01 10:00:00+00:00

veh-001 now shows INSIDE the Main Depot. The geo_fence_violations view reflects this immediately: no row for veh-001 appears in the violations output. The UNAUTHORIZED_ENTRY alert from its earlier excursion into the restricted zone is also gone. Three propagation steps (distance MV, status MV, violations MV) all updated from a single INSERT, with no manual refresh needed.

Fleet Dashboard: Latest Positions

For the live dashboard layer, a separate materialized view tracks the single most recent GPS reading per device, independent of geofence evaluation:

CREATE MATERIALIZED VIEW geo_latest_positions AS
SELECT DISTINCT ON (device_id)
    device_id,
    latitude,
    longitude,
    speed_kmh,
    heading,
    event_ts
FROM geo_gps_events
ORDER BY device_id, event_ts DESC;

This view is the data source for map-based dashboards showing where every device is right now. Query it with any PostgreSQL-compatible client (Grafana, Metabase, Superset, or a direct psql connection) because RisingWave is wire-compatible with the PostgreSQL protocol.

SELECT device_id, latitude, longitude, speed_kmh, event_ts
FROM geo_latest_positions ORDER BY device_id;

Verified on RisingWave 2.8.0:

 device_id | latitude  | longitude  | speed_kmh |         event_ts
-----------+-----------+------------+-----------+---------------------------
 ast-001   |  37.7755  | -122.4195  |         2 | 2026-04-01 11:00:00+00:00
 drn-001   |  37.7920  | -122.3900  |         0 | 2026-04-01 10:06:00+00:00
 veh-001   |  37.7749  | -122.4194  |         0 | 2026-04-01 10:00:00+00:00
 veh-002   |  37.7672  | -122.4320  |         0 | 2026-04-01 08:40:00+00:00
 veh-003   |  37.7600  | -122.4300  |        60 | 2026-04-01 08:10:00+00:00

veh-003 is still moving (60 km/h) outside all geofences. drn-001 has landed at the Corporate Campus center. veh-001 is parked at the depot.

Geofence Occupancy Summary

A summary materialized view provides instant headcounts per geofence, useful for operations dashboards:

CREATE MATERIALIZED VIEW geo_geofence_summary AS
SELECT
    fence_id,
    fence_name,
    fence_type,
    radius_meters,
    COUNT(*) FILTER (WHERE is_inside = true)  AS devices_inside,
    COUNT(*) FILTER (WHERE is_inside = false) AS devices_outside,
    COUNT(*) AS total_devices_tracked
FROM geo_device_status
GROUP BY fence_id, fence_name, fence_type, radius_meters;
SELECT fence_name, fence_type, radius_meters,
       devices_inside, devices_outside, total_devices_tracked
FROM geo_geofence_summary
ORDER BY fence_id;

Verified on RisingWave 2.8.0:

     fence_name      |  fence_type   | radius_meters | devices_inside | devices_outside | total_devices_tracked
---------------------+---------------+---------------+----------------+-----------------+-----------------------
 Main Depot          | depot         |           500 |              1 |               4 |                     5
 Downtown Restricted | restricted    |           300 |              0 |               5 |                     5
 Zone A Delivery     | delivery_zone |           400 |              1 |               4 |                     5
 Corporate Campus    | campus        |           600 |              1 |               4 |                     5

One device at the depot (veh-001 just returned), one making deliveries in Zone A (veh-002), one at the campus (drn-001). The Downtown Restricted zone shows zero devices inside, confirming the fleet is currently compliant.

Routing Alerts to External Systems

Detecting a violation in a materialized view is only half the story. To send a notification when geo_fence_violations produces a new row, use a RisingWave Kafka sink:

CREATE SINK geo_violation_alerts FROM geo_fence_violations
WITH (
    connector = 'kafka',
    properties.bootstrap.server = 'broker:9092',
    topic = 'geofence-alerts'
) FORMAT PLAIN ENCODE JSON;

Each new row in geo_fence_violations becomes a Kafka message on the geofence-alerts topic. Downstream consumers (a PagerDuty integration, a Slack webhook, a mobile push notification service) subscribe to that topic and act on the alert JSON. When a violation clears (the device moves back into compliance), RisingWave retracts the row, and a retraction message flows to Kafka so downstream consumers can close the alert.

For sinks where retraction is not supported natively, query the violations view periodically from your alert service and compare against the previous state to detect clearances.

Comparing Geofencing Approaches

ApproachAlert LatencySQL InterfaceState ManagementOperational Cost
Scheduled batch query15-60 seconds (poll interval)YesNone (stateless)Low
Custom Kafka Streams appSub-secondNo (Java/Scala API)Manual (RocksDB)High
Apache Flink SQL1-5 secondsYes (limited)Managed by FlinkHigh (cluster, JVM)
RisingWave streaming SQLSub-secondYes (PostgreSQL-compatible)Automatic (materialized views)Low (single service)

The batch approach fails whenever alert latency matters: a restricted zone entry that persists for 29 seconds before the next poll fires is a compliance failure with an audit trail. Custom Kafka Streams code gives low latency but requires building and maintaining distance computation logic in Java, writing your own state stores, and tuning JVM heap sizes. Apache Flink SQL covers many use cases but introduces cluster complexity that is disproportionate for teams that primarily work in SQL.

RisingWave fits the middle: sub-second latency with pure SQL, no JVM, and PostgreSQL compatibility for querying results from any existing BI tool. See the detailed comparison of RisingWave and Apache Flink for benchmark data across 22 Nexmark queries.

Production Considerations

GPS update frequency. Vehicles transmitting every 5 seconds generate 720 events per hour per device. At 1,000 devices, that is 720,000 events per hour, well within RisingWave's throughput range. Materialized views process updates incrementally, so high event frequency does not cause full re-scans.

Haversine vs. PostGIS. The Haversine formula works well for circular geofences (radii under 100 km) and requires no spatial extensions. For polygon-shaped geofences or line-crossing detection, you would integrate a preprocessing layer that determines the relevant fence_id before events enter RisingWave. Point-in-polygon checks run at ingestion time in the producer or enrichment layer.

Fence boundary buffer zones. GPS accuracy varies by device (consumer GPS: 3-5m accuracy, commercial: sub-meter). Adding a buffer zone of 10-20 meters around geofence boundaries prevents rapid oscillation between INSIDE and OUTSIDE states as a device sits near the edge. Encode the buffer by adjusting the comparison: distance_to_center_m <= radius_meters - 10 for the INSIDE condition and distance_to_center_m > radius_meters + 10 for the OUTSIDE condition.

Scaling fan-out. For large geofence sets (thousands of zones), pre-filtering events at the producer is important. Instead of fanning out every GPS event to every fence, compute a coarse geohash or grid cell for the device position and only match fences whose center falls in the same or adjacent cells. This reduces the fan-out ratio dramatically.

Exact-once semantics. RisingWave uses a checkpointing mechanism that provides exactly-once processing guarantees for Kafka sources and sinks. GPS events processed through a RisingWave pipeline are neither missed nor double-counted even if the system restarts mid-stream.

FAQ

What is real-time geofencing location tracking with SQL?

Real-time geofencing location tracking with SQL is the practice of evaluating GPS coordinates against virtual geographic boundaries (geofences) as each location update arrives, using SQL queries that run continuously on a streaming database. Instead of polling a traditional database on a schedule, a streaming database like RisingWave processes each GPS ping within milliseconds and maintains always-current materialized views of device positions, boundary violations, and alert states.

How does the Haversine formula work for geofencing in SQL?

The Haversine formula computes the great-circle distance between two latitude/longitude points on Earth's surface. In SQL, it uses RADIANS() to convert degrees to radians, then applies SIN(), COS(), and ASIN() to calculate the angular separation, scaled by Earth's radius (6,371,000 meters). A device is inside a circular geofence when this computed distance is less than or equal to the geofence radius. The formula handles Earth's curvature correctly for distances up to several hundred kilometers, making it suitable for fleet tracking, delivery zones, and asset monitoring.

Can RisingWave detect entry and exit events, or only current state?

RisingWave materialized views track current state (is the device inside or outside right now). For explicit entry/exit event detection (generate one alert on transition, not on every GPS ping), chain the status view into a second view that compares the current is_inside value to the previous one using window functions or lag patterns. Alternatively, use the Kafka sink with a downstream consumer that tracks the previous state per device-fence pair and fires an event only when the state changes.

How many geofences and devices can RisingWave handle?

RisingWave scales horizontally by adding compute nodes to the cluster. The GPS enrichment pattern (one row per event per fence) is the primary scaling lever: 1,000 devices transmitting every 5 seconds against 50 geofences produces 10,000 events per second, well within single-node throughput. For very large geofence sets, pre-filtering at the producer (using geohash proximity checks) reduces fan-out before events reach RisingWave. See the RisingWave architecture overview for details on horizontal scaling and disaggregated storage.

Conclusion

Real-time geofencing does not require a custom stream processing application. With RisingWave, the full pipeline from GPS ingestion to alert delivery is expressed in standard SQL:

  • Ingest GPS events from Kafka with CREATE TABLE ... WITH (connector = 'kafka', ...), no consumer code needed
  • Compute distances using the Haversine formula directly in a materialized view, updated incrementally per GPS ping
  • Track current status with DISTINCT ON materialized views that always reflect the latest device-fence relationship
  • Generate alerts by filtering the status view for violations, with results available in milliseconds
  • Route notifications using a Kafka sink that streams violations to PagerDuty, Slack, or any downstream consumer
  • Serve dashboards from the latest positions view via any PostgreSQL-compatible tool

All SQL in this article is verified against RisingWave 2.8.0 and runs without modification on any RisingWave deployment.


Ready to build your own geofencing system? Get started in minutes. Try RisingWave Cloud free or run it locally with a single Docker command.

Join our Slack community to connect with engineers building real-time location and logistics systems.

Best-in-Class Event Streaming
for Agents, Apps, and Analytics
GitHubXLinkedInSlackYouTube
Sign up for our to stay updated.