本文介绍了时空数据库Ganos轨迹模型在运输车辆位置分析中的应用场景。Ganos通过在数据库中原生内置移动对象的存储、检索与分析能力,打造全球首个移动对象数据库,为交通、物流、出行、生活服务类客户提供海量轨迹数据的分析挖掘能力。
能力介绍
关于轨迹模型
轨迹模型是时空对象中的一个重要模型,旨在支持对行人、汽车、船只、飞机等移动对象的处理与分析。
轨迹数据在应用中可以从两种视角进行分析。一种视角是将轨迹视为离散的点集,从而对其进行操作。另一种视角是将轨迹视为一条连续的线,表示为随时间变化的空间折线。这两种表示方法各有适用的范围。连续的轨迹线对采样频率不敏感,可作为折线进行各类空间运算。相较之下,离散的轨迹点对采样方式和采样频率的敏感性更高,但在算法上更加友好和简单。常见的相似度计算、轨迹切分等函数通常基于对点的操作。
轨迹常用操作
在实际使用中,常常出现需要满足特定条件的轨迹采样点的情况。例如:
实时数据的存储
搭载GPS的汽车、共享单车等交通工具,其轨迹点的产生频率极为迅速,可能每1至5秒就会生成一个新的采样点。在实时检测场景中,实时轨迹点具有重要的应用价值。然而,当轨迹点数量过多时,个别点所提供的信息实际上有限,但却占用了大量的存储空间。因此,需要去除一些轨迹点,以尽量保留轨迹信息的前提下,降低存储空间的占用。
轨迹数据的预处理
在实际场景中,由于多种原因,采样数据往往存在不准确的情况。非人为因素包括定位系统的误差、信号传递质量的不稳定以及设备的损坏等。人为因素则包括手动关闭采样或在未进行采样时移动物体等。在这些情况下,通常需要对轨迹上的特殊点进行特殊处理,诸如漂移点、驻留点和跳跃点。
轨迹特征提取
在业务应用中,许多情况下不仅需要直接使用轨迹,还需提取轨迹的一些特征信息。最基础的特征信息包括长度、点数、时间范围等,而高阶特征则涵盖轨迹之间的相似度、密度聚类的聚簇位置等。在许多高阶特征的计算中,通常采用离散的近似算法,如动态时间规整(DTW)、最小连续子序列(LCSS)、编辑距离(EDR)等。这类算法对前述的轨迹简化和采样不准确问题往往较为敏感,使用经过简化的轨迹或采样不均匀的轨迹可能导致结果的显著差异。因此,通常需要对轨迹进行重采样,以使得点与点之间的时间或空间差值趋于均匀。
案例场景
场景概述
交通运输公司每日管理着大量的运输车辆。这些车辆配备了GPS系统,能够每隔数秒获取实时位置信息。同时,车辆位置信息与车辆状态信息、运输单信息等业务数据进行关联,为后续开展深度轨迹分析与挖掘奠定基础。主要应用场景包括:
轨迹存储查询与展示:运输车辆的轨迹数据主要包括运输单、经度、纬度和时间这四个字段,推送频率为每5秒生成一个数据点。在正常情况下,一个公司每天有数百至数千辆车同时在途,预计每天会写入千万级的轨迹数据。在获取轨迹数据后,需要进行轨迹预处理(如压缩、抽稀等),并将处理结果展示于业务系统上。
到达性分析:为了确保运输车辆的及时到达,通常会在地图上设定多个电子围栏(例如高速公路出口等重要位置)。如果在特定时间点车辆尚未触及这些电子围栏,则意味着该车辆可能无法在规定时间内将货物送达客户。因此,需要触发预警,以通知相关工作人员进行处理。
停留点分析:长途运输车辆通常需要在途中进行休息。为了避免驾驶人员因夜间驾驶而带来的潜在安全隐患,有必要对运输车辆在夜间是否存在停留点进行监管(即大量轨迹在空间位置上的静止)。如果在夜间时间段内未出现停留点,则需提醒驾驶员尽快进行休息。
轨迹相似度:运输车队中的部分车辆出现了位置偏离,可能是由于车辆驶入错误的路线,从而导致货运到达性风险的产生。为此,需要根据多辆车辆轨迹的相似性,识别异常轨迹,以便及时发现潜在风险。
场景分析
为实现上述场景,可能需要对车辆轨迹进行预处理、分析与压缩,最终以支持前端展示。可能用到的主要功能包括::
轨迹切分(ST_Split):通过几何对象将一条轨迹切分为多条子轨迹,可以帮助业务将一辆运输车的总体轨迹按照不同的运货单进行分割。
轨迹构造(ST_append):通过向一条轨迹中不断追加新的轨迹点,可以获得全新的轨迹对象,从而帮助业务实时构建不断延长的轨迹。
轨迹空间关系判断(ST_intersects):判断轨迹是否与几何对象相关,有助于业务分析在相关时间段内是否触及了电子围栏的规定范围。
轨迹驻点(ST_StayPoint):提取轨迹中的停留点,可以帮助业务分析在任意时间段内车辆的停留时长,从而判断是否存在疲劳驾驶的行为。
轨迹简化(ST_Compress):压缩轨迹能够帮助业务根据空间距离偏移阈值、角度偏离阈值及加速度偏离阈值等标准进行数据压缩,从而减少最终渲染的数据量。
最佳实践
安装插件
执行如下SQL语句,安装所需插件。
CREATE EXTENSION ganos_spatialref;
CREATE EXTENSION ganos_geometry;
CREATE EXTENSION ganos_trajectory;
轨迹表配置
执行如下SQL语句,配置轨迹点表、轨迹表和电子围栏表。
-- 定义轨迹点表,包含运输单号、经度、纬度、时间四个字段
CREATE TABLE bill_point(id integer, longitude double precision, latitude double precision, sample_time timestamp);
-- 定义轨迹表, 包含运输单号、轨迹对象两个字段
CREATE TABLE bill_traj(id integer UNIQUE, traj trajectory);
--创建空间索引
CREATE INDEX ON bill_traj USING gist(traj);
-- 定义电子围栏表,包含围栏查询的id,时间和空间范围
CREATE TABLE fence(id integer, fence_time timestamp, area geometry);
-- 建立id和时间索引
CREATE INDEX ON fence USING btree(id);
CREATE INDEX ON fence USING btree(fence_time);
-- 创建Trigger函数
CREATE OR REPLACE FUNCTION trajectory_sync_point() RETURNS TRIGGER AS $$
BEGIN
INSERT INTO bill_traj
SELECT NEW.id, ST_MakeTrajectory(array_agg(ROW(NEW.sample_time, NEW.longitude, NEW.latitude)), false, '{}'::cstring[])
ON CONFLICT(id) DO UPDATE
SET traj = ST_Append(bill_traj.traj, excluded.traj);
RETURN NULL;
END;
$$
LANGUAGE plpgsql STRICT PARALLEL SAFE;
-- 创建同步Trigger
CREATE TRIGGER point_trigger AFTER INSERT ON bill_point
FOR EACH ROW EXECUTE PROCEDURE trajectory_sync_point();
通过点表插入数据并执行电子围栏查询
通过在轨迹点表中插入点,进而在轨迹表中构建轨迹数据。
-- 向点表插入id为1的轨迹数据 INSERT INTO bill_point VALUES (1, 2, 2, '2000-01-01 00:01:00'), (1, 2.1, 2, '2000-01-01 00:02:00'), (1, 2.2, 2, '2000-01-01 00:03:00'); -- 从轨迹表中查询,确认点表的新的点已经成功同步到轨迹表 SELECT * FROM bill_traj; -- 查看id为1的轨迹中的所有点 SELECT f.* FROM (SELECT traj FROM bill_traj WHERE id = 1) a, ST_AsTable(a.traj) AS f(t timestamp,x double precision, y double precision);
对轨迹表中的轨迹数据执行实时的电子围栏查询。
-- 设置电子围栏,这里我们设置为:在2000-01-01 00:05:00通过一个经度在 2.3,纬度在2附近的区域 INSERT INTO fence VALUES(1, '2000-01-01 00:05:00', ST_MakeEnvelope(2.29,1.99,2.31,2.01)); -- 我们假设业务上每隔一分钟执行一次电子围栏的扫描,则使用下列语句。 -- now() >= fence.fence_time AND now() - '60 s'::interval < fence.fence_time 这里我们因为一分钟执行一次,因此只截取在这一分钟需要判断的fence。这里使用当前的时间减去fence设定的时间,小于1分钟则代表我们目前需要对此fence进行判断 -- fence.id = bill_traj.id 提取对应的轨迹 -- ST_EndTime(bill_traj.traj) >= fence.fence_time 代表确认轨迹的末尾已经超过此时间 -- ST_2DIntersects(bill_traj.traj, fence.area)检查相交 -- 此时没有因为迟到而触发电子围栏的查询,返回空 SELECT fence.id FROM fence JOIN bill_traj ON now() >= fence.fence_time AND now() - '60 s'::interval < fence.fence_time AND fence.id = bill_traj.id AND ST_EndTime(bill_traj.traj) >= fence.fence_time AND NOT ST_2DIntersects(bill_traj.traj, fence.area); -- 新插入一个点,此时轨迹的时间到达了电子围栏的设定时间,此点不在围栏范围内,触发电子围栏 INSERT INTO bill_point VALUES (1, 2.25, 2, '2000-01-01 00:05:00'); -- 查看此时id为1的轨迹的点 SELECT f.* FROM (SELECT traj FROM bill_traj WHERE id = 1) a, ST_AsTable(a.traj) AS f(t timestamp,x double precision, y double precision); -- 现在我们假设当前时间是2000-01-01 00:05:00。执行下列语句,查询所有fence是否被触发;此时发现id为1的电子围栏被触发,说明对应的车辆已经迟到 SELECT fence.id FROM fence JOIN bill_traj ON fence.id = bill_traj.id AND ST_EndTime(bill_traj.traj) >= fence.fence_time AND NOT ST_2DIntersects(bill_traj.traj, fence.area); -- 此时又输入了一个新增轨迹点,其空间上进入了电子围栏,说明此时此车辆已经经过指定地点。此时再进行查询,就发现没有电子围栏被触发,所有车辆状态正常 INSERT INTO bill_point VALUES (1, 2.3, 2, '2000-01-01 00:06:00'); SELECT fence.id FROM fence JOIN bill_traj ON fence.id = bill_traj.id AND ST_EndTime(bill_traj.traj) >= fence.fence_time AND NOT ST_2DIntersects(bill_traj.traj, fence.area); -- 查看此时id为1的轨迹的点 SELECT f.* FROM (SELECT traj FROM bill_traj WHERE id = 1) a, ST_AsTable(a.traj) AS f(t timestamp,x double precision, y double precision); -- 轨迹点继续输入,离开电子围栏,但不再影响电子围栏是否被触发 INSERT INTO bill_point VALUES (1, 2.3, 2.1, '2000-01-01 00:07:00'); INSERT INTO bill_point VALUES (1, 2.2, 1.9, '2000-01-01 00:08:00');
直接构造轨迹数据并求取驻点
可以不通过点表,直接在轨迹表中构造一条较长的轨迹数据。
--根据分别指定轨迹的时间,空间对象来创建轨迹 INSERT INTO bill_traj SELECT 2, ST_makeTrajectory ('STPOINT'::leaftype, 'LINESTRING(0 0,1 1,2 2,2 2,2 2,2 3,3 4,2 4,2 3,2 3,2 3,2 2,2 1,2 0,1 0,0 0,-1 0)'::geometry, '{"2000-01-01 00:12:34","2000-01-01 00:23:37","2000-01-01 00:34:41","2000-01-01 00:45:45","2000-01-01 00:56:48","2000-01-01 01:07:52","2000-01-01 01:18:56","2000-01-01 01:30:00","2000-01-01 01:41:03","2000-01-01 01:52:07","2000-01-01 02:03:11","2000-01-01 02:14:14","2000-01-01 02:25:18","2000-01-01 02:36:22","2000-01-01 02:47:25","2000-01-01 02:58:29","2000-01-01 03:09:33"}'::timestamp[], NULL);
通过对轨迹的驻留点进行检测,可以判断车辆是否进行了足够的休息,以及其休息的具体位置。
-- 获取id为2的轨迹的驻留点。 -- 其中,第一个参数代表轨迹,第二个参数代表对轨迹按5分钟为采样间距进行采样,第三个参数和第四个参数代表将距离在0.1以内,时间差在15分钟以内的点视为临近点,第五个参数代表一个点的临近点数量超过3个就确定其在此处驻留 SELECT ST_StayPoint(traj, '5 minute', 0.1, '15 minute', 3) from bill_traj where id =2 ;
返回两个驻留点:
st_staypoint ------------------------------------------------------------------------------------------ (010100000000000000000000400000000000000040,"2000-01-01 00:34:41","2000-01-01 00:56:48") (010100000000000000000000400000000000000840,"2000-01-01 01:41:03","2000-01-01 02:03:11")
(可选)实际业务中,可以对驻留点查询结果进行加工。
-- 获取两个驻留点的位置 SELECT ST_AsText((ST_StayPoint(traj, '5 minute', 0.1, '15 minute', 3)).point) from bill_traj where id =2 ; -- 检查此轨迹的驻留(休息)总时长是否超过30分钟。如果没超过,可能代表其疲劳驾驶 SELECT SUM(duration) >= '30 minute'::interval FROM (SELECT (ST_StayPoint(traj, '5 minute', 0.1, '15 minute', 3)).endt - (ST_StayPoint(traj, '5 minute', 0.1, '15 minute', 3)).startt as duration from bill_traj where id =2 ) staytime;
轨迹简化
对于点数较多的轨迹点,可以使用ST_Compress函数对其进行简化,以便于后续的展示。
-- 获取简化后的轨迹,可以看到,id为2的轨迹简化后由17个点简化为7个点。参数0.2代表简化后的轨迹距离原轨迹最大距离为0.2。此简化后的轨迹可以用于前端显示。
SELECT ST_Compress(traj, 0.2) FROM bill_traj;
-- 查看简化后的id为2的轨迹的所有采样点
SELECT f.* FROM (SELECT traj FROM bill_traj WHERE id = 2) a, ST_AsTable(ST_Compress(a.traj, 0.2)) AS f(t timestamp,x double precision, y double precision);
总结
Ganos轨迹模型提供了一系列云原生的移动对象数据类型、函数以及存储过程,帮助您高效管理、查询和分析时空轨迹数据。相较于传统方案,Ganos的轨迹模型具备原生数据类型及空间索引,查询效率很高。同时,轨迹模型提供了包括轨迹构造、轨迹处理、轨迹属性、轨迹事件、轨迹空间关系、轨迹时空关系、轨迹统计、轨迹量测、轨迹相似度等十余大类、数百个算子的支持,具备较强的易用性。目前,Ganos轨迹模型在交通运输、快递物流、快捷出行、生活服务及公共安全等领域具备了十分完善的能力供给与应用案例,服务了多种客户群体,为客户的智能化位置服务应用提供了稳定、高效和健全的时空基础保障。