Tracer une ligne entre des points à une distance spécifique dans PostGIS?

9

J'ai une donnée de points le long des rues, je voudrais transformer ces points en simples lignes colorées. Y a-t-il des pointeurs sur le nom de ce problème ou des algorithmes qui peuvent m'aider à résoudre ce problème? Points le long de la rue que je voudrais transformer en lignes.

J'espérais utiliser des PostGISfonctions pour ce faire, mais je suis ouvert aux suggestions, il s'agit de données d'un .shpfichier.

Edit1: Mise à jour de l'image pour démontrer la solution idéale de ce problème.

Tracer la ligne serait uniquement basé sur la distance entre ces points, il n'y a rien d'autre que je puisse utiliser pour les regrouper. Idéalement, ce serait des points à une distance maximale spécifiée le long de la ligne projetée? Et par ligne projetée, je veux dire trouver le premier point puis le plus proche, puis projeter une ligne et vérifier s'il y a des points sur cette ligne à une distance maximale de ceux qui sont déjà sur la ligne.

Mahakala
la source
1
Quel logiciel prévoyez-vous d'utiliser?
ArMoraer
essayez-vous de les transformer en trottoirs?
DPSSpatial
J'espérais utiliser les fonctions PostGIS pour cela, mais je suis ouvert aux suggestions, il s'agit de données d'un fichier .shp.
Mahakala
1
Pourriez-vous montrer exactement quels points vous souhaitez connecter sur votre dessin ou sur un autre dessin? Est-ce seulement deux points à la fois? Ou trois? La distance entre les points à connecter est-elle toujours la même ou est-elle "juste" en dessous d'un certain seuil?
Peter Horsbøll Møller
1
Un grand merci à @dbaston et à MarHoff, je n'aurai pas le temps de tester vos idées avant la fin avril, j'aurais aimé pouvoir partager les primes, mais je devrai attribuer cela à 1 d'entre vous et dbaston m'a donné quelques questions aussi j'accepterai donc sa réponse. Merci à tous ceux qui ont pris le temps de répondre! Grande communauté pour faire partie de :-)
Mahakala

Réponses:

8

La

Vous pouvez utiliser une requête récursive pour explorer le voisin le plus proche de chaque point à partir de chaque fin de ligne détectée que vous souhaitez créer.

Prérequis : préparez un calque postgis avec vos points et un autre avec un seul objet Multi-linestring contenant vos routes. Les deux couches doivent être sur le même CRS. Voici le code de l'ensemble de données de test que j'ai créé, veuillez le modifier si nécessaire. (Testé sur postgres 9.2 et 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),

entrez la description de l'image ici

Voici les étapes :

  1. Générer pour chaque point la liste de tous les voisins et leur distance qui répondent à ces trois critères.

    • La distance ne doit pas dépasser un seuil défini par l'utilisateur (cela évitera la liaison avec un point isolé) entrez la description de l'image ici
      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
      ),
    • Le chemin direct ne doit pas traverser une route entrez la description de l'image ici
      graph as (
      SELECt graph_full.*
      FROM graph_full RIGHT JOIN
      roads ON st_intersects(graph_full.geom,roads.geom) = false
      ),
    • La distance ne doit pas dépasser un rapport défini par l'utilisateur de la distance du plus proche voisin (cela devrait mieux s'adapter à la numérisation irrégulière que la distance fixe) Cette partie était en fait trop difficile à mettre en œuvre, collée à un rayon de recherche fixe

    Appelons ce tableau "le graphique"

  2. Sélectionnez le point de fin de ligne en vous joignant au graphique et en ne gardant que le point qui a exactement une entrée dans le graphique. entrez la description de l'image ici

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

    Appelons ce tableau "eol" (fin de ligne)
    facile? que la récompense pour avoir fait un grand graphique mais les choses qui tiennent le coup deviendront folles à la prochaine étape

  3. Configurer une requête récursive qui passera des voisins aux voisins à partir de chaque eol entrez la description de l'image ici

    • Initialiser la requête récursive à l'aide de la table eol et en ajoutant un compteur pour la profondeur, un agrégateur pour le chemin et un constructeur de géométrie pour construire les lignes
    • Passez à l'itération suivante en passant au voisin le plus proche à l'aide du graphique et en vérifiant que vous ne reculez jamais en utilisant le chemin
    • Une fois l'itération terminée, ne conservez que le chemin le plus long pour chaque point de départ (si votre jeu de données comprend une intersection potentielle entre les lignes attendues, cette partie aurait besoin de plus de conditions)
    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

    Appelons cette table "recurse_eol"

  4. Conserver uniquement la ligne la plus longue pour chaque point de départ et supprimer tous les chemins exacts en double Exemple: les chemins 1, 2, 3, 5 et 5, 3, 2, 1 sont la même ligne découverte par ses deux "fins de ligne" différentes

    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. Vérifie manuellement les erreurs restantes (points isolés, lignes qui se chevauchent, rue de forme étrange)


Mis à jour comme promis, je n'arrive toujours pas à comprendre pourquoi parfois une requête récursive ne donne pas exactement le même résultat en commençant à partir de l'éol opposé d'une même ligne, donc certains doublons peuvent rester dans la couche résultat à partir de maintenant.

N'hésitez pas à demander, je comprends totalement que ce code ait besoin de plus de commentaires. Voici la requête complète:

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
MarHoff
la source
Salut @MarHoff, merci pour votre réponse J'ai quelque chose à faire. Je ne m'attendais pas à une solution complète, juste un pointeur où chercher des réponses. Je veux mieux comprendre cela et je continuerai à creuser et j'aurai probablement d'autres questions plus tard. J'ai besoin de comprendre votre algorithme et cela me prendra quand même du temps :)
Mahakala
Vous avez un script de travail, prévisualisez ici qgiscloud.com/MarHoff/test_qgiscloud_bis une petite mise en garde pour la déduplication reste ... Plus de prime, plus de pression je suppose, donc je publierai la version quand je le pourrai. Ce puzzle était amusant cependant
MarHoff
merci @MarHoff, si je pouvais j'aurais partagé cette prime, je ne vois pas comment je peux vous attribuer des points, mais un grand merci pour avoir regardé cela et votre preuve. Semble authentique :)
Mahakala
Terminé. Merci pour le casse-tête et désolé pour les délires. Si une autre réponse l'a fait pour vous, alors c'est tout à fait ok parfois simple est le mieux ... Ma réponse était peut-être un peu trop en y réfléchissant. Bien qu'un bel exemple d'utilisation de CTE + requête récursive + fonction Windows + postgis sur une seule requête;)
MarHoff
8

Comme le souligne @FelixIP, la première étape consiste à trouver les points qui composeront chaque ligne. Vous pouvez le faire en appelant ST_ClusterWithin avec votre distance de séparation maximale:

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

Ensuite, vous devrez utiliser une heuristique pour construire une ligne à travers tous les points de chaque cluster. Par exemple, si vous pouvez supposer que les lignes souhaitées sont monotones Y, vous pouvez trier les points dans chaque cluster et les alimenter dans ST_MakeLine . Combiner tout cela ressemblerait à ceci:

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
dbaston
la source
Bien sûr, mais l'approche Y-monotone (ou même basculer entre X / Y-monotone) ne fonctionnera pas bien si l'ensemble de données contient une route incurvée. Est-ce le cas? L'algorithme de commande est la partie la plus difficile de cette question à mon humble avis.
MarHoff
@MarHoff: oui, les routes courbes seront un problème, mais j'essaie de transformer la plupart des données automatiquement et le repos devra être fait manuellement. Ou je continuerai à approfondir le sujet pour trouver une solution, mais cela peut prendre plus de temps que de demander à quelqu'un de réparer les données restantes. Je devrai évaluer les résultats pour pouvoir décider. Merci d'avoir signalé cela!
Mahakala
Statut réglé Je viens de penser à un truc que je dois vérifier ...
MarHoff
Existe-t-il un moyen robuste de le faire qui n'implique pas d'essayer tous les ordres de points possibles et de trouver celui qui donne la longueur totale la plus courte?
dbaston
Si ces ensembles de points suivent toujours les routes, vous projetez la position du point sur le segment de route (ST_Line_Locate_Point), puis triez les points par le résultat.
travis