È possibile utilizzare una query ricorsiva per esplorare il vicino più vicino di ciascun punto a partire da ciascuna fine rilevata delle linee che si desidera costruire.
Prerequisiti : prepara un livello postgis con i tuoi punti e un altro con un singolo oggetto multi-linestring contenente le tue strade. I due livelli devono essere sullo stesso CRS. Ecco il codice per il set di dati di test che ho creato, modificarlo secondo necessità. (Testato su 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),
Ecco i passaggi :
Genera per ogni punto l'elenco di tutti i vicini e la loro distanza che soddisfano questi tre criteri.
- La distanza non deve superare una soglia definita dall'utente (questo eviterà il collegamento a un punto isolato)
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
),
- Il percorso diretto non deve attraversare una strada
graph as (
SELECt graph_full.*
FROM graph_full RIGHT JOIN
roads ON st_intersects(graph_full.geom,roads.geom) = false
),
La distanza non deve superare un rapporto definito dall'utente della distanza dal vicino più vicino (ciò dovrebbe consentire una migliore digitalizzazione irregolare rispetto alla distanza fissa) Questa parte era in realtà troppo difficile da implementare, attaccata al raggio di ricerca fisso
Chiamiamo questa tabella "il grafico"
Seleziona la fine del punto di linea unendo al grafico e mantenendo solo il punto che ha esattamente una voce nel grafico.
eol as (
SELECT points.* FROM
points JOIN
(SELECT id, count(*) FROM graph
GROUP BY id
HAVING count(*)= 1) sel
ON points.id = sel.id),
Chiamiamo questa tabella "eol" (fine linea)
facile? che la ricompensa per aver fatto un ottimo grafico ma le cose in sospeso diventeranno pazze al prossimo passaggio
Imposta una query ricorsiva che passerà da vicini a vicini a partire da ogni eol
- Inizializza la query ricorsiva usando la tabella eol e aggiungendo un contatore per la profondità, un aggregatore per il percorso e un costruttore di geometrie per costruire le linee
- Passa alla successiva iterazione passando al vicino più vicino utilizzando il grafico e verificando che non si torni mai indietro utilizzando il percorso
- Al termine dell'iterazione, mantenere solo il percorso più lungo per ciascun punto iniziale (se il set di dati include una potenziale intersezione tra le linee previste, tale parte richiederebbe più condizioni)
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
Chiamiamo questa tabella "recurse_eol"
Mantenere solo la linea più lunga per ciascun punto iniziale e rimuovere ogni percorso duplicato esatto Esempio: i percorsi 1,2,3,5 E 5,3,2,1 sono la stessa linea rilevata dalle due diverse "fine linea"
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
Controlla manualmente gli errori rimanenti (punti isolati, linee sovrapposte, strada a forma strana)
Aggiornato come promesso, non riesco ancora a capire perché a volte le query ricorsive non danno esattamente lo stesso risultato quando si parte dall'eol opposto di una stessa riga, quindi alcuni duplicati potrebbero rimanere nel layer dei risultati fin d'ora.
Sentiti libero di chiedere che ho capito che questo codice ha bisogno di più commenti. Ecco la query 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