【问题标题】:Grouping variably contiguous timestamped records with spacing threshold使用间距阈值对可变连续的时间戳记录进行分组
【发布时间】:2014-01-05 01:38:19
【问题描述】:

我有一系列带有时间戳的间歇性 GPS 坐标。我正在使用 PostGIS 将它们渲染到地图画布上。要渲染它们,需要使用 PostGIS 中的 ST_MakeLine() 聚合函数将这些点聚合成线,从而在地图上留下 GPS 数据缺失的间隙。数据不一定按设备顺序到达。

示例序列如下所示:

ID | Timestamp              | Location
--------------------------------------
1  | 2013-11-12 03:31:31    | (1,2)   
3  | 2013-11-12 03:31:34    | (1,3)   
7  | 2013-11-12 03:31:37    | (1,4)  
4  | 2013-11-12 03:31:43    | (1,5)   
2  | 2013-11-12 03:31:55    | (1,6)   
16 | 2013-11-12 03:33:22    | (1,7)   
22 | 2013-11-12 03:33:28    | (1,8)   
18 | 2013-11-12 03:33:32    | (1,9)   

分组条件为:

  • 如果与上一条记录的差距 > 30 秒或
  • 如果自该组中的第一个记录以来的时间

PostGIS 中的 ST_MakeLine() 函数将产生必要的线条,问题在于正确地对线条进行分组。

基于此,以上将产生:

Start               | End                 | ST_MakeLine(?)
----------------------------------------------------------------------------
2013-11-12 03:31:31 | 2013-11-12 03:31:43 | LINE((1,2),(1,3),(1,4),(1,5))
2013-11-12 03:31:43 | 2013-11-12 03:31:55 | LINE((1,5),(1,6))
2013-11-12 03:33:22 | 2013-11-12 03:33:32 | LINE((1,7),(1,8),(1,9))

这似乎是大多数其他“连续选择”问题所引用的“孤岛和间隙”问题的一种变体,但扭曲的是排序不规则,因此这些解决方案似乎并不适用。

我目前正在 SQL 之外处理数据以生成序列,但这会导致多次往返,如果可以的话,我想避免。

示例数据的SQLFiddle:http://sqlfiddle.com/#!15/1ff93/7

【问题讨论】:

  • 您是在寻找一个简单的查询,还是对 PL/pgSQL 开放(使用 PL/pgSQL,您可以在数据库服务器内对一个计算块和一系列查询进行分组,从而具有一种过程语言和 SQL 的易用性,但大大节省了客户端/服务器通信开销。)请参阅postgresql.org/docs/9.3/static/plpgsql.html
  • 独立查询更可取,但过程将在紧要关头工作。性能是决定性因素。
  • 你的分组条件是否正确?如果条件都需要> 这么多秒,我在同一组中看到 ID 1 和 ID 3 时遇到问题。你的意思是<
  • 是的,< 会更好读,更新。其目的是应将连续的坐标组分解为 15 秒的块,并具有共享的结束/开始坐标(示例中为 (1,5))。
  • 不确定第二条规则是什么意思。如果你有数据进入(比如说)每 1 秒,那么它会产生两个元素的线段,因为 1 小于 15 秒,所以你会有(a,b), (c,d)(c,d), (e,f)(e,f), (g,h) 等。这就是你想?它似乎不是来自示例。

标签: sql postgresql postgis


【解决方案1】:

我最终采取了两部分的方法来解决这个问题:

  • 将“组 ID”附加到每一行的存储过程
  • 一个简单的聚合查询

性能明显优于在数据库外部执行(45s vs 2.8s)

所以,给定一个由以下创建的表:

CREATE TABLE locations (
  id SERIAL PRIMARY KEY,
  ts TIMESTAMP WITHOUT TIME ZONE,
  location GEOMETRY(Point,4326)
);

以下函数将遍历表并将“组 ID”附加到每一行:

CREATE FUNCTION group_locations(
  IN scan_start_time TIMESTAMP WITHOUT TIME ZONE,
  IN max_time_gap INTERVAL, 
  IN max_line_duration INTERVAL)
RETURNS TABLE(
  out_geom GEOMETRY, 
  out_ts TIMESTAMP WITHOUT TIME ZONE, 
  out_group_id INTEGER) AS
$BODY$
DECLARE
  r locations%ROWTYPE;
  gid INTEGER;
  lastts TIMESTAMP;
  startts TIMESTAMP;
BEGIN
  gid := 0;
  lastts := NULL;
  startts := NULL;

  FOR r IN 
    SELECT * FROM locations 
    WHERE ts > scan_start_time
    ORDER BY ts ASC
  LOOP
    out_ts := r.ts;
    out_geom := r.location;
    out_group_id := gid;

    IF startts IS NULL OR lastts IS NULL THEN
      startts := r.ts;
    ELSIF r.ts - lastts >= max_time_gap THEN
      -- If we've hit a space in our data, bump the group id up
      -- and remember the start time for this group
      gid := gid+1;
      out_group_id = gid;
      startts := r.ts;
    ELSIF r.ts - startts >= max_line_duration THEN
      -- First, emit the current row to end the group
      RETURN NEXT;
      -- Then, bump the group id and start time, we will
      -- re-emit the same row with a higher group_id below
      gid := gid+1;
      out_group_id := gid;
      startts := r.ts;
    END IF;
    -- Emit the current row with the group_id appended
    RETURN NEXT;
    lastts := r.ts;
  END LOOP;
  RETURN;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;

如果运行我的示例数据,结果是:

out_ts              | out_geom | out_group_id
---------------------------------------------
2013-11-12 03:31:31 | (1,2)    | 0
2013-11-12 03:31:34 | (1,3)    | 0
2013-11-12 03:31:37 | (1,4)    | 0
2013-11-12 03:31:43 | (1,5)    | 0
2013-11-12 03:31:43 | (1,5)    | 1
2013-11-12 03:31:55 | (1,6)    | 1
2013-11-12 03:33:22 | (1,7)    | 2
2013-11-12 03:33:28 | (1,8)    | 2
2013-11-12 03:33:32 | (1,9)    | 2

然后,这个过程的输出可以简单地分组和聚合:

SELECT ST_Makeline(out_geom) AS geom,MIN(out_ts) AS start,MAX(out_ts) AS finish
FROM group_locations(
       NOW() AT TIME ZONE 'UTC' - '10 days'::INTERVAL,  -- how far back to look
       '30 seconds'::INTERVAL,  -- maximum gap allowed before creating a break
       '15 seconds'::INTERVAL  -- maximum duration allowed before forcing a break
)
GROUP BY out_group_id;

函数执行得相当快,至少比在外部执行相同的逻辑要好一个数量级。缺点是结果没有被索引,因此在进一步的查询中直接使用它们并不是特别高效。它运行大约 O(2N) 时间,第一次扫描附加组 ID,然后第二次扫描聚合。

我的最终解决方案每隔几分钟执行一次上述操作,以刷新完全索引的“calculated_tracks”表。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-01-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-03-07
    • 2017-02-08
    • 1970-01-01
    相关资源
    最近更新 更多