#1 Mon 15 February 2016 09:49
- CGuillaume
- Participant actif
- Lieu: Annecy
- Date d'inscription: 3 Aug 2011
- Messages: 59
[PostGIS] Découpages fonds de vallées
Bonjour,
Je cherche, avec PostGIS si possible, à découper des fonds de vallées pour les affecter à des tronçons de cours d’eau. Un petit exemple en image en pj : pour le tronçon vert lui affecter la zone colorisée en vert avec un découpage correspondant (grossièrement) aux lignes jaunes.
J’avais pensé faire de larges buffers autour des lignes, avec les extrémités plates, pour ensuite découper les fonds de vallées par les buffers.
Code:
st_buffer(a.geom, 100, 'endcap=flat join=bevel')
d'après la doc
Sauf que ça ne me donne pas toujours des extrémités plates (pourquoi ?) et que j’ai beaucoup de superpositions…(ce qui n’est pas le plus grave en soit).
Avez-vous une autre idée pour automatiser ce genre de découpage ?
Merci d’avance pour vos propositions qui seront j’en suis persuadé très élégantes !
Guillaume
Hors ligne
#2 Mon 15 February 2016 16:41
- Nicolas Ribot
- Membre
- Lieu: Toulouse
- Date d'inscription: 9 Sep 2005
- Messages: 1554
Re: [PostGIS] Découpages fonds de vallées
Bonjour,
Si vous disposez de chaque troncon de rivière sous forme de linestring (avec points de départ et d'arrivée), vous pouvez utilisez cette fonction très pratique:
https://trac.osgeo.org/postgis/wiki/Use … WithOffset
pour chaque start/end de chaque troncon, tracer les perpendiculaires au troncon puis découper les fonds de vallée.
Avec un buffer, ca peut aussi marcher, mais si le cours d'eau est tortueux par rapport au fond, les résultats peuvent etre bizarres
Nico
Hors ligne
#3 Tue 16 February 2016 13:06
- Nicolas Ribot
- Membre
- Lieu: Toulouse
- Date d'inscription: 9 Sep 2005
- Messages: 1554
Re: [PostGIS] Découpages fonds de vallées
Bonjour,
Le problème n'est simple, notamment pour fabriquer les lignes jaunes aux intersections des tronçons de cours d'eau:
si le cours est tortueux, et si on prend comme ligne jaune la normale au tronçon, alors il se peut que les lignes se recoupent DANS le fond, créant un polygone en papillon. (le même type de problème doit se poser en prenant un buffer aux extrémités carrées pour chaque tronçon)
J'ai testé un peu hier soir en prenant les normales aux tronçons pour les points d'intersection des tronçons (fn st_lineInterpolatePoint du wiki postgis)
A partir de ces lignes, on peut délimiter des parties intersectant le fond de vallée et reconstruire des polygones (st_polygonize) à partir de ces lignes et du polygone de fond.
On peut alors associer chaque polygone au tronçon de rivière concerné.
Le pb est alors d'identifier les parties de polygones en papillon, formés par les lignes jaunes qui se croisent dans le pg de fond.
En SQL, étape par étape, ca donne ca (on s'accroche aux rideaux ) :
• pour calculer les lignes jaunes, on prend la normale moyenne en un point: en effet, en un point, deux tronçons se touchent. si les tronçons ont des angles différents, deux normales peuvent etre calculées en ce point, une pour chaque tronçon.
Là, il y a matière à amélioration en faisant en sorte, après ce calcul, que les lignes jaunes ne s'intersectent pas à l'intérieur du fond
Code:
with tmp as ( -- on calcule les normales de chaque coté de chaque tronçon (distance de 300m prise au pif en fonction des données de test) SELECT c.dbid, st_startpoint(geom) AS geom, st_lineinterpolatepoint(geom, 0.01, 300) as pt_left, st_lineinterpolatepoint(geom, 0.01, -300) as pt_right FROM testcours c UNION ALL SELECT c.dbid, st_endpoint(geom) AS geom, st_lineinterpolatepoint(geom, 1, 300) as pt_left, st_lineinterpolatepoint(geom, 1, -300) as pt_right FROM testcours c ), tmp1 as ( -- on regroupe les points identiques a partir desquels on va calculer la normale aux lignes en ce point -- pour certains points, on a deux lignes (lignes connectées), pour d'autres, une seule (extrémité de ligne) SELECT array_agg(t.dbid) AS dbids, array_agg(t.pt_left) AS lefts, array_agg(t.pt_right) AS rights, t.geom FROM tmp t GROUP BY t.geom ), tmp2 as ( -- on duplique la ligne si une seule ligne en ce point, --histoire de pouvoir tout le temps calculer une normale moyenne entre deux lignes (même identiques) select t.geom, case when array_length(t.dbids, 1) = 1 then dbids||dbids[1] else dbids end as dbids, case when array_length(t.lefts, 1) = 1 then lefts||lefts[1] else lefts end as lefts, case when array_length(t.rights, 1) = 1 then rights||rights[1] else rights end as rights from tmp1 t ), tmp3 as ( -- on calcule les perpendiculaires moyennes en chaque point, qui donnent les lignes jaunes select t.dbids, st_makeLine( st_lineinterpolatepoint(st_makeLine(lefts[1], lefts[2]), 0.5), st_lineinterpolatepoint(st_makeLine(rights[1], rights[2]), 0.5)) as geom UNION ALL -- on ajoute l'exterieur du pg pour avoir un reseau de lignes (lignes jaunes + exterieur du PG) dont on veut reconstruire les surfaces select null::bigint[] as dbids, st_boundary(geom) as geom from testpg from tmp2 t ) , tmp4 as ( -- on fabrique l'union des lignes et de l'exterieur du pg, avec st_union, histoire d'avoir un réseau de lignes connectées aux intersections, -- condition indispensable pour que st_polygonize marche select (st_dump(st_union(t.geom))).geom from tmp3 t ), tmp5 as ( -- on construit tous les polygones issus des croisements des lignes -- précédentes avec st_polygonize, et on dump la collection obtenue en polygones individuels select (st_dump(st_polygonize(t.geom))).geom as geom from tmp4 t ), tmp6 as ( -- on conserve les polygones contenus dans le fond: -- --pb de precision ici: st_contains ne renvoie pas les bons résultats: petit buffer négatif pour contourner le pb de précision select t.geom from tmp5 t, testpg p where st_contains(p.geom, st_buffer(t.geom, -0.1)) ), tmp7 as ( -- on associe les pg de fond issus du découpage aux tronçons, en prenant le milieu des troncons select c.dbid, t.geom from testcours c join tmp6 t on st_contains(t.geom, st_lineinterpolatepoint(c.geom, 0.5)) ) select * from tmp7;
En image: le pg de fond est en bleu clair: on voit les pg en papillon pas identifiés dans la requete.
Une passe supplémentaire en cherchant les polygones qui touchent les lignes jaunes devrait permettre d'identifier ces pg.
Nicolas
Hors ligne
#4 Tue 16 February 2016 14:46
- CGuillaume
- Participant actif
- Lieu: Annecy
- Date d'inscription: 3 Aug 2011
- Messages: 59
Re: [PostGIS] Découpages fonds de vallées
Bonjour Nicolas,
Bon j'en étais que là moi... et je cherchais à faire des polygones et les réaffecter à mes tronçons
Code:
-- Création perpendiculaire en fin de tronçon SELECT gid, ST_MakeLine(ARRAY[st_line_interpolate_point(geom,0.001,-500), st_line_interpolate_point(geom,1,500)]) as geom from troncons; -- Création perpendiculaire en début de tronçon SELECT gid, ST_MakeLine(ARRAY[st_line_interpolate_point(geom,0.001,-500), st_line_interpolate_point(geom,1,500)]) as geom from troncons;
J'ai testé avec mon échantillon de données ça tourne bien
Maintenant il ne me reste plus qu'à reprendre ta requête étape par étape pour l'assimiler !
Je pense qu'en trouvant la bonne valeur de distance pour la construction des normales on peut limiter les papillons non identifiés et les récupérer par la suite.
En tout cas, merci beaucoup pour le boulot et respect ! J'adore ce forum.
@+
Guillaume
Hors ligne
#5 Tue 16 February 2016 18:09
- Nicolas Ribot
- Membre
- Lieu: Toulouse
- Date d'inscription: 9 Sep 2005
- Messages: 1554
Re: [PostGIS] Découpages fonds de vallées
Bonjour,
Merci. Oui, c'est un bon forum
Une autre idée m'est venue, peut etre plus simple:
• On découpe le polygone de fond en 2 berges, grâce au cours d'eau berge 1 et berge 2
• on travaille par tronçon de cours d'eau:
- pour chaque tronçon, on calcule les points sur chaque berge les plus proches du startpoint et endpoint du troncon: on obtient 4 points par tronçon:
berge 1, proche du start: sb1
berge 1, proche du end: eb1
berge 2, proche du start: sb2
berge 2, proche du end: eb2
On peut alors reconstruire 4 linestrings:
- une composée de sb1, startpoint, sb2
- une composée de eb1, endpoint, eb2
- une composée par la sub linestring de la berge 1 découpée entre les points sb1 et eb1
- une composée par la sub linestring de la berge 2 découpée entre les points sb2 et eb2
On peut alors merger ces 4 lignes, qui doivent (à la précision près du découpage), reconstruire des polygones délimitant les zones de tronçons.
La méthode des points les plus proches à l'air de mieux marcher que les normales aux fins de segments.
Dans certains cas, on peut avoir un mauvais ordre des points de découpage des berges, mais il est possible de les réordonner correctement dans une sous-requête.
Les sous-requetes jouent avec les tableaux postgres: c'est super puissant pour construire des géométries à partir de morceaux de geom.
Les tableaux peuvent etre mis en lignes facilement (unnest(array)).
En SQL:
Code:
-- découpage du pg en deux berges: drop table berge; create table berge as ( with tmp as ( select st_union(geom) as geom from testcours ), tmp1 as ( SELECT st_dump(st_difference(st_exteriorring(p.geom), t.geom)) as d FROM testpg p, tmp t ) select (t.d).path[1], (t.d).geom from tmp1 t ); -- puis exploitation des berges et des troncons: with tmp as ( -- on calcule les points les plus proches des start et end points sur chaque berge (st_snapToGrid pour eviter des pb de précision) select c.dbid, st_startpoint(c.geom) as start, st_snapToGrid(st_lineinterpolatepoint(b1.geom, st_linelocatepoint(b1.geom, st_startpoint(c.geom))), 0.01) AS sb1, st_snapToGrid(st_lineinterpolatepoint(b1.geom, st_linelocatepoint(b1.geom, st_endpoint(c.geom))), 0.01) AS eb1, st_endpoint(c.geom) as end, st_snapToGrid(st_lineinterpolatepoint(b2.geom, st_linelocatepoint(b2.geom, st_endpoint(c.geom))), 0.01) AS eb2, st_snapToGrid(st_lineinterpolatepoint(b2.geom, st_linelocatepoint(b2.geom, st_startpoint(c.geom))), 0.01) AS sb2, -- on garde aussi la position du point d'intersection sur chaque berge, elle servira a st_lineSubString() st_linelocatepoint(b1.geom, st_startpoint(c.geom)) AS lsb1, st_linelocatepoint(b1.geom, st_endpoint(c.geom)) AS leb1, st_linelocatepoint(b2.geom, st_endpoint(c.geom)) AS leb2, st_linelocatepoint(b2.geom, st_startpoint(c.geom)) AS lsb2, b1.geom as b1geom, b2.geom as b2geom from testcours c, berge b1, berge b2 WHERE b1.path = 1 AND b2.path = 2 ), tmp1 as ( -- on construit 4 arcs de ligne pour chaque troncon: ces arcs sont constitués -- des points calculés précédemment et des partie de berge interctées par les points sb1/eb1 et eb2/sb2 select t.dbid, -- arc 1: le segment dans le fond de vallee, au niveau du startpoint st_snaptogrid(st_makeLine(array[t.sb2, t.start, t.sb1]), 0.01) as sub1, -- arc2: partie de berge 1 st_snaptogrid(st_lineSubstring(b1geom, least(t.lsb1, t.leb1), greatest(t.lsb1, t.leb1)), 0.01) as sub2, -- arc 3: le segment dans le fond de vallee, au niveau du endpoint st_snaptogrid(st_makeLine(array[t.eb1, t.end, t.eb2]), 0.01) as sub3, -- arc4: partie de berge 2 st_snaptogrid(st_lineSubstring(b2geom, least(t.lsb2, t.leb2), greatest(t.lsb2, t.leb2)), 0.01) as sub4 from tmp t ), tmp2 as ( -- on remet le tableau de géométries en ligne, car st_lineMerge prend une multilinestring à fusionner. -- pour chaque troncon, on obtient 4 lignes qu'on pourra GROUP BY dbid plus tard SELECT t.dbid, unnest(ARRAY [sub1, sub2, sub3, sub4]) AS lines FROM tmp1 t ), tmp3 as ( -- on tente de reconstruire les polygones en mergeant les lignes par id de troncon SELECT t.dbid, st_lineMerge(st_union(t.lines)) as lines FROM tmp2 t GROUP BY t.dbid ) -- on filtre les éventuels pg en erreur: on n'arrive pas a construire un pg a partir des 4 lignes -- TODO: réordonner les points d'intersection sur les berges pour découper correctement select dbid, st_makePolygon(lines) from tmp3 where geometryType(lines) = 'LINESTRING';
En image: (il manque juste un tout petit bout non reconstruit, en haut à droite)
Nicolas
Hors ligne
#6 Fri 19 February 2016 09:00
- CGuillaume
- Participant actif
- Lieu: Annecy
- Date d'inscription: 3 Aug 2011
- Messages: 59
Re: [PostGIS] Découpages fonds de vallées
Bonjour,
Effectivement même si j'ai mis un peu de temps à reproduire le trick chez moi, ça marche encore mieux !
J'ai galéré avec mes tronçons de test qui ont pas mal d'affluents qui coupent le fond de vallée, du coup je me retrouver avec plus de 2 berges lors de la découpe ce qui est un peu moins logique
Mais après un petit travail pour sélectionner les tronçons "principaux" ça devrai le faire.
Merci Nicolas.
@Bientôt
Guillaume
Hors ligne