Tracciare una linea tra punti a distanza specifica in PostGIS?


9

Ho un dato di punti lungo le strade, vorrei trasformare quei punti in semplici linee colorate. Qualche suggerimento su come potrebbe essere chiamato questo problema o eventuali algoritmi che possono aiutarmi a risolverlo? Punti lungo la strada che vorrei trasformare in linee.

Speravo di usare le PostGISfunzioni per farlo, ma sono aperto a suggerimenti, questi sono dati da un .shpfile.

Modifica1: foto aggiornata per dimostrare la soluzione ideale di questo problema.

Tracciare la linea sarebbe puramente basato sulla distanza tra quei punti, non c'è nient'altro che posso usare per raggrupparli. Idealmente questo sarebbe punti alla massima distanza specificata lungo la linea proiettata? E per linea proiettata intendo trovare il 1 ° punto, quindi quello successivo più vicino ad esso, quindi proiettare una linea e controllare se ci sono punti su questa linea alla massima distanza da quelli già presenti sulla linea.


1
Quale software hai intenzione di usare?
ArMoraer,

stai cercando di trasformarli in marciapiedi?
DPSSpatial

Speravo di usare le funzioni PostGIS per fare questo, ma sono aperto a suggerimenti, questi sono i dati di un file .shp.
Mahakala,

1
Potresti mostrare esattamente quali punti vuoi connettere sul tuo disegno o su un altro disegno? Sono solo due punti alla volta? O tre? La distanza tra i punti che devono essere collegati è sempre la stessa o "è" appena al di sotto di una determinata soglia?
Peter Horsbøll Møller,

1
Grazie mille sia a @dbaston che a MarHoff, non avrò il tempo di testare le tue idee fino alla fine di aprile, vorrei poter dividere la taglia tra di loro, ma dovrò assegnare questo a 1 di voi e dbaston mi ha dato anche alcune domande quindi accetterò la sua risposta. Grazie a tutti coloro che hanno avuto il tempo di rispondere! Grande comunità di cui far parte :-)
Mahakala

Risposte:


8

È 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),

inserisci qui la descrizione dell'immagine

Ecco i passaggi :

  1. 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) inserisci qui la descrizione dell'immagine
      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 inserisci qui la descrizione dell'immagine
      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"

  2. Seleziona la fine del punto di linea unendo al grafico e mantenendo solo il punto che ha esattamente una voce nel grafico. inserisci qui la descrizione dell'immagine

    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

  3. Imposta una query ricorsiva che passerà da vicini a vicini a partire da ogni eol inserisci qui la descrizione dell'immagine

    • 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"

  4. 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
  5. 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


Ciao @MarHoff, grazie per la tua risposta, ho qualcosa da seguire. Non mi aspettavo una soluzione completa, solo un indicatore in cui cercare risposte. Voglio capirlo di più e continuerò a scavare e probabilmente avrò più domande in seguito. Devo capire il tuo algoritmo e questo mi richiederà comunque del tempo :)
Mahakala

Ho una sceneggiatura funzionante, vedi l' anteprima qui qgiscloud.com/MarHoff/test_qgiscloud_bis rimane un piccolo avvertimento per la de-duplicazione ... Niente più generosità, niente più pressione immagino, quindi rilascerò il rilascio quando posso. Questo enigma è stato divertente
MarHoff l'

grazie @MarHoff, se avessi potuto dividere questa taglia non riesco a vedere come posso assegnarti qualsiasi punto, ma grazie mille per aver esaminato questa e la tua prova. Sembra genuino :)
Mahakala l'

Fatto. Grazie per il puzzle, e scusa per aver parlato. Se un'altra risposta l'ha fatto per te, allora è del tutto ok a volte è meglio ... La mia risposta è stata forse un po 'troppo pensante. Sebbene un bell'esempio di utilizzo di CTE + query ricorsiva + funzione Windows + postgis su una singola query;)
MarHoff

8

Come sottolinea @FelixIP, il primo passo è trovare i punti che comporranno ciascuna riga. Puoi farlo chiamando ST_ClusterWithin con la massima distanza di separazione:

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

Quindi, dovrai usare un po 'di euristica per costruire una linea attraverso tutti i punti di ciascun cluster. Ad esempio, se si può presumere che le linee desiderate siano Y-monotone, è possibile ordinare i punti in ciascun cluster e inserirli in ST_MakeLine . Combinando tutti insieme sarebbe simile a questo:

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

La strada da percorrere, ma l'approccio Y-monotone (o addirittura passare tra X / Y-monotono) non funzionerà bene se il set di dati contiene una strada curva. È il caso? L'algoritmo di ordinazione è la parte più difficile di questa domanda IMHO.
MarHoff

@MarHoff: sì, le strade curve saranno un problema, ma sto cercando di trasformare la maggior parte dei dati automaticamente e il resto dovrà essere fatto manualmente. O continuerò a scavare nell'argomento più per capire la soluzione, ma potrebbe richiedere più tempo di convincere qualcuno a riparare i dati rimanenti. Dovrò valutare i risultati per poter decidere. Grazie per averlo sottolineato!
Mahakala,

Statut sintonizzato Ho appena pensato a un trucco che devo controllare ...
MarHoff

Esiste un modo efficace per farlo che non implichi il tentativo di tutti i possibili ordini di punti e la ricerca di quale dia la lunghezza totale più breve?
dbaston,

Se questi insiemi di punti seguono sempre le strade, si proietta la posizione del punto sul tratto di strada (ST_Line_Locate_Point), quindi si ordinano i punti in base al risultato.
Travis,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.