This guide walks you through a complete map matching workflow using GanosBase in PolarDB for PostgreSQL: install the extension, load road network data, build a topology workspace, and match a trajectory to the road network.
Key concepts
Road network: a structured digital representation of roads, including their locations, types, connections, geometric shapes, and traffic rules.
Node: a point where roads intersect or terminate. Nodes are the connection points between edges.
Edge: an abstract link between two nodes. In a directed graph, an edge has a travel direction. In an undirected graph, it does not. Edges carry attributes such as length and passability, but their primary role is to define the structural framework of a road network.
Segment: a physical road section with geographic attributes — road surface, width, slope, lane configuration, speed limit, and traffic rules. Segments are the basic units in navigation, transportation analysis, and road maintenance.
Prerequisites
Before you begin, make sure you have:
A PolarDB for PostgreSQL cluster with GanosBase enabled
Database superuser privileges (required to create extensions)
(For Example 2) An Object Storage Service (OSS) bucket containing the OSM data file
Install the extension
Run the following commands in your database to install the ganos_mapmatching and ganos_fdw extensions:
CREATE EXTENSION ganos_mapmatching CASCADE;
CREATE EXTENSION ganos_fdw CASCADE;Installing ganos_mapmatching also installs the ganos_geometry, ganos_spatialref, and ganos_trajectory extensions automatically.
To avoid permission issues, install the extensions in thepublicschema:If you seeCREATE EXTENSION ganos_mapmatching WITH SCHEMA PUBLIC CASCADE; CREATE EXTENSION ganos_fdw WITH SCHEMA PUBLIC CASCADE;ERROR: invalid extension name: "ganos_trajectory", contact Alibaba Cloud support.
Example 1: Match trajectories using your own road network data
This example uses a synthetic road network to walk through the full workflow: load data, build a workspace, and match a trajectory.
Step 1: Load road network data
Create the network table and insert road segment data. Each row defines one directed edge with a start node (source), an end node (target), a cost weight, and a LineString geometry.
CREATE TABLE network (
fid bigint, -- Unique segment identifier
source bigint, -- Start node ID
target bigint, -- End node ID
cost double precision, -- Edge weight (not currently used)
geom public.geometry(LineString, 4326) -- Segment geometry
);
INSERT INTO network(fid, source, target, cost, geom)
VALUES(1, 1, 2, 1, st_geomfromtext('LINESTRING(2 1,2 0)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(2, 2, 1, 1, st_geomfromtext('LINESTRING(2 0,2 1)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(3, 3, 1, 1, st_geomfromtext('LINESTRING(3 1,2 1)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(4, 4, 3, 1, st_geomfromtext('LINESTRING(4 1,3 1)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(5, 1, 5, 1, st_geomfromtext('LINESTRING(2 1,2 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(6, 5, 1, 1, st_geomfromtext('LINESTRING(2 2,2 1)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(7, 3, 6, 1, st_geomfromtext('LINESTRING(3 1,3 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(8, 7, 8, 1, st_geomfromtext('LINESTRING(0 2,1 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(9, 8, 7, 1, st_geomfromtext('LINESTRING(1 2,0 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(10, 5, 8, 3, st_geomfromtext('LINESTRING(2 2,1 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(11, 8, 5, 3, st_geomfromtext('LINESTRING(1 2,2 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(12, 6, 5, 1, st_geomfromtext('LINESTRING(3 2,2 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(13, 5, 6, 1, st_geomfromtext('LINESTRING(2 2,3 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(14, 6, 9, 1, st_geomfromtext('LINESTRING(3 2,4 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(15, 9, 6, 1, st_geomfromtext('LINESTRING(4 2,3 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(16, 10, 5, 1, st_geomfromtext('LINESTRING(2 3,2 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(17, 5, 10, 1, st_geomfromtext('LINESTRING(2 2,2 3)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(18, 6, 11, 1, st_geomfromtext('LINESTRING(3 2,3 3)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(19, 10, 11, 1, st_geomfromtext('LINESTRING(2 3,3 3)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(20, 11, 12, 1, st_geomfromtext('LINESTRING(3 3,4 3)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(21, 13, 10, 1, st_geomfromtext('LINESTRING(2 4,2 3)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(22, 10, 13, 1, st_geomfromtext('LINESTRING(2 3,2 4)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(23, 9, 12, 1, st_geomfromtext('LINESTRING(4 2,4 3)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(24, 12, 9, 1, st_geomfromtext('LINESTRING(4 3,4 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(25, 9, 4, 1, st_geomfromtext('LINESTRING(4 2,4 1)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(26, 4, 9, 1, st_geomfromtext('LINESTRING(4 1,4 2)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(27, 14, 15, 1, st_geomfromtext('LINESTRING(0.5 3.5,2 3.5)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(28, 15, 14, 1, st_geomfromtext('LINESTRING(2 3.5,0.5 3.5)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(29, 16, 17, 1, st_geomfromtext('LINESTRING(3.5 4,3.5 2.3)'));
INSERT INTO network(fid, source, target, cost, geom)
VALUES(30, 17, 16, 1, st_geomfromtext('LINESTRING(3.5 2.3,3.5 4)'));The resulting road network looks like this:

Step 2: Build a map matching workspace
st_createworkspace builds the topological network graph from your road network data and stores it in a named workspace. The second argument is a SQL query that must return fid, source, target, and geom.
SELECT st_createworkspace('mm_ws_test1', 'select fid, source, target, geom from network');The topology generated from the road network:

Step 3: Create trajectory data
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 input trajectory (GPS points to be matched):

Step 4: Run map matching
st_mapmatching snaps the trajectory to the road network using the ST-Matching algorithm. The third argument is a JSON object specifying algorithm parameters.
SELECT st_astext(
st_mapmatching(
'mm_ws_test1',
geom,
'{"algorithm":"stmatch","k":4,"r":0.25,"e":0.5}'::text
)
) AS geom
FROM traj;Algorithm parameters:
| Parameter | Description | Value in this example |
|---|---|---|
algorithm | Matching algorithm. Use stmatch for the ST-Matching algorithm. | stmatch |
k | Maximum number of road segment candidates to consider for each GPS point. Higher values improve accuracy but increase processing time. | 4 |
r | Search radius in degrees. GPS points outside this radius from a road segment are excluded from candidates. Increase this value if your GPS data has low accuracy. | 0.25 |
e | Hidden Markov Model (HMM) observation error threshold. Controls how much deviation from the road is tolerated. | 0.5 |
Expected output:
geom
----------------------------------------------
LINESTRING(4 1.6,4 1,3 1,2 1,2 2,2 3,2.49 3)
(1 row)The matched trajectory snapped to the road network:

If the result is empty, check that the trajectory coordinates fall within the coverage area of your road network. If no road segment is within the search radius r of any GPS point, the function returns no match.Example 2: Match trajectories using OpenStreetMap data
This example imports real-world road data from OpenStreetMap (OSM) for Macao (China) and runs map matching against it. Steps 1–3 prepare the data; Steps 4–5 follow the same workspace-and-match pattern as Example 1.
Step 1: Import OSM data from OSS
OSM data must be imported from an OSS bucket. Download the sample OSM file and upload it to your bucket first.
Register the OSM file as a foreign table and configure the OSS endpoint:
-- Register the OSM file as a foreign table.
-- Replace the placeholders with your AccessKey pair, endpoint, bucket name, and folder path.
-- The directory value should be the folder path without a leading slash.
SELECT st_regforeigntables(
'OSS://<accesskeyid>:<accesskeysecret>@<endpoint>/<bucket>/<directory>/macau-latest.osm.pbf',
'osmserver',
'OSMNT'
);
SELECT st_setcplconfig('OSS_ENDPOINT', '<endpoint>');Replace the following placeholders:
| Placeholder | Description |
|---|---|
<accesskeyid> | Your Alibaba Cloud AccessKey ID |
<accesskeysecret> | Your Alibaba Cloud AccessKey secret |
<endpoint> | The OSS endpoint for the region where your bucket is located |
<bucket> | Your OSS bucket name |
<directory> | The folder path within the bucket (omit the leading /) |
Step 2: Extract road network node data
Create the nodes table and populate it from the OSM foreign 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;Step 3: Extract road network edge data and resolve node references
Create the edges table, populate it from the OSM foreign table, and then link each edge to the node IDs in macau_network_nodes:
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;
-- Resolve OSM node references to internal node IDs.
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 resolved road network:

Step 4: Build a map matching workspace
SELECT st_createworkspace(
'mm_ws_test2',
'select fid, source, target, linestring from macau_network_ways'
);Step 5: Run map matching
Pass the trajectory as an inline WKT string instead of a table reference:
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
)
);Expected output:
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 matched trajectory on the Macao (China) road network:

What's next
Review the full
st_mapmatchingparameter reference to tune matching accuracy for your GPS data quality.Explore the GanosBase trajectory functions (
ganos_trajectory) for additional spatial analysis on your matched routes.