SQL Server / C # Как найти DateTime, когда линейная линия пересекает многоугольник - PullRequest
0 голосов
/ 04 мая 2018

Я строю систему отслеживания и хочу узнать, когда кто-то вошел или покинул область (Зона или Уровень). Мы собираем GPS-координаты с 5-секундными интервалами, и они сохраняются как в широте / длине, так и в столбце географии вместе со временем считывания даты. Геозоны для зон и ярусов хранятся в виде полигонов в столбце географии. Зоны могут содержать уровни, а уровни могут содержать подуровни. На рисунке T3 является подуровнем. Если кто-то находится в T3, они также находятся в T2. Очень слабый набор точек справа также является примером того, что произойдет. Люди будут входить и выходить из зон и ярусов весь день.

Example Geofence Layout

Конечным результатом является просмотр списка действий

  1. Вступил в зону 1 в 6:00:00 утра
  2. Поступил в Т2 в 6:05:00
  3. Поступил в Т3 в 7:13:12
  4. Выход из T3 в 7:49:32
  5. и т.д.

Есть ли способ найти точки, в которых кто-либо входит / выходит из зоны или уровня (зеленые кружки на изображении), и получить время считывания даты из таблицы точек GPS?

Я использовал STContains, чтобы собрать всех людей в геозону. Я думал об использовании этого подхода, упорядочив, считывая дату и время, и если я найду 0, то 1, они войдут, и наоборот, 1, а 0, они выйдут. Я думаю, что это слишком много петель и надеюсь, что есть лучший способ.

Я также попробовал STIntersection, но не могу найти способ привязать точки к дате. Если кто-то стоит на месте более 5 секунд, я получу две одинаковые точки, и сопоставление по координатам не работает.

Похоже, что класс DBGeography C # имеет те же функции, что и сервер SQL. Можно ли это сделать в C # вместо SQL?

1 Ответ

0 голосов
/ 05 мая 2018

У вас здесь множество проблем, но я думаю, что все они выполнима.

Я думаю, что вы уже решили первое: узнать, в каких регионах находится данная точка данных. Либо STContains(), либо STIntersects().

Во-вторых, вы, по сути, ищете кластеры смежности, основанной на времени. Предполагая, что у вас есть надежный сбор данных, это также решаемо. Если у вас есть набор кортежей (Person, Region, Timestamp) (сверху), это проблема пробелов и островов. Игрушечное решение представлено ниже:

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 для тех же критериев. Таким образом, их различие должно быть постоянным для строк, которые квалифицируются как эквивалентные на том же острове. Это дает нам удобное значение для группировки по.

...