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),
Aqui estão os passos :
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)
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
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"
Selecione o ponto final da linha juntando-se ao gráfico e mantendo apenas o ponto que possui exatamente uma entrada no gráfico.
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
Configure uma consulta recursiva que alternará de vizinhos para vizinhos a partir de cada eol
- 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"
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
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