GanosBase extends Apache Flink with spatial computing functions and in-memory spatial indexes, enabling real-time geofence detection at scale. This tutorial walks through a complete implementation: loading fence polygons into PolarDB, registering them as a Flink dimension table, and streaming point-in-polygon joins at 100,000 events per second (simulation rate).
What is a geofence
A geofence is a virtual geographic boundary defined by polygons or polylines. When a device crosses the boundary — detected through GPS, RFID, Wi-Fi, or cellular data — an action is triggered. Fences can cover anything from a single building to an entire city or country.

Use cases
Geofences are applied across a wide range of domains:
Traffic and logistics
Monitor vehicles against designated routes. Alert when a vehicle strays off course or approaches a blind-spot intersection.
No-fly and no-drive zone management
Restrict drones from entering airspace near airports. Issue a warning as soon as a drone crosses the boundary.
Emergency management
During severe weather events such as typhoons, alert vehicles and ships when they enter the affected area.
Marketing and retail
Automatically deliver promotions — such as coupons — to customers the moment they enter a designated zone.
IoT and personal safety
Trigger an alarm if a child or elderly person under surveillance leaves a predefined area.
Law enforcement
Notify authorities when a monitored individual exits a permitted boundary.
Smart home automation
Automatically adjust the thermostat or turn on lights when a homeowner's phone enters the home's geofence.
Animal and asset tracking
Alert pet owners or farmers when animals move beyond established boundaries. Notify network administrators when company devices leave specified areas.
Workplace security
Alert the security team when an employee attempts to enter a restricted area with a smart card.
How it works
The solution connects three components: a streaming source, a spatial database, and a write-back store.
A streaming source (such as Kafka) continuously emits location events with an ID, longitude, and latitude.
GanosBase registers the geofence table stored in PolarDB as a Flink dimension table. The fence table is pre-stored in PolarDB and refreshed periodically. On each incoming event, Flink performs a lookup join: it queries PolarDB at processing time to find which fence polygon, if any, contains the event's coordinate.
Matching records are written back to a result table in PolarDB.
The key mechanism is the lookup join pattern: the fence table is queried from PolarDB at the moment each event arrives. GanosBase accelerates the point-in-polygon check with an in-memory spatial index, making the approach viable at high event rates.
Prerequisites
Before you begin, ensure that you have:
A PolarDB cluster (enterprise edition)
A Realtime Compute for Apache Flink instance in the same region and Virtual Private Cloud (VPC) as the PolarDB cluster
The Realtime Compute for Apache Flink workspace's CIDR block added to the PolarDB cluster whitelist. For details, see Set cluster whitelist
GanosBase spatial computing functions uploaded to the Realtime Compute for Apache Flink workspace. After upload, Flink automatically registers the functions and lists them in the function panel. To get the functions, contact us. For upload instructions, see Manage user-defined functions (UDF)
To purchase a Realtime Compute for Apache Flink instance, log in to the Realtime Compute console and follow the steps in Activate Realtime Compute for Apache Flink.
Set up the geofence table
The geofence table stores fence geometries as Geometry objects in PolarDB. Flink reads from this table at processing time to evaluate each incoming coordinate.
Create the table and populate it with sample data
The following SQL creates a table of 10,000 random polygon geofences. Each fence is generated by creating a random point and expanding it into a circle buffer with a radius of 0.01 degrees (approximately 1 km).
-- Helper: generate a random float in [low, high]
CREATE OR REPLACE FUNCTION test_random_float(low float, high float)
RETURNS float AS $$
BEGIN
RETURN random() * (high-low) + low;
END;
$$ LANGUAGE 'plpgsql' STRICT;
-- Helper: generate a random geometry point within a bounding box
CREATE OR REPLACE FUNCTION test_random_geogpoint(lowx float, highx float,
lowy float, highy float)
RETURNS geometry AS $$
BEGIN
RETURN ST_SetSRID(st_point(test_random_float(lowx, highx), test_random_float(lowy, highy)),4326);
END;
$$ LANGUAGE 'plpgsql' STRICT;
-- Geofence table
CREATE TABLE IF NOT EXISTS geofencings (
name VARCHAR(100) PRIMARY KEY,
geofencing Geometry
);
-- Insert 10,000 random geofences
INSERT INTO geofencings SELECT 'g_' || s, test_random_geogpoint(129, 130, 49, 50) FROM generate_series(1, 10000) s;
-- Expand each point into a polygon buffer (~1 km radius)
UPDATE geofencings SET geofencing = ST_Buffer(geofencing, 0.01);Register the geofence table as a Flink dimension table
This step uses the lookup join pattern: Flink queries the fence table from PolarDB at the processing time of each incoming event (FOR SYSTEM_TIME AS OF points.proctime).
Register the PolarDB geofencings table as a Flink dimension table using the polardb connector. The join.predicate = 'CONTAINS' option tells GanosBase to use the CONTAINS spatial predicate for the join: for each incoming point, Flink checks which fence polygon contains that coordinate.
CREATE TEMPORARY TABLE geofencings (
name STRING NOT NULL,
geofencing STRING NOT NULL,
PRIMARY KEY (name) NOT ENFORCED
) WITH (
'connector' = 'polardb',
'jdbcUrl' = 'jdbc:postgresql://<yourHostname>:<yourPort>/<dbname>',
'username' = '<yourUserName>',
'password' = '<yourPassWord>',
'query' = 'SELECT name, ST_AsText(geofencing) FROM geofencings;',
'join.columnName' = 'geofencing',
'join.predicate' = 'CONTAINS'
);Replace the following placeholders:
| Placeholder | Description | Example |
|---|---|---|
<yourHostname> | PolarDB cluster endpoint | pc-xxx.polardbpg.rds.aliyuncs.com |
<yourPort> | Port number | 1521 |
<dbname> | Database name | mydb |
<yourUserName> | Database user | admin |
<yourPassWord> | Database password | — |
Set up the result table
Matching records are written back to PolarDB. Create the result table and register it as a Flink sink.
Create the result table in PolarDB
CREATE TABLE IF NOT EXISTS point_in_geofencing (
id serial PRIMARY KEY, -- Primary key
point_id VARCHAR(100), -- Point ID
geofencing_name VARCHAR(100), -- Fence name
point Geometry -- Point geometry
);Register the result table as a Flink table
Records are inserted in batches of 100 for efficiency.
CREATE TEMPORARY TABLE point_in_geofencing (
point_id STRING NOT NULL,
geofencing_name STRING NOT NULL,
point STRING NOT NULL
) WITH (
'connector' = 'polardb',
'jdbcUrl' = 'jdbc:postgresql://<yourHostname>:<yourPort>/<dbname>',
'username' = '<yourUserName>',
'password' = '<yourPassWord>',
'query' = 'INSERT INTO point_in_geofencing(point_id,geofencing_name,point) values(?,?,ST_GeomFromText(?));',
'batchSize' = '100'
);Run the geofence computation
Simulate a data source
The following Flink SQL uses the datagen connector to simulate a location event stream at 100,000 records per second, with random coordinates inside the bounding box used to generate the fences (longitude 129–130, latitude 49–50).
Thisdatagensource is for testing only. In production, replace it with a Kafka connector using the same column schema (id,lng,lat,proctime).
CREATE TEMPORARY TABLE points (
id STRING NOT NULL,
lng DOUBLE NOT NULL, -- Longitude
lat DOUBLE NOT NULL, -- Latitude
proctime AS PROCTIME(), -- Processing-time attribute (required for lookup join)
PRIMARY KEY (id) NOT ENFORCED
) WITH (
'connector' = 'datagen',
'rows-per-second' = '100000',
'fields.lng.kind' = 'random',
'fields.lng.min' = '129',
'fields.lng.max' = '130',
'fields.lat.kind' = 'random',
'fields.lat.min' = '49',
'fields.lat.max' = '50',
'fields.id.kind' = 'random',
'fields.id.length' = '5'
);Run the join query
The query joins the incoming point stream against the geofence dimension table. FOR SYSTEM_TIME AS OF points.proctime is the standard Flink lookup join syntax: it queries PolarDB at the processing time of each event. GanosBase evaluates the CONTAINS predicate — equivalent to geofencings.geofencing CONTAINS ST_MakePoint(lng, lat) — using the in-memory spatial index, then writes matching records to the result table.
INSERT INTO point_in_geofencing
SELECT points.id, geofencings.name, ST_MakePoint(lng, lat)
FROM points
JOIN geofencings FOR SYSTEM_TIME AS OF points.proctime
ON geofencings.geofencing = ST_MakePoint(lng, lat);Performance considerations
The following results were measured on a PolarDB enterprise edition cluster with 2 cores and 4 GB of memory, running the 10,000-fence dataset at 100,000 events per second (simulation rate):
| Metric | Value |
|---|---|
| Database write TPS | ~300 transactions per second |
| Records written per second | ~30,000 |
| PolarDB CPU utilization | ~10% |
The cluster was operating well within capacity at this load, leaving headroom for additional workloads.
What's next
PolarDB free trial — try the cloud-native PolarDB PostgreSQL edition and explore GanosBase spatiotemporal computing capabilities