.. _mobilitydbintroduce_label: 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额外提供了 ``period``、 ``timestampset``、``periodset`` 三种基本时间类型。 period:表示一个时间段,有下限和上限,[] 代表边界时间包含在其中,()表示边界时间不包含在其中,下限的值可以小于等于上限的值。 .. code:: text 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:表示一组不同的单个时间值,必须是排序后的状态 .. code:: text 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,必须是排序后的状态 .. code:: text 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类型,能表示连续的运动轨迹。 .. code:: text 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``。 .. code:: bash cd /opt/libpqxx-6.4 ./configure --disable-documentation make && make install 安装osm2pgrouting,如果postgresql数据库是手动安装的,需要指定libpq.so的路径。 .. code:: bash cd osm2pgrouting-2.3.8/ cmake3 .. -DPOSTGRESQL_LIBRARIES=/opt/pg13/lib/libpq.so make && make install 安装pgRouting,需要依赖perl-version、boost1.56以上,boost库版本过低的可以手动下载boost库编译安装,。 .. code:: bash yum install -y perl-version cd /opt/pgrouting-3.4.1 mkdir build cd build make && make install 安装osm2pgsql。 .. code:: bash yum install -y osm2pgsql 首先创建需要的扩展,hstore可以在postgresql源码里找到直接编译安装。 .. code:: sql CREATE EXTENSION hstore; CREATE EXTENSION pgRouting; 导入数据,脚本执行过程中需要输入对应的账号密码数据库名字,xml文件为MobilityDB-BerlinMOD目录下的配置文件,此步骤为加载地图并将其转换为适用于 pgRouting 的可路由网络拓扑格式。 .. code:: bash osm2pgrouting -h localhost -p 13000 -U pg13 -f brussels.osm --dbname mobility -c mapconfig_brussels.xml 导入原始osm数据到数据库里,注意指定参数的大小写。 .. code:: bash osm2pgsql -c -H localhost -P 13000 -U pg13 -W -d mobility brussels.osm 在服务端执行sql脚本,此脚本的作用是利用pgRouting功能来进行两个节点之间的路径规划,再对路线进行数据填充,生成时态数据。 .. code:: bash 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、范围查询 ------------------ 列出在某个区域通过的车辆 .. code:: sql 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; 列出某个时间段内某个区域内的车辆 .. code:: sql 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; 列出在一段时间内都位于区域内的车辆对 .. code:: sql 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; 列出车辆首次访问某个点的时间 .. code:: sql 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、时态聚合查询 ------------------ 计算每个时间段处于活动状态的车辆数量 .. code:: sql 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 分钟的间隔提供行程的窗口时态计数 .. code:: sql 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、距离查询 ------------------ 列出车辆在这段时间内的总行驶距离 .. code:: sql 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; 列出每辆车和每个点之间的最小距离 .. code:: sql 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; 列出每对车辆之间的最小时间距离。 .. code:: sql 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; 列出每对行程之间的最近接近时间、距离和最短线 .. code:: sql 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 或更短的时间和位置。 .. code:: sql 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、最近邻查询 ------------------ 对于每个行程,列出离该车辆最近的三个点 .. code:: sql 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; 对于每个行程,列出离该车辆最近的三辆车 .. code:: sql 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; 对于每个行程,列出该车辆在其三个最近邻居中的点 .. code:: sql 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; 对于每组十辆不相交的车辆,列出在给定时间段内与给定的十辆车辆组的最小聚合距离的点 .. code:: sql 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; .. |map| image:: /../_static/images/map.png :width: 600px .. |move| image:: /../_static/images/move.png :width: 600px .. |select| image:: /../_static/images/select.png :width: 600px .. |step| image:: /../_static/images/step.png :width: 600px .. |trip1| image:: /../_static/images/trip1.png :width: 600px .. |trip2| image:: /../_static/images/trip2.png :width: 600px