SQL Server / C#如何找到一个与多边形相交的线串的DateTime

时间:2018-05-04 18:10:06

标签: c# sql-server sqlgeography

我正在建立一个跟踪系统,想要找到有人进入或离开某个区域(区域或层)的时间。我们以5秒的间隔收集GPS坐标,它们被存储为纬度/经度和地理列以及读取日期时间。区域和层的地理围栏将作为多边形存储在地理列中。区域可以包含层,层可以包含子层。在图中,T3是一个子层。如果有人在T3,他们也在T2。右边非常微弱的点集也是将要发生的事情的一个例子。人们将整天进出区域和区域。

Example Geofence Layout

最终结果是查看活动列表

  1. 于上午6:00:00进入第1区
  2. 于上午6:05:00进入T2
  3. 于上午7:13:12进入T3
  4. 上午7:49:32退出T3
  5. 有没有办法找到有人进入/退出区域或层级(图像中的绿色圆圈)并从GPS点数表中获取阅读日期时间的点?

    我使用STContains让所有人都在地理围栏中。我已经考虑过使用这种方法,通过阅读日期时间来排序,如果我找到0然后是1,他们就进入了,相反是1然后是0,他们退出了。我认为这是太多的循环,希望有更好的方法。

    我也尝试过STIntersection,但是我找不到一种方法可以将这些点与日期时间联系起来。如果有人静止超过5秒钟,我会得到两个相同的点,坐标上的匹配效果不佳。

    似乎DBGeography C#类与SQL server具有相同的功能。这可以用C#而不是SQL来完成吗?

1 个答案:

答案 0 :(得分:1)

你在这里遇到了许多挑战,但我认为他们都是可行的。

我认为您已经解决了第一个知道给定数据点所在区域的问题。 STContains()STIntersects()

第二,你基本上是在寻找基于时间的连续集群。假设您有可靠的数据收集,这也是可以解决的。一旦你获得了一组(人物,地区,时间戳)元组(从上面开始),它就是一个Gaps和Islands问题。玩具解决方案如下:

IF OBJECT_ID('tempdb.dbo.#observations') IS NOT NULL
    DROP TABLE #observations;
IF OBJECT_ID('tempdb.dbo.#regions') IS NOT NULL
    DROP TABLE #regions;

CREATE TABLE #observations (
    ObservationID INT NOT NULL IDENTITY,
        CONSTRAINT PK_Observations PRIMARY KEY CLUSTERED (ObservationID),
    PersonID      INT NOT null,
    Point         GEOMETRY NOT null,
    TS            DATETIME2(0) NOT NULL CONSTRAINT DF_Observations_TS DEFAULT SYSUTCDATETIME()
);

CREATE TABLE #regions (
    RegionID INT NOT NULL IDENTITY,
        CONSTRAINT PK_Regions PRIMARY KEY CLUSTERED (RegionID),
    Area GEOMETRY NOT NULL
);

INSERT INTO #regions
(
    Area
)
VALUES
( geometry::STGeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))', 0) ),
( geometry::STGeomFromText('POLYGON((0 0, 2 0, 2 2, 0 2, 0 0))', 0) ),
( geometry::STGeomFromText('POLYGON((0 0, 4 0, 4 4, 0 4, 0 0))', 0) );

INSERT INTO #observations
(
    PersonID ,
    Point ,
    TS
)
VALUES
(   1 ,
    geometry::Point(0.5, 0.5, 0) ,
    '2018-01-01 00:00:00'
),
(   1 ,
    geometry::Point(1.5, 1.5, 0) ,
    '2018-01-01 00:00:05'
),
(   1 ,
    geometry::Point(2.5, 2.5, 0) ,
    '2018-01-01 00:00:10'
),
(   1 ,
    geometry::Point(3.5, 3.5, 0) ,
    '2018-01-01 00:00:15'
),
(   1 ,
    geometry::Point(4.5, 4.5, 0) ,
    '2018-01-01 00:00:20'
),
(   1 ,
    geometry::Point(0.5, 0.5, 0) ,
    '2018-01-01 01:00:00'
),
(   1 ,
    geometry::Point(1.5, 1.5, 0) ,
    '2018-01-01 01:00:05'
),
(   1 ,
    geometry::Point(2.5, 2.5, 0) ,
    '2018-01-01 01:00:10'
),
(   1 ,
    geometry::Point(3.5, 3.5, 0) ,
    '2018-01-01 01:00:15'
),
(   1 ,
    geometry::Point(4.5, 4.5, 0) ,
    '2018-01-01 01:00:20'
),
(   2 ,
    geometry::Point(3.5, 3.5, 0) ,
    '2018-01-01 00:00:00'
),
(   2 ,
    geometry::Point(3.5, 3.5, 0) ,
    '2018-01-01 00:00:05'
),
(   2 ,
    geometry::Point(3.5, 3.5, 0) ,
    '2018-01-01 00:00:10'
),
(   2 ,
    geometry::Point(3.6, 3.6, 0) ,
    '2018-01-01 00:00:15'
),
(   2 ,
    geometry::Point(4.5, 4.5, 0) ,
    '2018-01-01 00:00:20'
);

WITH cte AS (
    SELECT o.ObservationID,
           o.PersonID ,
           o.TS ,
           r.RegionID,
           (DATEDIFF(SECOND, '2017-01-01', o.ts)/5) - ROW_NUMBER() OVER (PARTITION BY o.PersonID, r.RegionID ORDER BY o.ts) AS gid,
           DATEDIFF(SECOND, '2017-01-01', o.ts)/5 AS diff,
           ROW_NUMBER() OVER (PARTITION BY o.PersonID, r.RegionID ORDER BY o.ts) AS rn
    FROM #observations AS o
    JOIN #regions AS r
        ON o.Point.STIntersects(r.Area) = 1
    --JOIN #timestamps AS ts
    --    ON ts.TS = o.TS
)
SELECT cte.PersonID, cte.RegionID, MIN(ts), MAX(ts)
FROM cte
GROUP BY cte.PersonID ,
         cte.RegionID,
         cte.gid;

技巧(如果有的话)意识到,row_number()对于岛中的每个成员递增1,并且对于相同的标准,(秒数)/ 5也应该递增1。因此,对于在同一岛中具有等效性的行,它们的差异应该是恒定的。这给了我们分组的便利价值。