Trajectory model

更新时间:
复制 MD 格式

Ganos Trajectory is a spatio-temporal engine extension for PolarDB for PostgreSQL. It introduces the trajectory data type, which stores a moving object's complete path as a single compressed object rather than one row per sampling point. Because the trajectory object encodes the full timeline, GanosBase can extract sub-trajectories, measure path similarity, and apply spatial or temporal filters — all without complex aggregation across millions of rows.

Typical use cases include vehicle tracking, logistics routing, shared-mobility archiving, and user behavior analysis for recommendation systems.

Key concepts

Trajectory structure

A trajectory records the spatial positions of a moving object at successive time points. Each sampling point contributes a timestamp, a spatial coordinate, and optional attributes such as speed or direction.

For example, a moving object that reports its position three times generates the following data:

timexyspeed
2020-04-11 17:42:30114.3539.284.3
2020-04-11 17:43:30114.3639.284.8
2020-04-11 17:45:00114.3539.293.5

Ganos Trajectory aggregates all sampling points of one object into a single trajectory value. The key advantage over a row-per-point table is that GanosBase can perform object-level operations — intersection detection, sub-trajectory extraction, and similarity scoring — that would otherwise require complex aggregation queries.

image

Core features

GanosBase Trajectory supports the following features for trajectory data storage and analysis:

  • Trajectory construction: Build trajectories from data sources such as geometries, timestamps, arrays, and data tables.

  • Trajectory editing: Edit the time, geometries, attributes, and events of a trajectory. Supported operations include simplifying, splitting, and smoothing a trajectory, extracting sub-trajectories, and editing the spatio-temporal features of a trajectory.

  • Trajectory analysis: Identify spatial relationships and spatio-temporal relationships, analyze similarity, and extract features of trajectories.

  • Trajectory indexing: Create a spatio-temporal index to accelerate queries and analyses.

For more information, see Trajectory SQL reference.

Four components

A trajectory value has four components:

  • Time: the ordered timestamps of all sampling points, stored as start_time, end_time, and a timeline array.

  • Space: the spatial shape of the path, stored as a LINESTRING in WKT (Well-Known Text) format with an associated SRID.

  • Attributes: per-point attribute arrays (e.g., velocity, accuracy, bearing, acceleration), each with a defined type, byte length, and nullable flag.

  • Events: time-stamped events that are not tied to a single sampling point, stored as {type: timestamp} pairs where type is a user-defined integer.

Event attributes

Events record occurrences at specific moments that are independent of sampling frequency. They are stored as 2-tuples in {type: timestamp} format, where type is a user-defined integer and timestamp marks when the event occurred. Use events to represent state changes such as engine start, stop, or alarm triggers.

Storage methods

Three methods are available for storing trajectory data:

MethodStorage formatUpdate performanceQuery performanceStorage sizeAttribute support
Row-per-point tableOne row per sampling pointHighLowHighYes
Geometry (LINESTRING M)2D/3D geometry; M dimension stores timestampsModerateHigh (spatial)LowNo
Trajectory typeAggregated trajectory objectModerateHigh (spatio-temporal)LowYes

The trajectory type (Method 3) extends the geometry approach with attribute and event support, and adds spatio-temporal queries. Use it when you need both spatio-temporal queries and per-point metadata. Use Method 1 when write throughput is the priority and you do not need trajectory-level analysis.

When querying, convert between formats as needed. The SQL section below shows how to convert a row table to a trajectory and back.

Spatial reference system

Ganos uses an integer SRID (Spatial Reference ID) to associate a trajectory with a spatial reference system (SRS). The SRID is embedded in the trajectory value and follows the same conventions as PostGIS geometries. For supported SRIDs, see Spatial reference.

Index

Ganos supports the GiST (Generalized Search Tree) index for trajectory columns. GiST is a balanced tree index that accelerates spatial, temporal, and spatio-temporal filter queries. The following query patterns benefit from a trajgist index:

  • Spatial filter: WHERE st_3dintersects(traj, <geometry>)

  • Temporal filter: WHERE st_TIntersects(traj, <start_timestamp>, <end_timestamp>)

  • Spatio-temporal filter: WHERE st_3dintersects(traj, <geometry>, <start_timestamp>, <end_timestamp>)

Create a trajectory GiST index with the trajgist access method:

CREATE INDEX tr_index ON traj_table USING trajgist (traj);

Implementation standard

Ganos trajectory supports and extends the interfaces defined by the OGC Moving Features standard. Geometric attributes are based on the geometry model. Use ST_trajectorySpatial to extract the spatial component for further processing.

The trajectory_columns view

The trajectory_columns view lists all trajectory-type columns in the current database, read from the system catalog:

ColumnTypeDescription
t_table_catalogvarchar(256)Always postgres
t_table_schemavarchar(256)Schema of the table
t_table_namevarchar(256)Table name
t_trajectory_columnvarchar(256)Name of the trajectory column
SELECT * FROM trajectory_columns;

Quick start

This section walks through the full workflow: create the extension, build a trajectory table, insert data, create an index, run spatial and temporal queries, and measure trajectory similarity.

For more advanced usage, see the following Trajectory best practices articles:

Step 1: Create the extension

CREATE EXTENSION ganos_trajectory WITH SCHEMA public CASCADE;
Create the extension in the public schema to avoid permission issues.

Step 2: Create a trajectory table

CREATE TABLE traj_table (id integer, traj trajectory);

Step 3: Insert trajectory data

ST_MakeTrajectory accepts three timestamp formats: a tsrange, two individual timestamp values, or a timestamp[] array. All three forms produce equivalent trajectories.

INSERT INTO traj_table VALUES
  -- Using a tsrange
  (1, ST_MakeTrajectory('STPOINT'::leaftype,
      st_geomfromtext('LINESTRING (114 35, 115 36, 116 37)', 4326),
      '[2010-01-01 14:30, 2010-01-01 15:30)'::tsrange,
      '{"leafcount": 3,"attributes" : {"velocity" : {"type":"integer","length":4,"nullable":false,"value":[120, 130, 140]},"accuracy":{"type":"integer","length":4,"nullable":false,"value":[120, 130, 140]},"bearing":{"type":"float","length":4,"nullable":false,"value":[120, 130, 140]},"acceleration":{"type":"float","length":4,"nullable":false,"value":[120, 130, 140]}}}')),
  -- Using start and end timestamps
  (2, ST_MakeTrajectory('STPOINT'::leaftype,
      st_geomfromtext('LINESTRING (114 35, 115 36, 116 37)', 4326),
      '2010-01-01 14:30'::timestamp, '2010-01-01 15:30'::timestamp,
      '{"leafcount": 3,"attributes" : {"velocity" : {"type":"integer","length":4,"nullable":false,"value":[120, 130, 140]},"accuracy":{"type":"integer","length":4,"nullable":false,"value":[120, 130, 140]},"bearing":{"type":"float","length":4,"nullable":false,"value":[120, 130, 140]},"acceleration":{"type":"float","length":4,"nullable":false,"value":[120, 130, 140]}}}')),
  -- Using a timestamp array
  (3, ST_MakeTrajectory('STPOINT'::leaftype,
      st_geomfromtext('LINESTRING (114 35, 115 36, 116 37)', 4326),
      ARRAY['2010-01-01 14:30'::timestamp, '2010-01-01 15:00'::timestamp, '2010-01-01 15:30'::timestamp],
      '{"leafcount": 3,"attributes" : {"velocity" : {"type":"integer","length":4,"nullable":false,"value":[120, 130, 140]},"accuracy":{"type":"integer","length":4,"nullable":false,"value":[120, 130, 140]},"bearing":{"type":"float","length":4,"nullable":false,"value":[120, 130, 140]},"acceleration":{"type":"float","length":4,"nullable":false,"value":[120, 130, 140]}}}')),
  -- Without attributes
  (4, ST_MakeTrajectory('STPOINT'::leaftype,
      st_geomfromtext('LINESTRING (114 35, 115 36, 116 37)', 4326),
      '[2010-01-01 14:30, 2010-01-01 15:30)'::tsrange,
      null));

Step 4: Create a trajectory index

The trajgist index accelerates spatial, temporal, and spatio-temporal filter queries.

CREATE INDEX tr_index ON traj_table USING trajgist (traj);

Step 5: Run queries

Spatial filter — find trajectories that intersect a polygon:

SELECT id, traj FROM traj_table
WHERE st_3dintersects(traj, ST_GeomFromText(
  'POLYGON((116.46747851805917 39.92317964155052,
            116.4986540687358  39.92317964155052,
            116.4986540687358  39.94452401711516,
            116.46747851805917 39.94452401711516,
            116.46747851805917 39.92317964155052))'));

Temporal filter — find trajectories active during a time window:

SELECT id, traj FROM traj_table
WHERE st_TIntersects(traj,
  '2010-01-01 12:30:44'::timestamp,
  '2010-01-01 14:30:44'::timestamp);

Spatio-temporal filter — combine spatial and temporal constraints:

SELECT id, traj FROM traj_table
WHERE st_3dintersects(traj,
  ST_GeomFromText(
    'POLYGON((116.46747851805917 39.92317964155052,
              116.4986540687358  39.92317964155052,
              116.4986540687358  39.94452401711516,
              116.46747851805917 39.94452401711516,
              116.46747851805917 39.92317964155052))'),
  '2010-01-01 13:30:44'::timestamp,
  '2010-01-03 17:30:44'::timestamp);

Time range query — get the start and end time of each trajectory:

SELECT st_startTime(traj), st_endTime(traj) FROM traj_table;

Expected output:

     st_starttime     |     st_endtime
---------------------+---------------------
 2010-01-01 14:30:00 | 2010-01-01 15:30:00
 2010-01-01 14:30:00 | 2010-01-01 15:30:00
 2010-01-01 14:30:00 | 2010-01-01 15:30:00
 2010-01-01 14:30:00 | 2010-01-01 15:30:00
(4 rows)

Step 6: Measure trajectory similarity

ST_euclideanDistance computes the Euclidean distance between two trajectories and returns a scalar score. A lower value indicates greater similarity.

WITH traj AS (
  SELECT
    ST_makeTrajectory('STPOINT', 'LINESTRING(1 1, 5 6, 9 8)'::geometry,
      '[2010-01-01 11:30, 2010-01-01 15:00)'::tsrange,
      '{"leafcount":3,"attributes":{"velocity": {"type": "integer", "length": 2,"nullable" : true,"value": [120,130,140]}, "accuracy": {"type": "float", "length": 4, "nullable" : false,"value": [120,130,140]}, "bearing": {"type": "float", "length": 8, "nullable" : false,"value": [120,130,140]}, "acceleration": {"type": "string", "length": 20, "nullable" : true,"value": ["120","130","140"]}, "active": {"type": "timestamp", "nullable" : false,"value": ["Fri Jan 01 11:35:00 2010", "Fri Jan 01 12:35:00 2010", "Fri Jan 01 13:30:00 2010"]}}, "events": [{"2" : "Fri Jan 02 15:00:00 2010"}, {"3" : "Fri Jan 02 15:30:00 2010"}]}') a,
    ST_makeTrajectory('STPOINT', 'LINESTRING(1 0, 4 2, 9 6)'::geometry,
      '[2010-01-01 11:30, 2010-01-01 15:00)'::tsrange,
      '{"leafcount":3,"attributes":{"velocity": {"type": "integer", "length": 2,"nullable" : true,"value": [120,130,140]}, "accuracy": {"type": "float", "length": 4, "nullable" : false,"value": [120,130,140]}, "bearing": {"type": "float", "length": 8, "nullable" : false,"value": [120,130,140]}, "acceleration": {"type": "string", "length": 20, "nullable" : true,"value": ["120","130","140"]}, "active": {"type": "timestamp", "nullable" : false,"value": ["Fri Jan 01 11:35:00 2010", "Fri Jan 01 12:35:00 2010", "Fri Jan 01 13:30:00 2010"]}}, "events": [{"2" : "Fri Jan 02 15:00:00 2010"}, {"3" : "Fri Jan 02 15:30:00 2010"}]}') b
)
SELECT ST_euclideanDistance(a, b) FROM traj;

Expected output:

 st_euclideandistance
----------------------
   0.0888997369940162
(1 row)

Step 7 (optional): Drop the extension

DROP EXTENSION ganos_trajectory CASCADE;

Output format

A trajectory is serialized to JSON for text output. The JSON contains four top-level sections corresponding to the four components: time, space, attributes, and events.

{
  "trajectory": {
    "version": 1,
    "type": "STPOINT",
    "leafcount": 3,
    "start_time": "2010-01-01 14:30:00",
    "end_time": "2010-01-01 15:30:00",
    "spatial": "SRID=4326;LINESTRING(114 35,115 36,116 37)",
    "timeline": [
      "2010-01-01 14:30:00",
      "2010-01-01 15:00:00",
      "2010-01-01 15:30:00"
    ],
    "attributes": {
      "leafcount": 3,
      "velocity": {
        "type": "integer",
        "length": 2,
        "nullable": true,
        "value": [120, 130, 140]
      },
      "accuracy": {
        "type": "float",
        "length": 4,
        "nullable": false,
        "value": [120.0, 130.0, 140.0]
      },
      "bearing": {
        "type": "float",
        "length": 8,
        "nullable": false,
        "value": [120.0, 130.0, 140.0]
      },
      "acceleration": {
        "type": "string",
        "length": 20,
        "nullable": true,
        "value": ["120", "130", "140"]
      },
      "active": {
        "type": "timestamp",
        "length": 8,
        "nullable": false,
        "value": [
          "2010-01-01 14:30:00",
          "2010-01-01 15:00:00",
          "2010-01-01 15:30:00"
        ]
      }
    },
    "events": [
      {"1": "2010-01-01 14:30:00"},
      {"2": "2010-01-01 15:00:00"},
      {"3": "2010-01-01 15:30:00"}
    ]
  }
}

Field reference:

FieldDescription
versionFormat version (fixed value)
typeLeaf type (fixed value, e.g., STPOINT)
leafcountNumber of sampling points
start_time / end_timeStart and end timestamps of the trajectory
spatialSpatial path in WKT format with SRID prefix
timelineTimestamp of each sampling point, as a string array
attributes.leafcountNumber of elements in each attribute array
attributes.<name>.typeData type of the attribute (integer, float, string, timestamp)
attributes.<name>.lengthByte length of the data type
attributes.<name>.nullableWhether the attribute accepts null values
attributes.<name>.valueArray of attribute values, one per sampling point
eventsArray of {type: timestamp} key-value pairs

Convert between storage formats

Use the following patterns to move data between row-per-point tables and the trajectory type.

-- Create a source table of individual sampling points
CREATE TABLE sample_points (
  userid    numeric,
  sample_time timestamp,
  x double precision,
  y double precision,
  z double precision,
  intensity int
);

INSERT INTO sample_points VALUES
  (1, '2020-04-11 17:42:30', 114.35, 39.28, 4, 80),
  (1, '2020-04-11 17:43:30', 114.36, 39.28, 4, 30),
  (1, '2020-04-11 17:45:00', 114.35, 39.29, 4, 50),
  (2, '2020-04-11 17:42:30', 114.3,  39,    34, 60),
  (2, '2020-04-11 17:43:30', 114.3,  39,    38, 58);

-- Create a trajectory table
CREATE TABLE trajectory_table (userid numeric PRIMARY KEY, traj trajectory);

-- Aggregate sampling points into trajectory values
INSERT INTO trajectory_table
SELECT userid,
  ST_Sort(ST_MakeTrajectory(pnts.tjraw, true, '{"intensity"}'::cstring[]))
FROM (
  SELECT userid,
    array_agg(ROW(sample_time, x, y, z, intensity)) AS tjraw
  FROM sample_points
  GROUP BY userid
) pnts;

-- Convert a trajectory back to individual rows
SELECT f.*
FROM trajectory_table,
  ST_AsTable(traj) AS f(t timestamp, x double precision, y double precision,
                         z double precision, intensity integer);

-- Extract the spatial component as a LINESTRING
SELECT ST_trajspatial(traj) FROM trajectory_table;

-- Build a trajectory from a LINESTRING and a time range
SELECT ST_MakeTrajectory(
  'STPOINT'::leaftype,
  st_geomfromtext('LINESTRING (114 35, 115 36, 116 37)', 4326),
  '[2010-01-01 14:30, 2010-01-01 15:30)'::tsrange,
  '{}');

Use cases

Archiving historical trajectories

Aggregate high-frequency GPS pings from devices such as shared bicycles into trajectory objects. Apply trajectory simplification and downsampling algorithms to reduce data volume, then store the results in the database or in OSS buckets. Ganos provides unified query access regardless of storage location, so downstream applications do not need to distinguish between the two.

Spatio-temporal analysis and similarity search

Query which trajectories passed through a specific geographic area during a given time window, or find trajectories most similar to a reference path. Use these results to identify customer segments, support targeted promotions, or detect anomalous movement patterns.

Feature extraction for machine learning

Cleanse historical trajectories by resampling at uniform intervals and removing drift points. Extract statistical features — total length, duration, stay points, and curvature — then feed them into machine learning pipelines (neural network or other algorithms) to build user profiles for recommendation and personalization systems.

What's next