Mobility轨迹扩展使用说明

简介

MobilityDB主要用于移动对象的地理空间轨迹,例如GPS轨迹,它为PostgreSQL数据库及其空间扩展PostGIS添加了对时间和时空对象的支持。本文主要对MobilityDB进行了实践,包括安装部署和基于OSM数据的处理入库、显示分析等示例。

Hint

在建立扩展时,如果出现 undefined symbol: ST_Distance 的错误,修改 postgresql.conf 文件,添加 shared_preload_libraries='postgis-3' ,postgis后面的数值根据目前实际使用的插件版本号进行修改。

基本介绍

MobilityDB是基于PostGIS的,它和PostGIS的区别在于它对时间和空间等数据格式进行了包装整合,PostGIS的数据是一个单独的点、线、面,一份单独的时间。而MobilityDB的每一条数据则是一份完整的时空对象,比如时间+空间的线串。

相对于 PostgreSQL 的 timestamptz 类型,MobilityDB额外提供了 periodtimestampsetperiodset 三种基本时间类型。

period:表示一个时间段,有下限和上限,[] 代表边界时间包含在其中,()表示边界时间不包含在其中,下限的值可以小于等于上限的值。

period '[2012-01-01 08:00:00, 2012-01-01 09:30:00)'; 表示八点(包含)到九点半(不包含)
period '[2012-01-01 08:00:00, 2012-01-01 08:00:00)'; 错误格式,是一个空的时间段
period '[2012-01-01 08:00:00, 2012-01-01 08:00:00]'; 正确格式,只包含了八点这一个瞬时时态

timestampset:表示一组不同的单个时间值,必须是排序后的状态

timestampset '{2012-01-01 08:00:00, 2012-01-01 08:30:00}'; 正确,一组不同时间且排序的值
timestampset '{2012-01-01 08:00:00, 2012-01-01 08:00:00}'; 错误,两个相同的值
timestampset '{2012-01-01 08:10:00, 2012-01-01 08:00:00}'; 错误,时间顺序不对

periodset:表示一组不相交的period,必须是排序后的状态

periodset '{[2012-01-01 08:00:00, 2012-01-01 08:10:00],[2012-01-01 08:20:00, 2012-01-01 08:40:00]}'; 正确,时间无相交,且有排序
periodset '{[2012-01-01 08:00:00, 2012-01-01 08:20:00],[2012-01-01 08:20:00, 2012-01-01 08:40:00]}'; 错误。时间相交
periodset '{[2012-01-01 08:20:00, 2012-01-01 08:40:00],[2012-01-01 08:00:00, 2012-01-01 08:10:00]}'; 错误,时间顺序不对

相对于PostGIS的geometry类型,MobilityDB提供了时间空间一体的tgeompoint类型,能表示连续的运动轨迹。

tgeompoint '{Point(0 0)@2017-01-01 08:00:00,Point(0 1)@2017-01-01 08:05:00}'; 表示一份轨迹在两个时间的的不同位置

实操使用

动态展示

我们从网上下载了一份OSM 数据 ,用它来生成一份真实的轨迹数据集,并在QGIS上动态的展示出来,生成过程中依赖一些脚本和插件工具,链接如下:

MobilityDB-BerlinMOD

pgRouting

osm2pgrouting

libpqxx

QGIS

move

Hint

本次操作依赖的工具作用包括OSM数据导入和建立拓扑,两点之间路径分析等,主要目的是根据该数据生成较为真实的时序数据,过程较为繁琐。实际使用中时序数据可以有多种来源,也可以根据自己的geometry数据自定义生成方式。

下载好依赖工具,需要的依赖文件可以参照链接里的说明,下载好后开始编译(部分可以直接在线安装)。 首先安装 libpqxx ,这是编译需要依赖的库,导出 pg_config 路径,本次缺少一些文档生成相关的库,选择不安装了,需要增加一个config选型 --disable-documentation

cd /opt/libpqxx-6.4
./configure --disable-documentation
make && make install

安装osm2pgrouting,如果postgresql数据库是手动安装的,需要指定libpq.so的路径。

cd osm2pgrouting-2.3.8/
cmake3 .. -DPOSTGRESQL_LIBRARIES=/opt/pg13/lib/libpq.so
make && make install

安装pgRouting,需要依赖perl-version、boost1.56以上,boost库版本过低的可以手动下载boost库编译安装,。

yum install -y perl-version

cd /opt/pgrouting-3.4.1
mkdir build
cd build
make && make install

安装osm2pgsql。

yum install -y osm2pgsql

首先创建需要的扩展,hstore可以在postgresql源码里找到直接编译安装。

CREATE EXTENSION hstore;
CREATE EXTENSION pgRouting;

导入数据,脚本执行过程中需要输入对应的账号密码数据库名字,xml文件为MobilityDB-BerlinMOD目录下的配置文件,此步骤为加载地图并将其转换为适用于 pgRouting 的可路由网络拓扑格式。

osm2pgrouting -h localhost -p 13000 -U pg13 -f brussels.osm --dbname mobility -c mapconfig_brussels.xml

导入原始osm数据到数据库里,注意指定参数的大小写。

osm2pgsql -c -H localhost -P 13000 -U pg13  -W -d mobility brussels.osm

在服务端执行sql脚本,此脚本的作用是利用pgRouting功能来进行两个节点之间的路径规划,再对路线进行数据填充,生成时态数据。

sql -d mobility -f /opt/software/3rd_src/MobilityDB-BerlinMOD-master/BerlinMOD/brussels_preparedata.sql
psql -d mobility -f /opt/software/3rd_src/MobilityDB-BerlinMOD-master/BerlinMOD/berlinmod_datagenerator.sql
psql -d mobility -c 'select berlinmod_generate(scaleFactor := 0.005)'

此时轨迹数据已生成到名为 trips 的表中。

客户端工具: 下载QGIS和move插件,安装完QGIS后,将move解压并在QGIS里通过zip文件安装插件。

打开QGIS,点击上方按钮,数据库 > move > open move interface。

move

此时下方会出现一个查询框,在下拉框中选择要连接的数据库名字,输入查询语句,查询完成后数据集会被添加到图层里。

select

在QGIS里用`Quickmapservices`插件添加一份底图到图层,以bing map为例。

map

在上方的时态控制面板中,点击右侧设置按钮,将帧率调至60,点击刷新按钮,会自动捕获时间段,步长设置为1秒钟。

step

显示效果如下:

trip1 trip2

功能介绍

下面列出了一些常用的数据查询方式,MobilityDB提供了一些函数,用法与PostGIS函数类似。

1、范围查询

列出在某个区域通过的车辆

SELECT DISTINCT R.RegionId, T.VehId
FROM Trips T, Regions R
WHERE ST_Intersects(trajectory(T.Trip), R.Geom)
ORDER BY R.RegionId, T.VehId;

列出某个时间段内某个区域内的车辆

SELECT R.RegionId, P.PeriodId, T.VehId
FROM Trips T, Regions R, Periods P
WHERE T.Trip && stbox(R.Geom, P.Period)
AND intersects(atPeriod(T.Trip, P.Period), R.Geom)
ORDER BY R.RegionId, P.PeriodId, T.VehId;

列出在一段时间内都位于区域内的车辆对

SELECT DISTINCT T1.VehId AS VehId1, T2.VehId AS VehId2, R.RegionId, P.PeriodId
FROM Trips T1, Trips T2, Regions R, Periods P
WHERE T1.VehId < T2.VehId AND
      T1.Trip && stbox(R.Geom, P.Period) AND
      T2.Trip && stbox(R.Geom, P.Period) AND
        intersects(atPeriod(T1.Trip, P.Period), R.Geom) AND
        intersects(atPeriod(T2.Trip, P.Period), R.Geom)
        ORDER BY T1.VehId, T2.VehId, R.RegionId, P.PeriodId;

列出车辆首次访问某个点的时间

SELECT T.VehId, P.PointId, MIN(startTimestamp(atValue(T.Trip,P.Geom))) AS Instant
FROM Trips T, Points P
WHERE ST_Contains(trajectory(T.Trip), P.Geom)
GROUP BY T.VehId, P.PointId;

2、时态聚合查询

计算每个时间段处于活动状态的车辆数量

SELECT P.PeriodID, COUNT(*), TCOUNT(atPeriod(T.Trip, P.Period))
FROM Trips T, Periods P
WHERE T.Trip && P.Period
GROUP BY P.PeriodID
ORDER BY P.PeriodID;

对于中的每个区域,以 10 分钟的间隔提供行程的窗口时态计数

SELECT R.RegionID, WCOUNT(atGeometry(T.Trip, R.Geom), interval '10 min')
FROM Trips T, Regions R
WHERE T.Trip && R.Geom
GROUP BY R.RegionID
HAVING WCOUNT(atGeometry(T.Trip, R.Geom), interval '10 min') IS NOT NULL
ORDER BY R.RegionID;

3、距离查询

列出车辆在这段时间内的总行驶距离

SELECT T.VehId, P.PeriodId, P.Period,
SUM(length(atPeriod(T.Trip, P.Period))) AS Distance
FROM Trips T, Periods P
WHERE T.Trip && P.Period
GROUP BY T.VehId, P.PeriodId, P.Period
ORDER BY T.VehId, P.PeriodId;

列出每辆车和每个点之间的最小距离

SELECT T.VehId, P.PointId, MIN(trajectory(T.Trip) <-> P.Geom) AS MinDistance
FROM Trips T, Points P
GROUP BY T.VehId, P.PointId
ORDER BY T.VehId, P.PointId;

列出每对车辆之间的最小时间距离。

SELECT T1.VehId AS Car1Id, T2.VehId AS Car2Id, tmin(T1.Trip <-> T2.Trip) AS MinDistance
FROM Trips T1, Trips T2
WHERE T1.VehId < T2.VehId AND period(T1.Trip) && period(T2.Trip)
GROUP BY T1.VehId, T2.VehId
ORDER BY T1.VehId, T2.VehId;

列出每对行程之间的最近接近时间、距离和最短线

SELECT T1.VehId AS Car1Id, T1.TripId AS Trip1Id, T2.VehId AS Car2Id,
T2.TripId AS Trip2Id, period(NearestApproachInstant(T1.Trip, T2.Trip)) AS Time,
NearestApproachDistance(T1.Trip, T2.Trip) AS Distance,
ShortestLine(T1.Trip, T2.Trip) AS Line
FROM Trips T1, Trips T2
WHERE T1.VehId < T2.VehId AND period(T1.Trip) && period(T2.Trip)
ORDER BY T1.VehId, T1.TripId, T2.VehId, T2.TripId;

列出一对车辆相距 10 m 或更短的时间和位置。

SELECT T1.VehId AS VehId1, T2.VehId AS VehId2, atPeriodSet(T1.Trip,
getTime(tdwithin(T1.Trip, T2.Trip, 10.0, TRUE))) AS Position
FROM Trips T1, Trips T2
WHERE T1.VehId < T2.VehId AND T1.Trip && expandSpatial(T2.Trip, 10) AND
tdwithin(T1.Trip, T2.Trip, 10.0, TRUE) IS NOT NULL
ORDER BY T1.VehId, T2.VehId, Position;

4、最近邻查询

对于每个行程,列出离该车辆最近的三个点

WITH TripsTraj AS (
SELECT TripId, VehId, trajectory(Trip) AS Trajectory FROM Trips )
SELECT T.VehId, P1.PointId, P1.Distance
FROM TripsTraj T CROSS JOIN LATERAL (
SELECT P.PointId, T.Trajectory <-> P.Geom AS Distance
FROM Points P
ORDER BY Distance LIMIT 3 ) AS P1
ORDER BY T.TripId, T.VehId, P1.Distance;

对于每个行程,列出离该车辆最近的三辆车

SELECT T1.VehId AS VehId1, C2.VehId AS VehId2, C2.Distance
FROM Trips T1 CROSS JOIN LATERAL (
SELECT T2.VehId, minValue(T1.Trip <-> T2.Trip) AS Distance
FROM Trips T2
WHERE T1.VehId < T2.VehId AND period(T1.Trip) && period(T2.Trip)
ORDER BY Distance LIMIT 3 ) AS C2
ORDER BY T1.VehId, C2.VehId;

对于每个行程,列出该车辆在其三个最近邻居中的点

WITH TripsTraj AS (
SELECT TripId, VehId, trajectory(Trip) AS Trajectory FROM Trips ),
PointTrips AS (
SELECT P.PointId, T2.VehId, T2.TripId, T2.Distance
FROM Points P CROSS JOIN LATERAL (
SELECT T1.VehId, T1.TripId, P.Geom <-> T1.Trajectory AS Distance
FROM TripsTraj T1
ORDER BY Distance LIMIT 3 ) AS T2 )
SELECT T.VehId, T.TripId, P.PointId, PT.Distance
FROM Trips T CROSS JOIN Points P JOIN PointTrips PT
ON T.VehId = PT.VehId AND T.TripId = PT.TripId AND P.PointId = PT.PointId
ORDER BY T.VehId, T.TripId, P.PointId;

对于每组十辆不相交的车辆,列出在给定时间段内与给定的十辆车辆组的最小聚合距离的点

WITH Groups AS (
SELECT ((ROW_NUMBER() OVER (ORDER BY V.VehId))-1)/10 + 1 AS GroupId, V.VehId
FROM Vehicles V ),
SumDistances AS (
SELECT G.GroupId, P.PointId,
SUM(ST_Distance(trajectory(T.Trip), P.Geom)) AS SumDist
FROM Groups G, Points P, Trips T
WHERE T.VehId = G.VehId
GROUP BY G.GroupId, P.PointId )
SELECT S1.GroupId, S1.PointId, S1.SumDist
FROM SumDistances S1
WHERE S1.SumDist <= ALL (
SELECT SumDist
FROM SumDistances S2
WHERE S1.GroupId = S2.GroupId )
ORDER BY S1.GroupId, S1.PointId;