SQL 服务器/C# 如何找到与多边形相交的线串的日期时间
SQL Server / C# How to find the DateTime a Linestring Intersected a Polygon
我正在构建一个跟踪系统,想知道有人何时进入或离开某个区域(区域或等级)。我们以 5 秒的间隔收集 GPS 坐标,它们与读取日期时间一起存储为 lat/long 和地理列。区域和层的地理围栏作为多边形存储在地理列中。区域可以包含层,层可以包含子层。在图中,T3 是一个子层。如果某人在 T3 中,则他们也在 T2 中。右边一组非常微弱的点也是将要发生的事情的一个例子。人们将整天进出区域和层级。
最终结果是看到一个activity列表
- 6:00:00 上午
进入 1 区
- 在 6:05:00 上午
进入 T2
- 在 7:13:12 上午
进入 T3
- 在 7:49:32 上午
退出 T3
- 等等
有没有办法找到某人 enters/exits 区域或层级(图像中的绿色圆圈)所在的点,并从 GPS 点 table 获取阅读日期时间?
我已经使用 STContains 让所有人都进入地理围栏。我考虑过使用这种方法,通过读取日期时间来排序,如果我找到 0 然后 1,他们进入,相反地,1 然后 0,他们退出。我认为这是太多的循环,希望有更好的方法。
我也尝试过 STIntersection 但我找不到将这些点与日期时间联系起来的方法。如果有人站着不动超过5秒,我会得到两个相同的点,坐标匹配不好。
看来 DBGeography C# class 具有与 SQL 服务器相同的功能。这可以用 C# 而不是 SQL 来完成吗?
这里有很多挑战,但我认为它们都是可行的。
我认为您已经解决了第一个问题,即了解给定数据点位于哪些区域。 STContains()
或 STIntersects()
.
第二个是您实际上是在寻找基于时间的连续性集群。假设你有可靠的数据收集,这也是可以解决的。一旦你得到了一组(人,地区,时间戳)元组(从上面),它就是一个差距和孤岛问题。玩具解决方案如下所示:
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。因此,对于在同一个岛屿中具有同等资格的行,它们的差异应该是恒定的。这为我们提供了一个方便的分组依据值。
我正在构建一个跟踪系统,想知道有人何时进入或离开某个区域(区域或等级)。我们以 5 秒的间隔收集 GPS 坐标,它们与读取日期时间一起存储为 lat/long 和地理列。区域和层的地理围栏作为多边形存储在地理列中。区域可以包含层,层可以包含子层。在图中,T3 是一个子层。如果某人在 T3 中,则他们也在 T2 中。右边一组非常微弱的点也是将要发生的事情的一个例子。人们将整天进出区域和层级。
最终结果是看到一个activity列表
- 6:00:00 上午 进入 1 区
- 在 6:05:00 上午 进入 T2
- 在 7:13:12 上午 进入 T3
- 在 7:49:32 上午 退出 T3
- 等等
有没有办法找到某人 enters/exits 区域或层级(图像中的绿色圆圈)所在的点,并从 GPS 点 table 获取阅读日期时间?
我已经使用 STContains 让所有人都进入地理围栏。我考虑过使用这种方法,通过读取日期时间来排序,如果我找到 0 然后 1,他们进入,相反地,1 然后 0,他们退出。我认为这是太多的循环,希望有更好的方法。
我也尝试过 STIntersection 但我找不到将这些点与日期时间联系起来的方法。如果有人站着不动超过5秒,我会得到两个相同的点,坐标匹配不好。
看来 DBGeography C# class 具有与 SQL 服务器相同的功能。这可以用 C# 而不是 SQL 来完成吗?
这里有很多挑战,但我认为它们都是可行的。
我认为您已经解决了第一个问题,即了解给定数据点位于哪些区域。 STContains()
或 STIntersects()
.
第二个是您实际上是在寻找基于时间的连续性集群。假设你有可靠的数据收集,这也是可以解决的。一旦你得到了一组(人,地区,时间戳)元组(从上面),它就是一个差距和孤岛问题。玩具解决方案如下所示:
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。因此,对于在同一个岛屿中具有同等资格的行,它们的差异应该是恒定的。这为我们提供了一个方便的分组依据值。