Рисуете линию между точками на определенном расстоянии в PostGIS?

9

У меня есть данные точек на улицах, я хотел бы превратить эти точки в простые цветные линии. Любые указатели, как эта проблема может быть названа или какие-либо алгоритмы, которые могут помочь мне решить эту проблему? Точки вдоль улицы я бы хотел превратить в линии.

Я надеялся использовать PostGISфункции для этого, но я открыт для предложений, это данные из .shpфайла.

Edit1: обновил изображение, чтобы продемонстрировать идеальное решение этой проблемы.

Рисование линии будет основано исключительно на расстоянии между этими точками, и я ничего не смогу использовать для их группировки. В идеале это были бы точки на максимально указанном расстоянии вдоль проектируемой линии? И под проекционной линией я имею в виду найти 1-ю точку, затем следующую, ближайшую к ней, затем спроецировать линию и проверить, есть ли какие-либо точки на этой линии на максимальном расстоянии от любой из тех, которые уже находятся на линии.

Mahakala
источник
1
Какое программное обеспечение вы планируете использовать?
ArMoraer
Вы пытаетесь превратить их в тротуары?
DPSSpatial
Я надеялся использовать для этого функции PostGIS, но я открыт для предложений, это данные из файла .shp.
Махакала
1
Не могли бы вы показать, какие именно точки вы хотите соединить на своем чертеже или на другом чертеже? Это только две точки одновременно? Или три? Является ли расстояние между точками, которые должны быть соединены, всегда одинаковым или оно «просто» ниже определенного порога?
Питер Хорсбёлл Мёллер
1
Большое спасибо как @dbaston, так и MarHoff, у меня не будет времени проверить ваши идеи до конца апреля, я хотел бы разделить награду между ними, но мне нужно наградить это одному из вас, и dbaston дал мне несколько запросов, чтобы посмотреть на поэтому я приму его ответ. Спасибо всем, кто нашел время, чтобы ответить! Отличное сообщество, чтобы стать частью :-)
Махакала

Ответы:

8

Вы можете использовать рекурсивный запрос для исследования ближайшего соседа каждой точки, начиная с каждого обнаруженного конца линий, которые вы хотите построить.

Предварительные условия : подготовьте слой postgis с вашими точками, а другой - с одним объектом с несколькими линиями, содержащим ваши дороги. Два слоя должны быть на одном CRS. Вот код для созданного тестового набора данных, пожалуйста, измените его при необходимости. (Проверено на postgres 9.2 и postgis 2.1)

WITH RECURSIVE
points as (SELECT id, st_transform((st_dump(wkb_geometry)).geom,2154) as geom, my_comment as com FROM mypoints),
roads as (SELECT st_transform(ST_union(wkb_geometry),2154) as geom from highway),

введите описание изображения здесь

Вот шаги :

  1. Создайте для каждой точки список всех соседей и их расстояния, которые соответствуют трем критериям.

    • Расстояние не должно превышать определенный пользователем порог (это позволит избежать привязки к изолированной точке) введите описание изображения здесь
      graph_full as (
      SELECT a.id, b.id as link_id, a.com, st_makeline(a.geom,b.geom) as geom, st_distance(a.geom,b.geom) as distance
      FROM points a
      LEFT JOIN points b ON a.id<>b.id
      WHERE st_distance(a.geom,b.geom) <= 15
      ),
      
    • Прямой путь не должен переходить дорогу введите описание изображения здесь
      graph as (
      SELECt graph_full.*
      FROM graph_full RIGHT JOIN
      roads ON st_intersects(graph_full.geom,roads.geom) = false
      ),
      
    • Расстояние не должно превышать определяемое пользователем отношение расстояния от ближайшего соседа (это должно лучше соответствовать нерегулярной оцифровке, чем фиксированное расстояние) Эта часть была на самом деле слишком трудна для реализации, привязана к фиксированному радиусу поиска

    Давайте назовем эту таблицу "граф"

  2. Выберите точку конца линии, присоединившись к графику и сохранив только ту точку, которая имеет ровно одну запись на графике. введите описание изображения здесь

    eol as (
    SELECT points.* FROM
    points  JOIN
    (SELECT id, count(*) FROM graph 
    GROUP BY id
    HAVING count(*)= 1) sel
    ON points.id = sel.id),
    

    Давайте назовем эту таблицу "eol" (конец строки)
    легко? что награда за создание отличного графика, но на следующем шаге сумасшедшие вещи сойдут с ума

  3. Настройте рекурсивный запрос, который будет циклически переходить от соседей к соседям, начиная с каждого eol введите описание изображения здесь

    • Инициализируйте рекурсивный запрос, используя таблицу eol и добавив счетчик для глубины, агрегатор для пути и геометрический конструктор для построения линий.
    • Переходите к следующей итерации, переключаясь на ближайшего соседа с помощью графика и проверяя, что вы никогда не вернетесь назад, используя путь
    • После завершения итерации сохраните только самый длинный путь для каждой начальной точки (если ваш набор данных включает потенциальное пересечение между ожидаемыми линиями, для этой части потребуется больше условий)
    recurse_eol (id, link_id, depth, path, start_id, geom) AS (--initialisation
    SELECT id, link_id, depth, path, start_id, geom FROM (
        SELECT eol.id, graph.link_id,1 as depth,
        ARRAY[eol.id, graph.link_id] as path,
        eol.id as start_id,
        graph.geom as geom,
        (row_number() OVER (PARTITION BY eol.id ORDER BY distance asc))=1 as test
        FROM eol JOIn graph ON eol.id = graph.id 
        ) foo
    WHERE test = true
    
    UNION ALL ---here start the recursive part
    
    SELECT id, link_id, depth, path, start_id, geom  FROM (
        SELECT graph.id, graph.link_id, r.depth+1 as depth,
        path || graph.link_id as path,
        r.start_id,
        ST_union(r.geom,graph.geom) as geom,
        (row_number() OVER (PARTITION BY r.id ORDER BY distance asc))=1 as test
        FROM recurse_eol r JOIN graph ON r.link_id = graph.id AND NOT graph.link_id = ANY(path)) foo
    WHERE test = true AND depth < 1000), --this last line is a safe guard to stop recurring after 1000 run adapt it as needed
    

    Давайте назовем эту таблицу "recurse_eol"

  4. Оставьте только самую длинную линию для каждой начальной точки и удалите каждый точный повторяющийся путь. Пример: пути 1,2,3,5 И 5,3,2,1 - это одна и та же линия, обнаруженная с помощью двух разных «концов линии»

    result as (SELECT start_id, path, depth, geom FROM
    (SELECT *,
    row_number() OVER (PARTITION BY array(SELECT * FROM unnest(path) ORDER BY 1))=1 as test_duplicate,
    (max(depth) OVER (PARTITION BY start_id))=depth as test_depth
    FROM recurse_eol) foo
    WHERE  test_depth = true AND test_duplicate = true)
    
    SELECT * FROM result
  5. Вручную проверяет оставшиеся ошибки (изолированные точки, перекрывающиеся линии, странные улицы)


Обновленный, как и было обещано, я до сих пор не могу понять, почему иногда рекурсивный запрос не дает точно такой же результат при запуске с противоположного eol той же строки, поэтому некоторый дубликат может остаться в результирующем слое на данный момент.

Не стесняйтесь спрашивать, я полностью понимаю, что этот код нуждается в большем количестве комментариев. Вот полный запрос:

WITH RECURSIVE
points as (SELECT id, st_transform((st_dump(wkb_geometry)).geom,2154) as geom, my_comment as com FROM mypoints),
roads as (SELECT st_transform(ST_union(wkb_geometry),2154) as geom from highway),

graph_full as (
    SELECT a.id, b.id as link_id, a.com, st_makeline(a.geom,b.geom) as geom, st_distance(a.geom,b.geom) as distance
    FROM points a
    LEFT JOIN points b ON a.id<>b.id
    WHERE st_distance(a.geom,b.geom) <= 15
    ),

graph as (
    SELECt graph_full.*
    FROM graph_full RIGHT JOIN
    roads ON st_intersects(graph_full.geom,roads.geom) = false
    ),

eol as (
    SELECT points.* FROM
    points  JOIN
        (SELECT id, count(*) FROM graph 
        GROUP BY id
        HAVING count(*)= 1) sel
    ON points.id = sel.id),


recurse_eol (id, link_id, depth, path, start_id, geom) AS (
    SELECT id, link_id, depth, path, start_id, geom FROM (
        SELECT eol.id, graph.link_id,1 as depth,
        ARRAY[eol.id, graph.link_id] as path,
        eol.id as start_id,
        graph.geom as geom,
        (row_number() OVER (PARTITION BY eol.id ORDER BY distance asc))=1 as test
        FROM eol JOIn graph ON eol.id = graph.id 
        ) foo
    WHERE test = true

UNION ALL
    SELECT id, link_id, depth, path, start_id, geom  FROM (
        SELECT graph.id, graph.link_id, r.depth+1 as depth,
        path || graph.link_id as path,
        r.start_id,
        ST_union(r.geom,graph.geom) as geom,
        (row_number() OVER (PARTITION BY r.id ORDER BY distance asc))=1 as test
        FROM recurse_eol r JOIN graph ON r.link_id = graph.id AND NOT graph.link_id = ANY(path)) foo
    WHERE test = true AND depth < 1000),

result as (SELECT start_id, path, depth, geom FROM
    (SELECT *,
    row_number() OVER (PARTITION BY array(SELECT * FROM unnest(path) ORDER BY 1))=1 as test_duplicate,
    (max(depth) OVER (PARTITION BY start_id))=depth as test_depth
    FROM recurse_eol) foo
WHERE  test_depth = true AND test_duplicate = true)

SELECT * FROM result

MarHoff
источник
Привет @MarHoff, спасибо за ваш ответ, у меня есть кое-что, чтобы пойти после. Я не ожидал полного решения, просто указатели, где искать ответы. Я хочу понять это больше, и я буду продолжать копать и, возможно, у меня будет больше вопросов позже. Мне нужно понять ваш алгоритм, и это все равно займет у меня некоторое время :)
Махакала,
Есть рабочий сценарий, предварительный просмотр здесь qgiscloud.com/MarHoff/test_qgiscloud_bis осталось небольшое предостережение для дедупликации ... Нет больше награды, больше давления, я думаю, поэтому я выпущу релиз, когда смогу. Эта головоломка была забавной, хотя
MarHoff
спасибо @MarHoff, если бы я мог разделить эту награду, я не могу понять, как я могу наградить вас какими-либо очками, но большое спасибо за внимание к этому и вашему доказательству. Выглядит искренне :)
Махакала
Выполнено. Спасибо за загадку и извините за разглагольствование. Если другой ответ сделал это для вас, тогда все в порядке, иногда просто лучше ... Мой ответ был, возможно, немного продуман. Хотя хороший пример использования CTE + рекурсивный запрос + функция Windows + postgis для одного запроса;)
MarHoff
8

Как указывает @FelixIP, первый шаг - найти точки, которые будут составлять каждую линию. Вы можете сделать это, вызвав ST_ClusterWithin с вашим максимальным разделительным расстоянием:

SELECT
  row_number() OVER () AS cid, 
  (ST_Dump(geom)).geom 
FROM (
  SELECT unnest(st_clusterwithin(geom, 0.05)) AS geom 
  FROM inputs) sq

Затем вам нужно будет использовать некоторую эвристику, чтобы построить линию через все точки в каждом кластере. Например, если вы можете предположить, что нужные линии являются Y-монотонными, вы можете отсортировать точки в каждом кластере и передать их в ST_MakeLine . Объединяя все это, выглядело бы так:

SELECT 
  ST_MakeLine(geom ORDER BY ST_Y(geom)) AS geom
FROM (
  SELECT row_number() OVER () AS cid, 
  (ST_Dump(geom)).geom FROM (
    SELECT unnest(st_clusterwithin(geom, 0.05)) AS geom 
    FROM inputs) sq) ssq 
GROUP BY cid
dbaston
источник
Путь, но Y-монотонный (или даже переключение между X / Y-монотонным) подход не будет работать хорошо, если набор данных содержит изогнутую дорогу. Это так? Алгоритм заказа является самой сложной частью этого вопроса ИМХО.
MarHoff
@MarHoff: да, изогнутые дороги будут проблемой, но я пытаюсь преобразовать большинство данных автоматически, а остальное нужно будет сделать вручную. Или я продолжу копаться в теме, чтобы найти решение, но это может занять больше времени, чем заставить кого-то исправить оставшиеся данные. Мне нужно оценить результаты, чтобы иметь возможность принять решение. Спасибо за указание на это!
Махакала
Статут настроен Я только что подумал о трюке, который мне нужно проверить ...
MarHoff
Есть ли надежный способ сделать это, который не включает в себя попытки всех возможных упорядочений точек и определения, какая из них дает наименьшую общую длину?
dbaston
Если эти наборы точек всегда следуют за дорогами, вы проецируете положение точки на сегмент дороги (ST_Line_Locate_Point), а затем упорядочиваете точки по результату.
Трэвис