能力介绍
关于Ganos
Ganos是阿里云数据库产品事业部联合阿里巴巴达摩院数据库与存储实验室联合共同研发的新一代云原生位置智能引擎,它将时空数据处理能力融入了云原生关系型数据库PolarDB、云原生多模数据库Lindorm、云原生数据仓库AnalyticDB和云数据库RDS PG等核心产品中。Ganos目前拥有几何、栅格、轨迹、表面网格、体网格、3D实景、点云、路径、地理网格、快显十大核心引擎,为数据库构建了面向新型时空多模多态数据的一体化表达、存储、查询与分析计算能力。
本文介绍的轨迹引擎能力,依托阿里云云原生关系型数据库PolarDB、云原生数据仓库AnalyticDB和云数据库RDS PG建设输出。
关于轨迹模型
轨迹模型是Ganos时空数据库中一个重要的模型,用来支持行人、汽车、船只、飞机等移动对象的处理和分析。轨迹数据在应用上有两种视角,一种是离散的点,将轨迹视为一个点集并进行操作;另一种是连续的线,将轨迹视为一条空间中随时间变化的折线。两种表示各有其适用的范围,连续的轨迹线对采样频率不敏感,可以当作折线进行各类的空间运算;离散的轨迹点虽然对采样方式、采样频率更敏感,但对算法来说更加友善和简单,常见的相似度计算、轨迹切分等函数,都是基于对点的操作。
time |
x |
y |
speed |
|
2020-04-11 17:42:30 |
114.35 |
39.28 |
4.3 |
|
2020-04-11 17:43:30 |
114.36 |
39.28 |
4.8 |
|
2020-04-11 17:45:00 |
114.35 |
39.29 |
3.5 |
轨迹常用的操作
在实际的使用中,常常出现需要轨迹的采样点满足一定条件的情况。例如:
- 实时数据的存储
搭载了GPS的汽车、共享单车等交通工具,其轨迹点的产生频率是非常快的——可能每1-5秒就会产生一个新的采样点。实时的轨迹点在实时检测场景中非常有用,然而当轨迹点数量过多后,中间每个点所提供的信息实际上并不多,但会挤占大量的存储空间。此时,我们希望去除掉一些轨迹点,使得尽可能保有轨迹信息的情况下,降低存储空间。
- 轨迹数据的预处理
在实际场景中,常常会由于各种原因带来采样数据不准确的情况。非人为的因素包括定位系统的误差,信号传递质量的不稳定,设备的损坏等;人为因素包括手动关闭采样,或在不进行采样时挪动物体等。在这些情况下,经常需要对轨迹上的特殊点:如漂移点,驻留点,跳跃点进行特殊处理。
- 轨迹特征提取
业务上,很多时候不仅直接使用轨迹,同时还需要提取轨迹的一些特征信息。最基础的特征信息包括长度、点数、时间范围等,而高阶的特征则包括轨迹间的相似度,密度聚类的聚簇位置等等。在很多高阶特征计算时,使用的都是离散的近似算法,例如DTW,LCSS,EDR等等距离。而此类算法对上述我们提到的轨迹简化、采样不准问题常常比较敏感,使用简化后的轨迹或采样不均匀的轨迹都会造成结果的差异。因此,我们常常需要对轨迹进行重新采样,使得点与点之间的时间或空间差值趋于均匀。
使用场景
场景概述
交通运输公司每天管理着大量的运输车辆,车辆上安装有GPS信息可以每间隔数秒获取实时位置,同时车辆位置信息与车辆状态信息、车辆运输单信息等业务数据进行关联,后续开展深度的轨迹分析与挖掘,主要的应用场景包括:
- 轨迹存储查询与展示:运输车辆的轨迹数据主要包含运输单、经度、维度、时间这四个字段,推送频次为每5秒一个点,正常情况下一个公司每天有成百数千辆车同时在途,每天约有千万级轨迹数据写入;获取到轨迹数据后需要进行轨迹预处理(压缩、抽稀等),并将结果显示在业务系统上
- 到达性分析:为了保证运输车辆的及时到达,往往会在地图上设置多个电子围栏(如高速路出口等重要位置),如果在某个时间点还没触碰到这个电子围栏,那么就意味着该车有可能在规定的时间内不能把货物送到客户手中,就要触发预警通知相关工作人员处理
- 停留点分析:长途运输车辆往往需要中途休息,为了避免驾驶人员开夜车造成可能的安全隐患,需要监管运输车辆是否在夜间存在停留点(即大量的轨迹没有空间位置上的移动),如果在夜间时间段内停留点没有产生,则需要提醒驾驶员尽快休息
- 轨迹相似度:运输车队有部分车辆发生了位置偏离,可能由于车辆驶入了错误的路线,造成货运出现到达性风险,需要根据多个车辆轨迹的相似情况,寻找异常轨迹,及时发现可能的风险
功能分析
为实现上述场景,可能需要针对车辆轨迹进行预处理、分析与压缩,最终支持前端展示,可能用到的主要功能包括:
- 轨迹切分(ST_Split):使用用几何对象将一条轨迹切分为多条子轨迹,可以帮助业务把一辆运输车的总体轨迹按不同的运货单切开
- 轨迹构造(ST_append):向一条轨迹中不断追加新的轨迹点而获取到全新的轨迹对象,可以帮助业务实时构造不断变长的轨迹
- 轨迹空间关系判断(ST_2DIntersects):判断轨迹是否与几何对象相关,可以帮助业务分析在相关时间段是否触碰到了电子围栏的规定范围
- 轨迹驻点(ST_StayPoint):提取轨迹的停留点,可以帮助业务判断在任意时间段内车辆的停留时长从而判断是否存在疲劳驾驶的行为
- 轨迹简化(ST_Compress):压缩轨迹,可以帮助业务根据空间距离偏移阈值、角度偏离阈值、加速度偏离阈值等方式进行压缩,以减少最终渲染的数据量
技术实现
创建扩展
连接数据库,执行以下SQL语句,创建ganos_trajectory扩展。
CREATE EXTENSION ganos_spatialref; CREATE EXTENSION ganos_geometry; CREATE EXTENSION ganos_trajectory;
轨迹表配置
配置轨迹点表,轨迹表,电子围栏表
-- 定义轨迹点表,包含运输单号、经度、纬度、时间四个字段 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轨迹引擎在交通运输、快递物流、快捷出行、生活服务、公共安全等领域有十分完整的能力供给与应用案例,服务了多类客户群体,为客户智能化位置服务应用提供了稳定高效健全的时空基底保障。
感谢阿里云DataV数据可视化团队对本文的大力支持,DataV已经可以接入Ganos轨迹引擎,实现海量移动对象的高效渲染,并与Ganos形成了联合解决方案,欢迎各位用户体验