Puede usar una consulta recursiva para explorar el vecino más cercano de cada punto a partir de cada extremo de líneas detectado que desea construir.
Prerrequisitos : prepare una capa postgis con sus puntos y otra con un solo objeto Multi-linetring que contenga sus carreteras. Las dos capas deben estar en el mismo CRS. Aquí está el código para el conjunto de datos de prueba que creé, modifíquelo según sea necesario. (Probado en postgres 9.2 y 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),
Aquí están los pasos :
Genere para cada punto la lista de cada vecino y su distancia que cumpla estos tres criterios.
- La distancia no debe exceder un umbral definido por el usuario (esto evitará la vinculación a un punto aislado)
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
),
- El camino directo no debe cruzar una carretera
graph as (
SELECt graph_full.*
FROM graph_full RIGHT JOIN
roads ON st_intersects(graph_full.geom,roads.geom) = false
),
La distancia no debe exceder una relación definida por el usuario de la distancia desde el vecino más cercano (esto debería acomodarse mejor a la digitalización irregular que la distancia fija) Esta parte fue realmente demasiado difícil de implementar, pegada al radio de búsqueda fijo
Llamemos a esta tabla "el gráfico"
Seleccione el punto de final de línea uniéndose al gráfico y manteniendo solo el punto que tenga exactamente una entrada en el 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),
Llamemos
fácil a esta tabla "eol" (final de línea) ? que la recompensa por hacer un gran gráfico pero que las cosas se vuelvan locas en el próximo paso
Configure una consulta recursiva que irá en ciclo de vecinos a vecinos a partir de cada eol
- Inicialice la consulta recursiva usando la tabla eol y agregando un contador para la profundidad, un agregador para la ruta y un constructor de geometría para construir las líneas.
- Pase a la siguiente iteración cambiando al vecino más cercano usando el gráfico y verificando que nunca retroceda usando la ruta
- Una vez finalizada la iteración, conserve solo la ruta más larga para cada punto de partida (si su conjunto de datos incluye una posible intersección entre las líneas esperadas, esa parte necesitaría más condiciones)
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
Llamemos a esta tabla "recurse_eol"
Mantenga solo la línea más larga para cada punto de inicio y elimine todas las rutas duplicadas exactas Ejemplo: las rutas 1,2,3,5 Y 5,3,2,1 son la misma línea descubierta por sus dos "finales de línea" 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
Comprueba manualmente los errores restantes (puntos aislados, líneas superpuestas, calles con formas extrañas)
Actualizado como se prometió, todavía no puedo entender por qué a veces la consulta recursiva no da exactamente el mismo resultado al comenzar desde el extremo opuesto de una misma línea, por lo que puede haber algún duplicado en la capa de resultados a partir de ahora.
Siéntase libre de preguntar, entiendo totalmente que este código necesita más comentarios. Aquí está la 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