Desenhando uma linha entre pontos a uma distância específica no PostGIS?


9

Tenho dados de pontos ao longo das ruas, gostaria de transformar esses pontos em simples linhas coloridas. Qualquer ponteiro como esse problema pode ser chamado ou qualquer algoritmo que possa me ajudar a resolver isso? Pontos ao longo da rua que eu gostaria de transformar em linhas.

Eu esperava usar PostGISfunções para fazer isso, mas estou aberto a sugestões, esses são dados de um .shparquivo.

Edit1: Atualizado a imagem para demonstrar a solução ideal desse problema.

Desenhar a linha seria puramente baseado na distância entre esses pontos; não há mais nada que eu possa usar para agrupá-los. Idealmente, isso seria pontos à distância máxima especificada ao longo da linha projetada? E por linha projetada, quero dizer, encontrar o 1º ponto e o próximo mais próximo, em seguida, projetar uma linha e verificar se há algum ponto nessa linha a uma distância máxima de qualquer um dos que já estão na linha.


11
Qual software você planeja usar?
ArMoraer

você está tentando transformá-las em calçadas?
amigos estão dizendo sobre dpsspatial

Eu esperava usar as funções do PostGIS para fazer isso, mas estou aberto a sugestões. Esses são dados de um arquivo .shp.
Mahakala

11
Você poderia mostrar exatamente quais pontos deseja conectar no seu desenho ou em outro desenho? São apenas dois pontos de cada vez? Ou três? A distância entre os pontos que devem ser conectados é sempre a mesma ou ou é "apenas" abaixo de um determinado limite?
Peter Horsbøll Møller

11
Muito obrigado a @dbaston e MarHoff, não terei tempo para testar suas idéias até o final de abril, gostaria de poder dividir a recompensa entre eles, mas precisarei atribuir isso a 1 de vocês e o dbaston me fez algumas perguntas para analisar então eu vou aceitar a resposta dele. Thx todo mundo que teve tempo para responder! Grande comunidade para fazer parte :-)
Mahakala

Respostas:


8

O que outras pessoas estão dizendo

Você pode usar uma consulta recursiva para explorar o vizinho mais próximo de cada ponto, começando em cada extremidade detectada das linhas que deseja construir.

Pré - requisitos : prepare uma camada postgis com seus pontos e outra com um único objeto Multi-linestring contendo suas estradas. As duas camadas devem estar no mesmo CRS. Aqui está o código para o conjunto de dados de teste que eu criei, modifique-o conforme necessário. (Testado no postgres 9.2 e 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),

insira a descrição da imagem aqui

Aqui estão os passos :

  1. Gere para cada ponto a lista de todos os vizinhos e a distância deles que atendem a esses três critérios.

    • A distância não deve exceder um limite definido pelo usuário (isso evitará vincular ao ponto isolado) insira a descrição da imagem aqui
      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
      ),
    • Caminho direto não deve atravessar uma estrada insira a descrição da imagem aqui
      graph as (
      SELECt graph_full.*
      FROM graph_full RIGHT JOIN
      roads ON st_intersects(graph_full.geom,roads.geom) = false
      ),
    • A distância não deve exceder uma proporção definida pelo usuário da distância do vizinho mais próximo (isso deve acomodar melhor a digitalização irregular do que a distância fixa) Essa parte era realmente muito difícil de implementar, aderida ao raio de pesquisa fixo

    Vamos chamar esta tabela de "o gráfico"

  2. Selecione o ponto final da linha juntando-se ao gráfico e mantendo apenas o ponto que possui exatamente uma entrada no gráfico. insira a descrição da imagem aqui

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

    Vamos chamar esta tabela de "eol" (fim de linha)
    fácil? que a recompensa por fazer um ótimo gráfico, mas as coisas de espera ficarem loucas na próxima etapa

  3. Configure uma consulta recursiva que alternará de vizinhos para vizinhos a partir de cada eol insira a descrição da imagem aqui

    • Inicialize a consulta recursiva usando a tabela eol e adicionando um contador para a profundidade, um agregador para o caminho e um construtor de geometria para construir as linhas
    • Vá para a próxima iteração alternando para o vizinho mais próximo usando o gráfico e verificando se você nunca retrocede usando o caminho
    • Após o término da iteração, mantenha apenas o caminho mais longo para cada ponto de partida (se o conjunto de dados incluir interseção em potencial entre as linhas esperadas, essa parte precisaria de mais condições)
    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

    Vamos chamar esta tabela de "recurse_eol"

  4. Mantenha apenas a linha mais longa para cada ponto inicial e remova todos os caminhos duplicados exatos Exemplo: os caminhos 1,2,3,5 E 5,3,2,1 são a mesma linha descoberta por seus dois "final de linha" diferentes

    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. Verifica manualmente os erros restantes (pontos isolados, linhas sobrepostas, ruas de formas estranhas)


Atualizado como prometido, ainda não consigo descobrir por que a consulta recursiva às vezes não fornece exatamente o mesmo resultado ao iniciar a partir do eol oposto de uma mesma linha, para que algumas duplicatas possam permanecer na camada de resultados a partir de agora.

Sinta-se à vontade para perguntar que entendo totalmente que esse código precisa de mais comentários. Aqui está a consulta completa:

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

Olá @MarHoff, obrigado pela sua resposta. Tenho algo a seguir. Eu não esperava uma solução completa, apenas um indicador de onde procurar respostas. Quero entender mais isso e continuarei pesquisando e provavelmente tenho mais perguntas mais tarde. Eu preciso entender seu algoritmo e isso vai me levar algum tempo de qualquer maneira :)
Mahakala

Tenho um script de trabalho, visualize aqui qgiscloud.com/MarHoff/test_qgiscloud_bis uma pequena ressalva para a eliminação da duplicação ... Não há mais recompensas, não há mais pressão, acho, então lançarei a versão quando puder. Este puzzle foi divertido embora
MarHoff

obrigado @MarHoff, se eu pudesse dividir essa recompensa, não vejo como posso premiá-lo com alguns pontos, mas muito obrigado por analisar isso e sua prova. Parece :) genuína
Mahakala

Feito. Obrigado pelo quebra-cabeça e desculpe-me por reclamar. Se outra resposta fez isso para você, então está totalmente ok, em algum momento simples é o melhor ... Minha resposta foi talvez um pouco demais. Embora bom exemplo de como usar CTE + recursiva consulta + do Windows função PostGIS + em uma única consulta;)
MarHoff

8

Como o @FelixIP aponta, o primeiro passo é encontrar os pontos que formarão cada linha. Você pode fazer isso chamando ST_ClusterWithin com sua distância máxima de separação:

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

Em seguida, você precisará usar alguma heurística para criar uma linha através de todos os pontos em cada cluster. Por exemplo, se você puder assumir que as linhas desejadas sejam monotônicas em Y, poderá classificar os pontos em cada cluster e alimentá-los no ST_MakeLine . Combinando que todos juntos ficariam assim:

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

Caminho a percorrer, mas a abordagem monotônica Y (ou mesmo alternar entre monotonia X / Y) não funcionará bem se o conjunto de dados contiver estrada curva. É esse o caso? Algoritmo de pedidos é a parte mais difícil desta pergunta IMHO.
MarHoff # 7/16

@ MarHoff: sim estradas curvas serão um problema, mas estou tentando obter a maioria dos dados transformados automaticamente e o restante precisará ser feito manualmente. Ou continuarei pesquisando mais sobre o tópico para descobrir a solução, mas pode levar mais tempo do que conseguir que alguém conserte os dados restantes. Vou precisar avaliar os resultados para poder decidir. Obrigado por apontar isso!
Mahakala

Estatuto sintonizado Eu apenas pensei de um truque que eu preciso verificar ...
MarHoff

Existe alguma maneira robusta de fazer isso que não envolva tentar todos os pedidos possíveis de pontos e descobrir qual deles oferece o menor comprimento total?
Dbaston 07/04

Se esses conjuntos de pontos sempre seguirem as estradas, você projeta a posição do ponto no segmento de estrada (ST_Line_Locate_Point) e ordena os pontos pelo resultado.
Travis
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.