GanosBase includes a MapMatching engine that snaps GPS trajectories to the nearest roads in a road network. This guide walks you through the full workflow: install the extension, load road network data, build a workspace, and run your first map matching query.
Prerequisites
Before you begin, make sure you have:
-
A running PolarDB for Oracle instance with GanosBase available
-
Database superuser or schema-level CREATE privileges
Key terms
| Term | Definition |
|---|---|
| Road network | A comprehensive and organized digital representation of the structure and characteristics of a road network, including the locations, types, classifications, connections, geometric shapes, and traffic regulations of roads. |
| Node | A point at which roads intersect or end in a road network. A node is a connecting point between edges. |
| Edge | An abstract concept that connects two nodes in a road network. In an undirected graph, edges have no direction. In a directed graph, edges represent the travel direction. Edges carry attribute information such as length and passability, and form the structural framework of a road network. |
| Segment | A section of a road with physical attributes and geographic location information. A segment is a continuous section of road that may span from one intersection to the next, or cover a section with uniform characteristics such as lane count and speed limit. Segments are the basic units in navigation, transportation analysis, and road maintenance planning. |
Install the extension
Run the following commands to install the ganos_mapmatching extension and its required companion ganos_fdw:
CREATE EXTENSION ganos_mapmatching CASCADE;
CREATE EXTENSION ganos_fdw CASCADE;
Installing ganos_mapmatching automatically installs three additional extensions: ganos_geometry, ganos_spatialref, and ganos_trajectory.
If you see ERROR: invalid extension name: "ganos_trajectory", contact Alibaba Cloud support for assistance.
To avoid permission issues, install the extensions in the public schema:
CREATE EXTENSION ganos_mapmatching WITH SCHEMA PUBLIC CASCADE;
CREATE EXTENSION ganos_fdw WITH SCHEMA PUBLIC CASCADE;
How it works
Map matching requires three inputs:
-
A road network — a set of road segments, each with a unique ID, a start node, an end node, and a geometry.
-
A workspace — an in-memory topological network graph built from the road network, used for matching queries.
-
A trajectory — a LineString geometry representing raw GPS points.
The st_createworkspace function builds the workspace from your road network data. The st_mapmatching function then matches a trajectory to the closest roads in that workspace and returns a snapped LineString.
Required columns for st_createworkspace:
| Column | Type | Description |
|---|---|---|
fid |
bigint | Unique segment ID |
source |
bigint | Start node ID |
target |
bigint | End node ID |
| geometry column | geometry(LineString) | Segment geometry (WGS84, SRID 4326) |
The cost column is not used by the map matching algorithm. You can include it in your table for future use, but it does not affect matching results.
Example 1: Match a trajectory against your own road network
Step 1: Create and load road network data
Create a table with the required schema. Each row represents one road segment — a directed connection between two nodes:
CREATE TABLE network (
fid bigint, -- Segment ID (unique identifier)
source bigint, -- Start node ID
target bigint, -- End node ID
cost double precision, -- Edge weight (reserved; not used by map matching)
geom public.geometry(LineString,4326) -- Segment geometry (WGS84)
);
Insert the road segment data:
INSERT INTO network(fid, source, target, cost, geom) VALUES
(1, 1, 2, 1, st_geomfromtext('LINESTRING(2 1,2 0)')),
(2, 2, 1, 1, st_geomfromtext('LINESTRING(2 0,2 1)')),
(3, 3, 1, 1, st_geomfromtext('LINESTRING(3 1,2 1)')),
(4, 4, 3, 1, st_geomfromtext('LINESTRING(4 1,3 1)')),
(5, 1, 5, 1, st_geomfromtext('LINESTRING(2 1,2 2)')),
(6, 5, 1, 1, st_geomfromtext('LINESTRING(2 2,2 1)')),
(7, 3, 6, 1, st_geomfromtext('LINESTRING(3 1,3 2)')),
(8, 7, 8, 1, st_geomfromtext('LINESTRING(0 2,1 2)')),
(9, 8, 7, 1, st_geomfromtext('LINESTRING(1 2,0 2)')),
(10, 5, 8, 3, st_geomfromtext('LINESTRING(2 2,1 2)')),
(11, 8, 5, 3, st_geomfromtext('LINESTRING(1 2,2 2)')),
(12, 6, 5, 1, st_geomfromtext('LINESTRING(3 2,2 2)')),
(13, 5, 6, 1, st_geomfromtext('LINESTRING(2 2,3 2)')),
(14, 6, 9, 1, st_geomfromtext('LINESTRING(3 2,4 2)')),
(15, 9, 6, 1, st_geomfromtext('LINESTRING(4 2,3 2)')),
(16, 10, 5, 1, st_geomfromtext('LINESTRING(2 3,2 2)')),
(17, 5, 10, 1, st_geomfromtext('LINESTRING(2 2,2 3)')),
(18, 6, 11, 1, st_geomfromtext('LINESTRING(3 2,3 3)')),
(19, 10, 11, 1, st_geomfromtext('LINESTRING(2 3,3 3)')),
(20, 11, 12, 1, st_geomfromtext('LINESTRING(3 3,4 3)')),
(21, 13, 10, 1, st_geomfromtext('LINESTRING(2 4,2 3)')),
(22, 10, 13, 1, st_geomfromtext('LINESTRING(2 3,2 4)')),
(23, 9, 12, 1, st_geomfromtext('LINESTRING(4 2,4 3)')),
(24, 12, 9, 1, st_geomfromtext('LINESTRING(4 3,4 2)')),
(25, 9, 4, 1, st_geomfromtext('LINESTRING(4 2,4 1)')),
(26, 4, 9, 1, st_geomfromtext('LINESTRING(4 1,4 2)')),
(27, 14, 15, 1, st_geomfromtext('LINESTRING(0.5 3.5,2 3.5)')),
(28, 15, 14, 1, st_geomfromtext('LINESTRING(2 3.5,0.5 3.5)')),
(29, 16, 17, 1, st_geomfromtext('LINESTRING(3.5 4,3.5 2.3)')),
(30, 17, 16, 1, st_geomfromtext('LINESTRING(3.5 2.3,3.5 4)'));
The road network looks like this:
Step 2: Build a map matching workspace
Build a workspace named mm_ws_test1 from the road network. The SELECT statement passed to st_createworkspace must include fid, source, target, and the geometry column:
SELECT st_createworkspace('mm_ws_test1', 'select fid, source, target, geom from network');
This generates the topological network graph used for matching:
Step 3: Create trajectory data
Create a table and insert the GPS trajectory you want to match. The trajectory is a LineString in WGS84 (SRID 4326):
CREATE TABLE traj (id int, geom geometry(linestring, 4326));
INSERT INTO traj(id, geom)
VALUES (1, st_geomfromtext('LINESTRING(4.15 1.6,3.47 0.92,2.4 0.92,2.14 1.53,2.14 2.57,2.49 2.98)'));
The raw trajectory looks like this:
Step 4: Run map matching
Match the trajectory against the road network:
SELECT st_astext(
st_mapmatching(
'mm_ws_test1',
geom,
'{"algorithm":"stmatch","k":4,"r":0.25,"e":0.5}'::text
)
) AS geom
FROM traj;
Result:
geom
-----------------------------------------------
LINESTRING(4 1.6,4 1,3 1,2 1,2 2,2 3,2.49 3)
(1 row)
The output is a LineString snapped to the road network. Each coordinate in the result aligns to a road segment in the workspace. The matched path is visualized below:
Example 2: Match a trajectory using OpenStreetMap (OSM) data
Use this approach when you have real-world road network data from OpenStreetMap (OSM) instead of a custom dataset.
Step 1: Import OSM data from Object Storage Service (OSS)
OSM data must be imported from OSS. Download the sample OSM file for Macau (China) and upload it to an OSS bucket, then run:
-- Register the OSM file as a foreign table.
-- Replace the placeholders with your OSS AccessKey ID, AccessKey secret, endpoint, bucket, and directory.
SELECT st_regforeigntables(
'OSS://{accesskeyid}:{accesskeysecret}@{endpoint}/{bucket}/{directory}/macau-latest.osm.pbf',
'osmserver',
'OSMNT'
);
-- Set the OSS endpoint for data access.
SELECT st_setcplconfig('OSS_ENDPOINT', '{endpoint}');
Extract node data (intersections) from the OSM file into a local table:
CREATE TABLE macau_network_nodes (
id BIGSERIAL,
fid BIGINT,
eout INTEGER,
lon DOUBLE PRECISION,
lat DOUBLE PRECISION,
cnt INTEGER,
chk INTEGER,
ein INTEGER,
name TEXT,
tags TEXT,
point GEOMETRY(point, 4326)
);
INSERT INTO macau_network_nodes(fid, lon, lat, name, tags, point)
SELECT osm_id::bigint, ST_X(geom), ST_Y(geom), name, to_json(other_tags)::jsonb, geom
FROM points;
Extract road segment (way) data into a separate table:
CREATE TABLE macau_network_ways (
gid BIGSERIAL,
fid BIGINT,
length DOUBLE PRECISION,
length_m DOUBLE PRECISION,
p TEXT,
source BIGINT,
target BIGINT,
source_osm BIGINT,
target_osm BIGINT,
cost DOUBLE PRECISION,
reverse_cost DOUBLE PRECISION DEFAULT -1,
cost_s DOUBLE PRECISION,
reverse_cost_s DOUBLE PRECISION DEFAULT -1,
rule TEXT,
one_way INT,
oneway TEXT,
x1 DOUBLE PRECISION,
y1 DOUBLE PRECISION,
x2 DOUBLE PRECISION,
y2 DOUBLE PRECISION,
maxspeed_forward DOUBLE PRECISION,
maxspeed_backward DOUBLE PRECISION DEFAULT -1,
priority DOUBLE PRECISION DEFAULT 1,
name TEXT,
tags JSONB,
linestring GEOMETRY(LINESTRING, 4326)
);
INSERT INTO macau_network_ways(fid, source_osm, target_osm, name, tags, linestring)
SELECT osm_id::bigint, source_osm, target_osm, name, to_json(other_tags)::jsonb, geom
FROM lines;
Step 2: Build the road network topology
Link each road segment to its start and end nodes by updating the source and target columns:
UPDATE macau_network_ways
SET source = macau_network_nodes.id
FROM macau_network_nodes
WHERE macau_network_ways.source_osm = macau_network_nodes.fid;
UPDATE macau_network_ways
SET target = macau_network_nodes.id
FROM macau_network_nodes
WHERE macau_network_ways.target_osm = macau_network_nodes.fid;
The resulting road network for Macau (China) looks like this:
Step 3: Build a map matching workspace
SELECT st_createworkspace(
'mm_ws_test2',
'select fid, source, target, linestring from macau_network_ways'
);
Step 4: Run map matching
Match a GPS trajectory against the Macau (China) road network. This example uses additional parameters to improve matching accuracy for real-world traces:
SELECT st_astext(
st_mapmatching(
'mm_ws_test2',
'LINESTRING(113.5492 22.1881,113.5493 22.1881,113.5504 22.1885,113.5505 22.1886,113.5502 22.189,113.5501 22.1891,113.5491 22.1887,113.5489 22.1887)',
'{"algorithm":"stmatch","k":4,"r":0.25,"e":0.5,"tolerance":0.5,"vmax":1.0,"factor":0.5}'::text
)
);
Result:
LINESTRING(113.5492633 22.1880942,113.5493491 22.1881266,113.5504129 22.1885289,113.5505378 22.1885762,113.5502504 22.1892274,113.5505378 22.1885762,113.5502504 22.1892274,113.5501282 22.1891812,113.5490843 22.1887864,113.5489761 22.1887455)
The output coordinates are snapped to the actual road geometries in the workspace:
st_mapmatching parameters
All examples use the stmatch algorithm. The JSON options string controls matching behavior:
| Parameter | Type | Description | Example value |
|---|---|---|---|
algorithm |
string | Matching algorithm. Currently only stmatch is supported. |
"stmatch" |
k |
integer | Number of candidate road segments considered for each GPS point. Higher values improve accuracy on dense road networks but increase computation time. | 4 |
r |
float | Search radius in the coordinate unit of your data. Only road segments within this radius are candidates for each GPS point. Increase this value for noisy or sparse traces. | 0.25 |
e |
float | GPS measurement error tolerance. Controls how strictly the algorithm filters out outlier points. | 0.5 |
tolerance |
float | Maximum allowed deviation between a raw GPS point and its matched position. Points that exceed this threshold may be skipped. Use with real-world traces. | 0.5 |
vmax |
float | Maximum speed used to validate transitions between matched road segments. Use with real-world traces. | 1.0 |
factor |
float | Weighting factor that balances spatial distance and transition probability in the matching score. Use with real-world traces. | 0.5 |
Tuning tips:
-
Noisy GPS traces: Increase
r(search radius) ande(error tolerance) to allow the algorithm to consider more candidates and tolerate larger measurement errors. -
Dense road networks: Increase
kto evaluate more candidate segments per GPS point, which improves accuracy but increases computation time. -
Real-world traces: Add
tolerance,vmax, andfactor(as shown in Example 2) to handle the variability typical of real GPS data. -
Clean synthetic traces: Use smaller values for
rande(as shown in Example 1) when your data is generated from precise coordinates.