背景
如果原始GPS点没有包括行驶速度等信息,那就需要使用时间和位置这些已知信息,通过额外计算车辆的行驶状态,来找出车辆的停车点。
使用PostGIS函数
使用到的PostGIS函数如下
轨迹停车点
业务需求是,如果一系列在N分钟内的GPS点距离小于等于M米,则认为车辆在当前位置停车N分钟。
注意: 从严格定义来说,我们求的是“停留点”而不是“停车点”,因为车辆非常慢的行驶也可能被算法当做是停车了。
通过相邻两个GPS点的距离和时间,可以判断是否停车。思路如下
- 遍历轨迹中的每一个点,并把点依次放入停车点集合;
- 如果集合起点P1和当前点PT的距离小于M,时间大于N,则标记PT为停车点,并放入当前停车点聚集集合中;
- 检查下一个点,直到PT被标记为行驶点,则已经找到当前位置的所有停车点,返回当前集合;
- 重复上诉过程直到处理完所有点;
停车点算法
drop function if exists f_get_staypoints;
/*
查找给定行驶轨迹的停车点聚集。
停车点的判定依据是,如果一系列在N分钟内的GPS点距离小于等于M米,则这些点构成一个停车点聚集,每一个聚集作为一个Linestring返回。
@traj 轨迹
@length_meters 用米作为单位的距离阈值
@duration_mins 用分钟作为单位的停车时长阈值
*/
create or replace function f_get_staypoints(traj geometry, length_meters int=50, duration_mins int=30)
returns table(sp_cluster geometry)
as ?
declare
pt geometry;
p1 geometry;
segment geometry;
is_stop boolean;
prev_stop boolean;
begin
p1 := null;
segment := null;
is_stop := false;
prev_stop := false;
-- 遍历轨迹中的每一个点
for pt in select (st_dumppoints(traj)).geom loop
-- 1st point
if segment is null and p1 is null then
p1 := pt;
-- 2nd point
elsif segment is null then
segment := st_makeline(p1, pt);
p1 := null;
if st_length(segment::geography) <= length_meters then is_stop := true; end if;
-- others points
else
segment := st_addpoint(segment, pt);
-- 如果是停车状态就扩展聚集;否则就把聚集中点的时长控制在阈值内
if not is_stop then
while st_npoints(segment) > 2 and ( st_m(st_endpoint(segment)) - st_m(st_startpoint(segment)) ) >= duration_mins*60 loop
segment := st_removepoint(segment, 0);
end loop;
end if;
-- 根据距离阈值判定当前状态是否为停车
if st_length(segment::geography) <= length_meters then is_stop := true;
elsif st_distance( st_startpoint(segment)::geography, pt::geography) > length_meters then is_stop := false;
else is_stop := false;
end if;
-- 已经找到停车聚集的最后一个点,还需要确保满足必须的停车时长。如果都满足,就返回当前停车点聚集
if not is_stop and prev_stop then
-- 终点和起点的距离 是否大于阈值
if ( st_m(st_pointn(segment, st_npoints(segment)-1)) - st_m(st_startpoint(segment)) ) >= duration_mins*60 then
sp_cluster := st_removepoint(segment, st_npoints(segment)-1);
return next;
segment := null;
p1 := pt;
end if;
end if;
end if;
prev_stop := is_stop;
end loop;
-- 最后一个轨迹点是否是停车点聚集的最后一个点
if prev_stop and ( st_m(st_endpoint(segment)) - st_m(st_startpoint(segment)) ) >= duration_mins*60 then
sp_cluster := segment;
return next;
end if;
end;
? language plpgsql strict;
调用算法测试
-- 停车点聚集
with sp_cluster as
(
select tr.tid, tr.dt,
demo.f_get_staypoints(traj, 1000, 30) as pts
from demo.t_taxi_trajectory tr
where tr.tid = 1353 and tr.dt = '2008-02-03'
),
-- 求每一个停车点聚集的信息
sp_single as
(
select
spc.tid, spc.dt,
st_npoints(spc.pts) sp_count,
st_length( spc.pts::geography )::int len_m,
to_timestamp( st_m( ST_startpoint(spc.pts ) ))::timestamp without time zone as ts_start,
to_timestamp( st_m( ST_endpoint(spc.pts ) ))::timestamp without time zone as ts_end,
st_startpoint(spc.pts ) sp,
spc.pts
from sp_cluster spc
)
-- 把聚集的第一个点标记为本次停车点
select sps.tid, sps.dt,
sps.ts_start,
sps.ts_end,
(sps.ts_end-sps.ts_start) as duration,
sps.sp_count,
sps.len_m,
st_x(sps.sp) as lng,
st_y(sps.sp) as lat,
-- 逆地址查找停车点
(select f.ext_path from demo.t_area_fence f where st_within(sps.sp, f.fence_polygon) order by f.id desc limit 1) as stay_area,
sps.sp as staypoint
from sp_single sps
order by tid, dt, ts_start
;
部分查询结果如下
tid | dt | ts_start | ts_end | duration | sp_count | len_m | lng | lat | stay_area
------+------------+---------------------+---------------------+----------+----------+-------+-----------+----------+----------------------
1353 | 2008-02-03 | 2008-02-03 06:07:36 | 2008-02-03 08:48:26 | 02:40:50 | 17 | 674 | 116.36887 | 39.85015 | 北京市-北京市-丰台区
1353 | 2008-02-03 | 2008-02-03 10:58:37 | 2008-02-03 11:38:49 | 00:40:12 | 11 | 805 | 116.37633 | 39.8974 | 北京市-北京市-西城区
1353 | 2008-02-03 | 2008-02-03 13:22:57 | 2008-02-03 14:32:51 | 01:09:54 | 12 | 797 | 116.36912 | 39.85082 | 北京市-北京市-丰台区
1353 | 2008-02-03 | 2008-02-03 18:51:22 | 2008-02-03 19:26:44 | 00:35:22 | 5 | 931 | 116.42133 | 39.89582 | 北京市-北京市-东城区
(4 rows)