#1 Fri 17 April 2020 11:52
- preliator
- Participant assidu
- Date d'inscription: 17 Nov 2018
- Messages: 433
Couper une ligne tous les X mètres
Bonjour,
Disposant d'une couche multilinestring représentant les routes d'un département, je souhaiterais couper cette couche en lignes de 500m.
Voici un aperçu de ma couche de routes : https://zupimages.net/viewer.php?id=20/16/eh9e.png
Après quelques recherche sur internet, je suis parti sur cette démarche :
- Regrouper ma couche de route en un seul bloc (j'ai trouvé cela cohérent car la couche de base est constitués de plusieurs lignes de tailles variables)
- Transformer ce bloc en linestring pour la requête suivante
- Créer des points tous les 500m le long de ma linestring avec st_lineinterpolatepoints (pas possible de créer les points sur ma couche regroupée, car cette fonction n'accepte que du linestring)
- Couper la couche regroupée sur cette couche de points.
Code:
-- REGROUPER MA COUCHE DE ROUTE EN UN SEUL BLOC MULTILINESTRING drop table if exists ligne_regroup; create table ligne_regroup as select St_linemerge(ST_Union(geom)) as geom from public.route_echantillon; -- TRANSFORMER LE BLOC EN LINESTRING drop table if exists ligne_regroup_linestring; CREATE TABLE ligne_regroup_linestring AS SELECT (ST_Dump(ligne_regroup.geom)).geom::Geometry(LineString,2154) AS geom FROM ligne_regroup; -- CREER UN IDENTIFIANT UNIQUE SUR LA LINESTRING alter table ligne_regroup_linestring add column id serial; -- CREER LES POINT TOUS LES 500 METRES drop table if exists point_500; create table point_500 as with tmp as( select * from ligne_regroup_linestring where st_length(ligne_regroup_linestring.geom) > 500) SELECT id, st_lineinterpolatepoints((tmp.geom),500/st_length(tmp.geom)) as geom from tmp; -- ON COUPE LA ROUTE REGROUPEE SUR LES POINTS drop table if exists route_cut_500_metres; create table route_cut_500_metres as select ST_SNAP((ST_DUMP(st_difference(ligne_regroup.geom,point))).geom,all_point,0.1) as geom from (select ST_Multi(ST_Union(st_expand(point_500.geom, 0.05))) as point from point_500 ) as t1, ligne_regroup,(select ST_MULTI(ST_COLLECT(point_500.geom))as all_point from point_500) as t2;
Malheureusement, j'obtiens des résultats aberrants. Les lignes semblent bien se découper, mais il manque de nombreuses lignes.
Comme on peut voir sur l'exemple suivant : https://zupimages.net/viewer.php?id=20/16/oaem.png
En vert : ma couche de route regroupée (ligne_regroup)
En rouge : le résultat de "route_cut_500_metres"
Les points : Résultat de la requête qui me crée des points tous les 500 mètres.
Merci.
Dernière modification par preliator (Fri 17 April 2020 11:59)
Hors ligne
#2 Fri 17 April 2020 13:56
- Nicolas Ribot
- Membre
- Lieu: Toulouse
- Date d'inscription: 9 Sep 2005
- Messages: 1554
Re: Couper une ligne tous les X mètres
Bonjour,
Il y a pas mal de posts sur georezo avec des exemples pour découper des lignes régulèrement.
Pas besoin de regrouper ou générer des points tous les 500m: vous pouvez dumper les lignes et pour chacune, appeler st_lineSubstring avec la valeur qui va bien pour la valeur endfraction, qui devra s'incrémenter pour chaque tronçon de 500m découpable dans les lignes.
Nico
Hors ligne
#3 Fri 17 April 2020 15:00
- Nicolas Ribot
- Membre
- Lieu: Toulouse
- Date d'inscription: 9 Sep 2005
- Messages: 1554
Re: Couper une ligne tous les X mètres
Voici un moyen de faire:
• dumper les multilinestrings en LN simples, en gardant l'index de la ligne dans la collection (st_dump(geom).path[1]): ca servira a reconstruire les multilinestrings plus tard.
• calculer le nombre de découpage a faire pour chaque ligne: longueur_ligne/longueur_découpe (ici, 50m, sur mon dataset, 500m ne découpait rien): numcut; et calculer combien représentent 50m en fraction de la ligne a découper (chiffre entre 0 et 1, a passer a st_linesubstring (https://postgis.net/docs/manual-2.5/ST_ … tring.html) )
• générer (avec generate_series) le bon nombre de découpage pour chaque ligne: on doit découper entre 0 et numcut portions. Le cross join lateral permet d'appeler generate_series avec une valeur de numcut différente pour chaque ligne.
• calculer les valeurs de startfraction et endfraction pour chaque découpage (chiffre entre 0 et 1, a passer a st_linesubstring)
• Enfin, découper la ligne avec le startfraction et endfraction calculés. (il reste éventuellement a regrouper les lignes de meme id, order by idln, pour reconstruire les multilinestrings initiales, mais avec des segments découpés a 50m)
En sql:
Code:
with tmp as ( select id, (st_dump(geom)).path[1] as idln, (st_dump(geom)).geom from work_nico.roadml ), tmp1 as ( select id, idln, floor(st_length(geom) / 50.0)::int as numcut, (50/st_length(geom)) as frac_50m, geom from tmp ), tmp2 as ( select t.*, g.cutpos, g.cutpos * frac_50m as startfraction, least((g.cutpos * frac_50m) + frac_50m, 1) as endfraction from tmp1 t cross join lateral ( select cutpos from generate_series(0, t.numcut) as cutpos ) g ) select t.id, idln, numcut, frac_50m, startfraction, endfraction, st_linesubstring(geom, startfraction, endfraction) as geom from tmp2 t;
Ici, je me suis arrêté au découpage des routes, je n'ai pas regroupé en MLN (st_collect(geom order by idln))
En image: en vert, les routes initiales avec startpoint/endpoint sous forme de flêches.
En rouge, les startpoint/endpoint des lignes découpées tous les 50m
Nico
Hors ligne
#4 Fri 17 April 2020 15:36
- preliator
- Participant assidu
- Date d'inscription: 17 Nov 2018
- Messages: 433
Re: Couper une ligne tous les X mètres
C'est ... impressionnant ! Ca fonctionne très bien.
Je vous remercie sincèrement pour votre temps
Hors ligne
#5 Fri 17 September 2021 11:58
- Mathieu Denat
- Participant actif
- Lieu: Montpellier
- Date d'inscription: 5 May 2010
- Messages: 110
Re: Couper une ligne tous les X mètres
Bonjour,
Tout d'abord merci Nico et Preliator pour cet échange qui m'a servi un très grand nombre de fois! Notamment pour découper des cours d'eau par tronçons de 500m. L'idée est simple: découper un cours d'eau de l'aval vers l'amont selon des tronçons de 500 m. Ensuite, je suis amené à compter les entités ou des linéaires ou des surfaces d'espèces végétales par tronçon.
En général ce script fonctionne sans que je me pose trop de question, mais cette fois ci c'est l'impasse.
Mon cours d'eau en entrée, fait ~ 25 km de long, je devrais donc obtenir 51 tronçons (50 x 500m + 1 x moins de 500m à l'amont)
Mon problème est que le script ne génère pas de tronçon de 500 m, et je ne comprend pas pourquoi...
À l'origine mon cours d'eau est composé de 79 portions (il est issu de la BDTopage).
À la sortie j'ai toujours mes 79 tronçons, dont la longueur varie de 6 à 650m environ.
J'uploade en PJ mes données (le résultat du 1er select (boyne), histoire de pouvoir tester grandeur nature!), et voici mon script SQL (j'ai ajouté la longueur des tronçons pour vérifier ce qui clochait).
Avez-vous une idée de ce qui ne va pas?
Merci d'avance!
Code:
WITH boyne AS ( SELECT cdcourseau, geom from referentiel.cours_eau where topooh ilike '%boyne' -- sélection de la Boyne ) ,tmp AS ( SELECT cdcourseau, (st_dump(geom)).path[1] as idln, (st_dump(geom)).geom from boyne ) -- from outils.cours_eau_simplifie ) --utilisé uniquement dans le cas de cours d'eau contenant des plans d'eau ,tmp1 AS ( SELECT cdcourseau, idln, floor(st_length(geom) / 500.0)::int as numcut, (500/st_length(geom)) as frac_500m, geom FROM tmp) ,tmp2 as ( SELECT t.*, g.cutpos, g.cutpos * frac_500m as startfraction, least((g.cutpos * frac_500m) + frac_500m , 1) as endfraction FROM tmp1 t CROSS JOIN LATERAL ( SELECT cutpos FROM generate_series(0, t.numcut) as cutpos ) g ) SELECT st_length(geom) longueur, t.cdcourseau, idln, numcut, frac_500m, startfraction, endfraction, st_linesubstring(geom, startfraction, endfraction) as geom, concat(cdcourseau,'-',to_char(row_number() over (),'FM000')) id_troncon FROM tmp2 t order by 1 --id_troncon
Dernière modification par Mathieu Denat (Fri 17 September 2021 12:01)
Mathieu
C'est en forgeant qu'on devient forgeron
Hors ligne
#6 Fri 17 September 2021 20:36
- tumasgiu
- Membre
- Lieu: Ajaccio
- Date d'inscription: 5 Jul 2010
- Messages: 1160
Re: Couper une ligne tous les X mètres
Salut,
votre requête est bonne, c'est votre dataset qui "cloche" : votre geometrie est multilinestring composé pour la plupart de segments de - de 500m.
En recréant une seule lneestring à partir de votre geometrie grâce à st_linemerge dans votre premiere CTE, vous arrivez au résultat attendu.
Dernière modification par tumasgiu (Fri 17 September 2021 20:51)
Hors ligne
#7 Sat 18 September 2021 20:44
- Mathieu Denat
- Participant actif
- Lieu: Montpellier
- Date d'inscription: 5 May 2010
- Messages: 110
Re: Couper une ligne tous les X mètres
Bonsoir tumasgiu,
Merci pour votre réponse.
Ça ne fonctionne pas du premier coup, je ne comprends pas pourquoi, mais vous m'avez débloqué!
Mais en creusant dans ce sens, j'ai trouvé une alternative: faire un st_memunion suivi d'un st_linemerge et hop ça fonctionne.
D'un point de vue géométrique, je ne comprends pas pourquoi le st_linemerge ne fonctionne pas, si l'un⋅e d'entre vous à une idée, je suis preneur.
À bientôt.
Voici le script définitif, si ça peut servir à d'autres!
Code:
WITH boyne AS ( SELECT cdcourseau, st_linemerge(st_memunion(geom)) geom --fusion (st_memunion puis st_linemerge, mystérieux: une seule des 2 fonctions ne suffis pas!) from referentiel.cours_eau where topooh ilike '%boyne' -- sélection de la Boyne GROUP BY cdcourseau ) ,tmp AS ( SELECT cdcourseau, (st_dump(geom)).path[1] as idln, (st_dump(geom)).geom from boyne ) -- from outils.cours_eau_simplifie ) --utilisé uniquement dans le cas de cours d'eau contenant des plans d'eau ,tmp1 AS ( SELECT cdcourseau, idln, floor(st_length(geom) / 500.0)::int as numcut, (500/st_length(geom)) as frac_500m, geom FROM tmp) ,tmp2 as ( SELECT t.*, g.cutpos, g.cutpos * frac_500m as startfraction, least((g.cutpos * frac_500m) + frac_500m , 1) as endfraction FROM tmp1 t CROSS JOIN LATERAL ( SELECT cutpos FROM generate_series(0, t.numcut) as cutpos ) g ) SELECT t.cdcourseau, idln, numcut, frac_500m, startfraction, endfraction, st_linesubstring(geom, startfraction, endfraction) as geom, concat(cdcourseau,'-',to_char(row_number() over (),'FM000')) id_troncon FROM tmp2 t order by 1 --id_troncon
Mathieu
C'est en forgeant qu'on devient forgeron
Hors ligne
#8 Sat 18 September 2021 20:58
- tumasgiu
- Membre
- Lieu: Ajaccio
- Date d'inscription: 5 Jul 2010
- Messages: 1160
Re: Couper une ligne tous les X mètres
Si votre cours d'eau est composé de plusieurs multilinestring dans votre base de données, alors cela parait logique de faire une union de celles-ci avant d'appliquer un linemerge sur l'ensemble.
Dernière modification par tumasgiu (Mon 20 September 2021 17:19)
Hors ligne