Quantcast
Channel: PortailSIG - Systèmes d'Information Géographique
Viewing all 43 articles
Browse latest View live

QGIS, représentation 3D des couches vectorielles (shapefiles dits 3D ou shapefiles avec attributs z) avec les modules Python Matplotlib ou Visvis à partir de la console Python

$
0
0
Niveau Intermédiaire
Logiciels utilisésQuantum GIS (QGIS)
Shapely (module Python)
matplotlib (module Python)
visvis (module Python)
Plateforme Windows | Mac | Linux | FreeBSD

Comment utiliser avec profit dans Quantum GIS, des objets 3D comme les shapefiles dits 3D (PointZ, PolylineZ, PolygonZ,... ) ou les objets 2D qui disposent d'un attribut z. C'est la question qui a été reposée récemment sur la liste QGIS-Developer (osgeo-org.1560.n6.nabble.com/Re-Qgis-user-Using-visualizing-3D-data-Z-values-td5004272.html) et à laquelle j'ai répondu à osgeo-org.1560.n6.nabble.com/visualizing-3D-data-Z-values-or-data-with-z-attribute-a-solution-td5005360.htmlà l'aide d'autres modules Python que PyQGIS.

La représentation 3D dans QGIS est en effet une question récurrente sur les Forums et les listes de diffusion.  Les solutions proposées sont jusqu'à présent indirectes comme celles de passer par GRASS GIS, SpatiaLite ou PostGIS (www.faunalia.com/content/transfer-3d-shapefiles-z-values-table-attributes) en attendant l'extension Globe, encore en développement.

  • Le cas des couches avec un attribut d'altitude est simple à traiter. L'extension Interpolation permet de les traiter et de générer des surfaces au format de grille ASCII ArcInfo(.asc) mais elle n'offre aucun moyen de visualiser le résultat en 3D.
  • Le problème des shapefiles dits 3D, où la valeur z est intégrée dans la géométrie, provient du fait que QGIS ou PyQGIS ne les gèrent pas: il sont considérés comme des shapefiles 2D.

Pourtant, il y a moyen, dès à présent, de créer des représentations 3D à l'aide de la console Python et des modules Python qui permettent une telle représentation:

  1. Pour le traitement des shapefiles dits 3D, il existe d'autres modules Python qui sont capables d'extraire la valeur z de la géométrie, comme ogr et Shapely, directement depuis la couche QGIS, ou Pyshp/shapefileà partir du fichier shapefile original (code.google.com/p/pyshp/wiki/CreateElevationValues). Ces modules ont déjà été détaillés sur le Portail et peuvent être utilisés dans la Console Python sans problème.
  2. Pour la visualisation en 3 dimensions, nous allons utiliser des modules Python qui fonctionnent très bien dans la console Python:
    • le classique module Python Matplotlib;
    • un nouveau module, Visvis. Il est écrit en pur Python et se base sur Numpy et PyOpenGL;
    • nous laissons de côté le très puissant module Mayavi, déjà illustré sur ce Portail, trop complexe à utiliser pour ce que l'on veut faire.

Le problème des fichiers shapefiles dits 3D dans QGIS

Examinons tout d'abord le traitement de ces géométries dans la console Python avec une couche shapefile 3D:

  •  traitement avec le module PyQGIS

Le point sélectionné est bien reconnu comme un Point 3D (WKBPoint25D) mais il n'y aucune fonction de la classe Point de PyQGIS qui permet d'extraire la coordonnée z.

  • avec le module ogr:

Transformons ce point en objet ogr (à l'aide du format WKB):

Ici la classe Point de ogr permet de récupérer la coordonnée z

  • avec le module Shapely

Même démarche avec le module Shapely:

et même conclusion pour la classe Point de Shapely

La solution est donc d'utiliser un de ces modules, en complément de PyQGIS, pour extraire la coordonnée z.

Traitements dans la console Python

Les shapefiles dits 3D

Les utilisateurs de QGIS qui veulent se lancer dans les traitements en Python s'appuient généralement sur les tutoriels de http://www.qgis.org/pyqgis-cookbook/index.html ou de www.qgisworkshop.org/html/index.html. Le problème est que ces deux tutoriels, sans vouloir vexer les auteurs, se révèlent fort complexes et peu  « Pythonesques » à utiliser dans la console comme l'ont souligné diverses personnes dont Sean Gillies (l'auteur de Shapely et Fiona), surtout dans les processus d'itération.

Prenons par exemple, "iterating over a Vector Layer" dans le premier tutoriel qui peut être effectué en quelques lignes de code:

  • La première chose est de sélectionner tous les éléments (géométrie et attributs d'une couche). Une simple fonction suffit (sans feat = QgsFeature(), allAttrs = provider.attributeIndexes(), provider.select(allAttrs) et autres éléments...):
def select_tout(couche):    couche.select([])    couche.setSelectedFeatures([obj.id()for obj in couche])
  • ensuite  la couche vectorielle choisie est activée et la fonction précedente est utilisée pour sélectionner tous les éléments:
macouche = qgis.utils.iface.activeLayer()select_tout(macouche)
  • et toutes les géométries et les attributs de la couche se retrouvent dans la variable macouche. il est alors possible d' utiliser les boucles for, les compréhensions de liste, etc.

Les coordonnées x, y, z sont ensuite extraites grâce à la fonction loads de Shapely:

from shapely.wkbimport loads# lecture de toutes les géométries de la couche choisie et extraction des coordonnées x,y,zx=[]y=[]z=[]for elem in macouche.selectedFeatures():           # extraction de la géométrie au format wkb           geom= elem.geometry()           wkb = geom.asWkb()           # utilisation de Shapely pour extraire les coordonnées x,y,z           x.append(loads(wkb).x)           y.append(loads(wkb).y)           z.append(loads(wkb).z)

Les couches 2D avec un attribut

Ici, plus besoin d'utiliser un autre module puisque les coordonnées x et y peuvent être directement obtenues par PyQGIS.

Dans la boucle présentée, les attributs sont directement accessibles avec la fonction elem.attribute.map()

  • comme dans le cas présent l'attribut z (elev) est en deuxième position, il sera donc obtenu par elem.attribute.map()[1]. Il y aurait moyen d'améliorer le processus en permettant de sélectionner l'attribut par son nom (elev), mais ce n'est pas le sujet ici;
  • la partie .toFloat()[0] n'est que la transformation d'un objet Qvariant de PyQt4  en une variable standard de Python.

Le traitement devient donc :

# lecture des coordonnées x,y  et extraction des valeurs de l'attribut zx=[]y=[]z=[]for elem in macouche.selectedFeatures():           geom= elem.geometry()           x.append(geom.asPoint()[0])           y.append(geom.asPoint()[1])           z.append(elem.attributeMap()[1].toFloat()[0])

Les résultats sont, dans les deux cas, 3 listes Python avec les coordonnées x,y et z.

Représentations 3D (points, surfaces à partir des points, grilles .asc)

1) les points

Il est dès lors très facile d'utiliser ces listes avec  Matplotlib dans la console pour représenter les points en 3D: 

from mpl_toolkits.mplot3d.axes3dimport*import matplotlib.pyplotas pltfrom matplotlib import cmfig = plt.figure()ax = Axes3D(fig)ax.scatter3D(x,y,z,c=z,cmap=plt.cm.jet)plt.show()

avec Matplotlib:

avec Visvis :

import visvis# ouverture d'une fenêtre et affichage des pointsf = visvis.gca()m = visvis.plot(x,y,z, lc='k', ls='', mc='g', mw=2, lw=2, ms='.')f.daspect = 1,1,10# z x 10

 

Il y a moyen, bien entendu, d'apporter aux figures toutes les fioritures désirées.

2) surfaces à partir des points

Il y a moyen d'aller beaucoup plus loin puisque des modules Python comme Matplotlib, Numpy ou SciPy disposent de toutes les fonctions d'interpolation que l'on désire.

interpolation avec la fonction griddata de matplotlib

C'est la plus simple à utiliser, elle utilise un algorithme de Delaunay ou celui des plus proches voisins (en fonction de l'installation de Matplotlib) pour interpoler une grille en fonction des coordonnées x, y, z

import numpy as npfrom matplotlib.mlabimport griddata# création d'une grille 2Dxi = np.linspace(min(x), max(x))yi = np.linspace(min(y), max(y))X, Y = np.meshgrid(xi, yi)# interpolationZ = griddata(x, y, z, xi, yi)

avec Matplotlib

fig = plt.figure()ax = Axes3D(fig)ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.jet,linewidth=1, antialiased=True)plt.show()

 figure

avec Visvis:

f = visvis.gca()m = visvis.grid(xi,yi,Z)# ou m = visvis.grid(yi,xi,Z) si une ancienne versionf.daspect = 1,1,10# z x 10

grille avec la couche point superposée:

relief ombragé avec une table de couleur élévation (visvis.CM_JET):

m = visvis.surf(xi,yi,Z)m.colormap = visvis.CM_JET

interpolation avec une des fonctions splines de SciPy

À titre d'exemple des possibilités, nous allons essayer l'un des algorithmes de spline proposé par le module SciPy: l'algorithme de « Spline en plaque mince (thin-plate spline) » (sigomatic.blogspot.be/2009/09/lissage-par-spline-en-plaque-mince.html)

import scipy as spimport scipy.interpolate# construction de la grillespline = sp.interpolate.Rbf(x,y,z,function='thin-plate')xi = np.linspace(min(x), max(x))yi = np.linspace(min(y), max(y))X, Y = np.meshgrid(xi, yi)# interpolationZ = spline(X,Y)

avec Visvis

f = visvis.gca()m = visvis.surf(xi,yi,Z)m.colormap = visvis.CM_JET f.axis.visible = False

3) traitement des fichiers .asc créés par l'extension Interpolation

Il y a 2 méthodes pour lire les fichiers .asc créés par l'extension Interpolation dans la console Python:

  • avec le module Python osgeo/gdal
  • depuis très longtemps, David Finlayson a proposé diverses versions de ses fonctions pour extraire les points x, y et z d'une grille ASCII ArcInfo (ou d'une grille Golden Surfer), en pur Python. La dernière version disponible se trouve à bitbucket.org/david_finlayson/pyraster/src.

Puisque c'est un grille régulière, il a des fonctions Numpy/SciPy pour les traiter directement.

résultat de l'extension Interpolation sur les points (par l'algorithme de Delaunay de l'extension)

résultat du traitement dans la Console Python (animation réalisée avec le module visvis):

L'extension QGIS gSurf, qui permet de calculer l'intersection d'un MNT et d'une surface géologique et de la figurer en 3D, utilise cette technique avec Matplotlib (www.malg.eu/gsurf_en.php).

Autres traitements

D'autres traitements peuvent être aussi effectués comme la représentation des lignes de contours en 3 dimensions (alors que l'extension Contour, elle aussi basée sur Matplotlib, ne le fait qu'en 2 dimensions)

résultat de l'extension contour sur les points

représentation 3D des lignes de contour (Matplotlib)

 

En pratique, toutes les lignes 3D ou les polygones 3D peuvent être représentés (Visvis):

Comme je suis géologue, il est possible de représenter des sondages (à partir d'un shapefile avec la cote des formations géologiques). Il est aussi possible de générer des surfaces pour chaque limite de formation.

Pour aller plus loin, l'exportation des surfaces

Tout ça c'est bien beau me direz-vous, mais ce n'est que de la visualisation, et on ne peut pas exploiter ces résultats. Et bien non, il y a très facilement moyen de sauver les grilles résultantes en grilles ASCII en utilisant les fonctions proposéés par David Finlayson ou en utilisant Numpy/SciPy.

Comparaison avec les résultats dans GRASS GIS

À aucun moment dans ces traitements en Python, n'interviennent les projections, hormis par l'entremise des coordonnées elles-mêmes (référentiel purement cartésien). Il est donc intéressant de comparer les résultats obtenus avec ceux obtenus par GRASS GIS avec ces mêmes données, dans un système de projection:

résultat avec Visvis (z x 1)                résultat avec GRASS GIS (z x 1)

résultat avec Visvis (z x 10)                                      GRASS GIS (z x 10)

Les différences principales ne résultent pas du fait que les données soient projetées ou non (pour autant que le système de projection soit en x,y) mais de l'algorithme d'interpolation, du coefficient d'exagération des hauteurs utilisé et du type de perspective. L'utilisation du module Basemap permettrait sans doute de traiter le problème des projections mais je n'en ai pas besoin pour le moment.

Conclusions

Il est clair que ces méthodes ne remplaceront jamais l'utilisation de logiciels comme GRASS GIS ou l'extension Globe toujours en développement:

figure reprise de www.faunalia.com/quantumgis

Mais elles me sont très utiles en première approche. Elles me permettent de visualiser ce que je veux directement à partir de points sélectionnés  et/ou de combiner les diverses sélections sans devoir passer par GRASS GIS.

Leur utilisation a déjà amené à des corrections dans les codes du module visvis (groups.google.com/forum/). Elles pourraient, à mon avis, facilement être transposées à ArcGIS avec ArcPy.

En faire une extension me semble difficile, car:

Tous les traitements ont été effectués sur Mac OS X  avec QGIS, version "master branch"  de Larry Shaffer disponible à qgis.dakotacarto.com/ et QGIS, version 1.8 de  William Kyngesburye (www.kyngchaos.com/)

Site officiel :QGIS (Quantum GIS)
Autres Liens :Python et les shapefiles (suite) ou le module le plus simple à utiliser (un seul fichier, sans dépendance)
Autres Liens :Python: le module Shapely, géométries, prédicats spatiaux, analyse spatiale, matrices de Clementini (DE-9IM), traitements de shapefiles ou autres, et représentation avec matplotlib


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France


Jython et le géospatial: exemples avec GeoScript (GeoTools) , JTS ( Java Topology Suite), OpenJump, gvSIG, WorldWind ou Geoserver

$
0
0

python

Tout le monde connait le Python standard (ou « C Python »), qui s'exécute dans un environnement C, mais, peu connaissent ou utilisent ses autres implémentations (voir «  Python: géospatial, dialectes (standard, pour ESRI, pour FME, pour GvSIG etc... ») .

Parmi celles-ci, il y a Jython, qui est son implémentation en Java et qui s'exécute dans une Machine Virtuelle Java (JVM). Dans le monde géospatial, il est utilisé comme langage de script pour les SIGs écrits en Java, comme gvSIG ou OpenJump et pour un serveur cartographique, GeoServer.

Mais, tout comme Python, Jython peut-être utilisé directement pour traiter les données géospatiales. Il peut utiliser tous les modules Python écrits en Python pur, mais, comme déjà signalé, il sera impossible d'utiliser des modules écrits en C (GDAL, numpy,...). En contrepartie, Jython pourra utiliser toutes les classes des librairies Java existantes. Ses caractéristiques sont les suivantes (fr.wikipedia.org/wiki/Jython):

  • le code Python est compilé en bytecode Java ;
  • les classes Python peuvent hériter des classes Java;
  • possibilité d'exécuter des codes Python durant le fonctionnement d’un programme Java (utilisée par GvSIG ou OpenJump) ;
  • utilisation d’objets Java dans le code Python.

Dans Jython, la variable CLASSPATH est fondamentale, car c'est elle qui permet l'utilisation de n'importe quelle classe Java. Ainsi si je veux utiliser le driver JDBC natif (postgresql-xxx.jdbc3.jar) pour me connecter à  PostgreSQL  (voir www.chicoree.fr/w/Utiliser_JDBC_%C3%A0_partir_de_Jython):

connexion

  1. # ajustement du CLASSPATH de Jython pour l'importation des classes du driver .jar
  2. $ export CLASSPATH="/Library/Java/Extensions/postgresql-9.0-801.jdbc3.jar"
  3.  
  4. $ jython
  5. # importation des classes (première utilisation, après cela ne sera plus signalé)
  6. *sys-package-mgr*: processing new jar, '/Library/Java/Extensions/postgresql-9.0-801.jdbc3.jar'
  7.  
  8. Jython 2.5.2 (Release_2_5_2:7206, Mar 22011, 23:12:06)
  9. [Java HotSpot(TM)64-Bit Server VM (Apple Inc.)] on java1.6.0_24
  10. Type "help", "copyright", "credits"or"license"for more information.
  11. >>>from org.postgresqlimport Driver
  12. >>>
  13. # si aucune erreur n'est signalée, c'est que ça marche

Je n'irai pas plus loin dans la présentation de Jython (installation sur les diverses plates-formes , principes, etc.) renvoyant les lecteurs à des sites plus spécialisés comme opikanoba.org/java/java-et-python (en français, mais, il y en a beaucoup d'autres). Pour débuter, il y a un livre présenté en Open Source avec une licence Creative Commons 3.0. , « The Definitive Guide to Jython » (www.jython.org/jythonbook/en/1.0/), tous les livres et articles présentés à wiki.python.org/jython/JythonBibliography, des blogs ou des forums comme le  Forum des développeurs (www.developpez.net/forums/). 

Je me limiterai ici  à son application dans le domaine géospatial et dans la suite, nous traiterons:

  • l'utilisation de Jython seul avec des modules géospatiaux (GeoScript, JTS);
  • l'utilisation de Jython comme langage de script de logiciels SIG (OpenJump, GvSIG, Worldwind;
  • l'utilisation de Jython avec un serveur cartographique (GeoServer)

Utilisation de Jython seul

GeoScript ou l'équivalent de Shapely pour Jython

Shapely, dépendant de la librairie C GEOS, ne peut donc pas être utilisé, mais, il  a son équivalent pour Jython, GeoScript, proposé par GeoTools « The Open Source Java GIS Toolkit » qui est un produit de l'OSGeo. GeoScript permet d'utiliser les classes de GeoTools en Jython, JavaScript, Scala et Groovy.

Comment l'installer:

La manière la plus simple est d'installer easy_install (c'est possible depuis les versions 2.5.x de Jython). Pour ce faire, il faut télécharger le script peak.telecommunity.com/dist/ez_setup.py et ensuite:

jython ez_setup.py

Ensuite, après téléchargement et décompression de GeoScript (geoscript.org/py/quickstart.html#quickstart)

jython setup.py install (easy_install va vous installer toutes les dépendances)

Ce qui va vous créer (entre autres choses) deux fichier dans le dossier bin du dossier Jython2.5.x, geoscript et geoscript-classpath

Utilisation

Il y a 2 manières d'utiliser GeoScript:

  • soit à partir de l'application geoscript

    avec geoscript

    1. $ geoscript
    2. Jython 2.5.2 (Release_2_5_2:7206, Mar 22011, 23:12:06)
    3. [Java HotSpot(TM)64-Bit Server VM (Apple Inc.)] on java1.6.0_24
    4. Type "help", "copyright", "credits"or"license"for more information.
    5. >>>
  • soit à partir de jython lui-même

    avec jython

    1. # ajustement du CLASSPATH
    2. export CLASSPATH=`/.../jython2.5.2/bin/geoscript-classpath`
    3. $ jython
    4. Jython 2.5.2 (Release_2_5_2:7206, Mar 22011, 23:12:06)
    5. [Java HotSpot(TM)64-Bit Server VM (Apple Inc.)] on java1.6.0_24
    6. Type "help", "copyright", "credits"or"license"for more information.
    7. >>>import geoscript
    8. >>>
    9.  
    10. # si vous ne le faites pas
    11. $ jython
    12. Jython 2.5.2 (Release_2_5_2:7206, Mar 22011, 23:12:06)
    13. [Java HotSpot(TM)64-Bit Server VM (Apple Inc.)] on java1.6.0_24
    14. Type "help", "copyright", "credits"or"license"for more information.
    15. >>>import geoscript
    16. Error: Could not find GeoTools libraries on classpath.

Ensuite, c'est pratiquement comme avec le module Shapely (voir www.portailsig.org/content/python-le-module-shapely-geometries-predicats-spatiaux-analyse-spatiale-matrices-de-clementi)

traitement

  1. >>>from geoscript import geom
  2. >>> point = geom.readWKT('POINT(45 20)')
  3. >>> point
  4. POINT (4520)
  5. # ou
  6. >>> point = geom.Point(4.607817,50.664930)
  7. >>> point
  8. POINT (4.60781750.66493)
  9. >>># ou
  10. >>> wkt = str(point)
  11. >>> wkt
  12. 'POINT (4.607817 50.66493)'

Mais, en plus, GeoScript permet:

  • les changements de système de projection:

    projections

    1. >>>from geoscript import proj
    2. >>> point = geom.Point(4.607817,50.664930)
    3. >>> point2 = proj.transform(point, 'epsg:4326', 'epsg:31370')
    4. >>> point2
    5. POINT (166901.5939415752150507.1768997945)
  • l'importation directe des shapefiles et leur création (et des couches PostGIS):

    Shapefiles

    1. >>>from geoscript.layerimport Shapefile
    2. >>> macouche = Shapefile('.../montest.shp')
    3. >>>from geoscript.layerimport Shapefile
    4. >>> pntGeoms = [f.geomfor f in macouche.features()]
    5. >>> pntGeoms
    6. [MULTILINESTRING ((0.03168202764976960.6129032258064516, 0.11578341013824880.7177419354838709, 0.22177419354838710.683179723502304, 0.28398617511520740.5656682027649769, 0.36808755760368670.6048387096774194, 0.43260368663594470.7085253456221199, 0.52937788018433190.6071428571428571)), MULTILINESTRING ((0.31624423963133650.8940092165898617, 0.23214285714285720.8421658986175116, 0.24596774193548390.7039170506912441, 0.17569124423963130.5794930875576036, 0.27361751152073730.4331797235023041))]
  • leur représentation graphique (directement par Java):

    plot

    1. >>>from geoscript.viewerimport plot
    2. >>> plot(pntGeoms)
  • etc. (voir geoscript.org/py/index.html ou  geoscriptblog.blogspot.com/ pour de nombreux exemples)

avec la librairie Java,  JTS - Java Topology Suite

La Java Topology Suite est la librairie Java pour le traitement des géométries en 2 dimensions. La librairie C GEOS en est dérivée. Elle est utilisée par GeoTools, GeoServer, tous les Jump's, GvSIG ou Udig par exemple (voir tsusiatsoftware.net/jts/jts-links.html#systems).  GeoScript est basée sur cette librairie, mais toutes ses classes n'ont pas encore été portées.

JTS ne possède pas d'implémentation Python, mais nous avons vu que Jython permet d'employer n'importe quelle classe Java. Alors certains ont agi...

Jython et JTS

  1. >>>importsys
  2. >>># autre manière d'importer des classes (ici JTS)
  3. >>>sys.path.append('.../jts-1.8.0/lib/jts-1.8.jar')
  4. >>># test d'une classe de JTS
  5. >>>from com.vividsolutions.jts.geomimport Point
  6. >>>

Le problème est de trouver comment appeler les classes Java de JTS en Jython. Cela est réalisé par certains à coup d'essais et d'erreurs. Il est cependant nécessaire de bien connaitre la bibliothèque Java au préalable. C'est le cas de Carl Trachte sur son site geoscriptblog.blogspot.com/2010/06/unwrapped-jts-with-python.html, qui offre de très belles applications.

résultat d'une triangulation de Delaunay avec JTS

Utilisation de Jython comme langage de script de logiciels SIG

Jython peut aussi, en théorie, être utilisé comme langage de script dans des applications Java comme gvSig, OpenJump (et la grande famille des Jump's, voir www.portailsig.org/content/sortie-de-la-version-151-de-openjump-gis-et-catalogue-de-tous-les-logiciels-derives-de-jump) ou Worldwind Java. Pourquoi en théorie ? Parce que son utilisation nécessite une bonne connaissance des classes Java de l'application et donc de Java et qu'il y a très peu d'exemples disponibles.

avec OpenJump (et la famille des Jump's)

Nous commençons par OpenJump car son module Jython est l'application directe de l'utilisation de la Java Topology Suite (JTS).

Malheureusement, bien que très puissant, il faut « chipoter » pour trouver comment ce module fonctionne. Il n'y a ni tutoriel ni exemples. J'y suis arrivé en analysant systématiquement les fonctions des objets obtenus et la connaissance de la manière de fonctionner des modules Python dans le domaine geospatial. Comme cet aspect est mal connu pour OpenJump, une petite synthèse des résultats obtenus:

traitements avec le module Jython d'OpenJump

  1. # importation du module principal d'OpenJump
  2. from org.openjump.util.python.JUMP_GIS_Frameworkimport*
  3. # obtention des couches sélectionnées
  4. couches = getSelectedLayers()
  5. # fonctions disponibles pour les couches sélectionnées:
  6. dir(couches)
  7. ['add', 'addAll', 'class', 'clear', 'clone', 'contains', 'containsAll', 'empty', 'ensureCapacity', 'equals', 'get', 'getClass', 'hashCode', 'indexOf', 'isEmpty', 'iterator', 'lastIndexOf', 'listIterator', 'notify', 'notifyAll', 'remove', 'removeAll', 'retainAll', 'set', 'size', 'subList', 'toArray', 'toString', 'trimToSize', 'wait']
  8.  
  9. # itération à travers les couches sélectionnées
  10. for i in couches.iterator():
  11. print couche.name
  12. ...
  13. # fonctions disponibles pour une couche:
  14. dir(couche)
  15. ['FIRING_APPEARANCE_CHANGED_ON_ATTRIBUTE_CHANGE', 'addStyle', 'addUndo', 'basicStyle', 'blackboard', 'class', 'cloneStyles', 'dataSourceQuery', 'defaultLineColor', 'description', 'dispose', 'drawingLast', 'editable', 'equals', 'featureCollection', 'featureCollectionModified', 'featureCollectionWrapper', 'fireAppearanceChanged', 'fireLayerChanged', 'getBasicStyle', 'getBlackboard', 'getClass', 'getDataSourceQuery', 'getDescription', 'getFeatureCollectionWrapper', 'getLabelStyle', 'getLayerManager', 'getMaxScale', 'getMinScale', 'getName', 'getStyle', 'getStyles', 'getTask', 'getVertexStyle', 'hasReadableDataSource', 'hashCode', 'isDrawingLast', 'isEditable', 'isFeatureCollectionModified', 'isReadonly', 'isScaleDependentRenderingEnabled', 'isSelectable', 'isSynchronizingLineColor', 'isVisible', 'labelStyle', 'layerManager', 'maxScale', 'minScale', 'name', 'notify', 'notifyAll', 'readonly', 'removeStyle', 'scaleDependentRenderingEnabled', 'selectable', 'setDataSourceQuery', 'setDescription', 'setDrawingLast', 'setEditable', 'setFeatureCollection', 'setFeatureCollectionModified', 'setLayerManager', 'setMaxScale', 'setMinScale', 'setName', 'setReadonly', 'setScaleDependentRenderingEnabled', 'setSelectable', 'setStyles', 'setSynchronizingLineColor', 'setVisible', 'styles', 'synchronizingLineColor', 'task', 'toString', 'tryToInvalidateEnvelope', 'vertexStyle', 'visible', 'wait']
  16.  
  17. ### Applications ###
  18. # itération à travers les géométries d'une couche
  19. fc = couches[0].featureCollectionWrapper
  20. for elem inrange(fc.size):
  21. obj = fc.features[elem]
  22. geom = obj.getGeometry()
  23. ...
  24.  
  25. # fonctions disponibles pour la géométrie d'un élément (la 3D est reconnue par défaut):
  26. dir(geom)
  27. ['SRID', 'apply', 'area', 'boundary', 'boundaryDimension', 'buffer', 'centroid', 'class', 'clone', 'compareTo', 'contains', 'convexHull', 'coordinate', 'coordinates', 'coveredBy', 'covers', 'crosses', 'difference', 'dimension', 'disjoint', 'distance', 'empty', 'envelope', 'envelopeInternal', 'equals', 'equalsExact', 'equalsNorm', 'equalsTopo', 'exteriorRing', 'factory', 'geometryChanged', 'geometryType', 'getArea', 'getBoundary', 'getBoundaryDimension', 'getCentroid', 'getClass', 'getCoordinate', 'getCoordinates', 'getDimension', 'getEnvelope', 'getEnvelopeInternal', 'getExteriorRing', 'getFactory', 'getGeometryN', 'getGeometryType', 'getInteriorPoint', 'getInteriorRingN', 'getLength', 'getNumGeometries', 'getNumInteriorRing', 'getNumPoints', 'getPrecisionModel', 'getSRID', 'getUserData', 'hashCode', 'interiorPoint', 'intersection', 'intersects', 'isEmpty', 'isRectangle', 'isSimple', 'isValid', 'isWithinDistance', 'length', 'norm', 'normalize', 'notify', 'notifyAll', 'numGeometries', 'numInteriorRing', 'numPoints', 'overlaps', 'precisionModel', 'rectangle', 'relate', 'reverse', 'setSRID', 'setUserData', 'simple', 'symDifference', 'toString', 'toText', 'touches', 'union', 'userData', 'valid', 'wait', 'within']
  28. # d'où :
  29. geom.geometryType
  30. 'Polygon'
  31. # la géométrie
  32. geom.envelope
  33. POLYGON ((226023.718173, 226023.728173.5, 234026.328173.5, 234026.318173, 226023.718173))
  34. # nombre de points
  35. geom.getNumPoints()
  36. 5
  37. # les coordonnées des points de la géométrie
  38. geom.coordinates
  39. array(com.vividsolutions.jts.geom.Coordinate,[(226023.7, 28173.5, NaN), (234025.0, 28173.5, NaN), (234026.3, 18173.1, NaN), (226024.5, 18173.0, NaN), (226023.7, 28173.5, NaN)])
  40. geom.coordinate
  41. (226023.7, 28173.5, NaN)
  42. # le centroide
  43. geom.getCentroid()
  44. POINT (230024.8683380178723173.222923277397)
  45. # la limite extérieure de la géométrie
  46. geom.getBoundaryDimension()
  47. 1
  48. geom.getBoundary()
  49. LINEARRING (226023.728173.5, 23402528173.5, 234026.318173.1, 226024.518173, 226023.728173.5)
  50. # ou
  51. geom.exteriorRing
  52. LINEARRING (226023.728173.5, 23402528173.5, 234026.318173.1, 226024.518173, 226023.728173.5)
  53.  
  54. # les attributs
  55. fonctions disponibles pour les attributs d'un élément:
  56. >>> dir(obj)
  57. ['ID', 'attributes', 'class', 'clone', 'compare', 'compareTo', 'equals', 'geometry', 'getAttribute', 'getAttributes', 'getClass', 'getDouble', 'getGeometry', 'getID', 'getInteger', 'getSchema', 'getString', 'hashCode', 'isModified', 'modified', 'notify', 'notifyAll', 'schema', 'setAttribute', 'setAttributes', 'setGeometry', 'setModified', 'setSchema', 'toString', 'wait']
  58.  
  59. # d'
  60. obj.attributes
  61. array(java.lang.Object,[POLYGON ((226023.728173.5, 23402528173.5, 234026.318173.1, 226024.518173, 226023.728173.5)), '71/5', '71', '225W', 'LAMORTEAU', 'LAMORTEAU - RUETTE', '71/5-6'])
  62. # on retrouve donc la géométrie
  63. obj.getGeometry()
  64. POLYGON ((226023.728173.5, 23402528173.5, 234026.318173.1, 226024.518173, 226023.728173.5))
  65. # ou
  66. obj.getAttribute(0)
  67. POLYGON ((226023.728173.5, 23402528173.5, 234026.318173.1, 226024.518173, 226023.728173.5))
  68. # attributs éventuels (fonction de la liste obtenue)
  69. obj.getAttribute(1)
  70. '71/5'
  71. obj.getString(3)
  72. '225W'

Et donc, il y a moyen d'utiliser directement toutes les fonctions de la JTS:

Il est aussi possible de remarquer que bien qu'OpenJump ne gère pas les projections, son module Jython permet théoriquement de le faire (SRID).

Il a aussi moyen de créer des extensions/modules en Jython (voir mekandizim.mersin.edu.tr/belgeler/SSA-OJ.pdf, par exemple, et son module disponible à mekandizim.mersin.edu.tr/eklentiler/eklentiler.html)

avec gvSIG:

Le traitement est comparable à celui d'OpenJump mais il sera moins développé ici du fait d'un changement de module avec la future version 2.0 en développement.

from gvsig import*from commonsdialog import*def main():  layer = currentLayer()  if layer == None:    msgbox("Vous devez choisir une couche!", "AVIS", 1)    return  emax = 0.0  emin = 0.0  for feature in layer.features():    if feature.ELEVATION> emax :      emax = feature.ELEVATION    if feature.ELEVATION< emin or emin ==0.0:      emin = feature.ELEVATION  msgbox("Altitude maximum=%s et minimum=%s"%(emax, emin), "Elevation", 0)

Résultat:

image reprise de blog.gvsig.org/2012/10/22/scripting-exprime-tu-gvsig/

Il est aussi possible de créer des extensions/modules en Jython (voir mekandizim.mersin.edu.tr/belgeler/book.pdf et la même référence que pour OpenJump)

avec Worldwind Java

Je n'ai trouvé qu'un seul exemple d'utilisation à www.perrygeo.net/wordpress/.

Utlisation de Jython avec un serveur cartographique: GeoServer

GeoServer a aussi lancé son module Jython (geoserver.org/display/GEOS/Python+Scripting+Extension). Encore expérimental, il permet d'interroger Geoserver et même d'interagir avec lui.

from geoserver import Catalogcat = Catalog('aff_geol')print cat.stores()[u'affleurements', u'cartes']st = cat['affleurements']print st.layers()[u'affs']l = st['affs']print l.count()3214print l.bounds()(107206.74,148102.45,114110.46,157514.40, EPSG:31370)

Conclusions

Tant qu'il s'agit de modules « clés en main »,  comme GeoScript, Jython est facile à utiliser, mais dès que l'on veut utiliser directement une librairie Java, une bonne connaissance de ce langage s'impose. Le même constat s'adresse aussi aux langages de scripts des SIGs.

Le nouveau module gvsig de la version 2 de gvSIG parait néanmoins se rapprocher beaucoup plus d'une version Python (sans devoir connaître le détail des classes Java dont il dépend). Et finalement j'ai été fort impressionné par les possibilités du module d'OpenJump (logiciel à mon avis sous-évalué).

Je regrette toujours qu'il ne soit pas possible d'utiliser tout en même temps, à savoir PyQGIS dans gvSIG et l'inverse ou les deux dans un shell, par exemple....

Mais, en pratique, connaître Python + Java, le tout sans un tutoriel détaillé, rend son utilisation quelque peu difficile pour le commun des mortels ...

Tous les traitements ont été effectués sur Mac OS X avec Jython 2.5.2, GeoScript version 1.1, OpenJump version 1.5.1 rev.2721 et diverses implémentations de gvSIG.


Site officiel :Jython
Site officiel :GeoTools (OSGeo)
Site officiel :GeoScript
Site officiel :Java Topology Suite (VIVID Solutions)
Site officiel :JTS Topology Suite
Site officiel :JTS (Sourceforge)
Autres Liens :Python: géospatial, dialectes (standard, pour ESRI, pour FME, pour GvSIG etc.) et incompréhensions...


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France

Plugin QGIS - MultiQml

$
0
0

Dans une discussion récente du forumSIG, un de nos membres pointait certains manques actuels de QGIS. Cette discussion est très intéressante dans une optique de faire évoluer QGIS, cela dit il apparait que certaines fonctionnalités sont déjà présentes via les plugins. C'est le cas pour un des besoins listés :

13- Possibilité de sélectionner plusieurs couches pour donner un style identique.

Le plugin MultiQml est fait pour ça. Il ne fonctionne pas exactement comme précisé dans la demande, mais il permet bien d'affecter un style à n couches de données, et cela pour des vecteurs mais également des rasters.

Alors comment ce plugin fonctionne t'il ?

Celui ci est très simple d'utilisation, imaginons que j'ai des couches de communes par département et que je veuille affecter le même style à l'ensemble de ces couches, soit 96. (ici je choisis de représenter les communes en fonction de leur statut : commune simple, chef-lieu de canton, sous préfecture, préfecture, l'exemple n'est pas forcément très pertinent mais il permet de voir le fonctionnement du module).

Il suffit de définir un style pour une couche en particulier et l'exporter en qml :

Propriété de la couche / style / sauvegarder le style

Une fois que vous avez sauvegardé votre fichier de style qml, il vous suffit de lancer MultiQml via Extensions / MultiQml / MultiQml (bien entendu il est possible d'utiliser un qml déjà existant et ainsi éviter l'étape de création du style) :

Vous voyez dans l'interface que le plugin fonctionne pour les rasters et les vecteurs, dans notre cas il faut cocher Vector pour voir apparaître la liste des couches vecteurs ouvertes dans qgis :

Il suffit maintenant de choisir les couches auquelles on souhaite affecter le style, à noter que l'on peut les sélectionner toutes via le bouton Select all layers puis en faisant Apply style ..., on va chercher le qml préalablement généré, et le tour est joué !

Ce module s'installe classiquement sous QGIS via l'installateur d'extensions.


Site officiel :GIS-LAB
Site officiel :MultiQml


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

Plugin QGIS - NumericalDigitize / Numerical Vertex Edit

$
0
0

Récemment j'ai voulu créer un polygone (un rectangle) avec des coordonnées que l'on m'avait fournies. J'ai cherché rapidement dans la liste des plugins pour voir si quelque chose existait pour faire cela, je n'ai rien trouvé et j'ai du faire ça en python avec la console.

J'ai du regarder trop vite car il existe pourtant bel et bien un plugin très simple permettant de le faire, il s'agit de NumericalDigitize.

Après l'avoir installé et ouvert une couche en édition, il suffit de cliquer sur l'icone  pour ouvrir la fenêtre pour saisir les coordonnées :

Il est possible de rentrer les coordonnées soit dans le système de référence spatial du projet ou celui de la couche. Il ne reste plus qu'à saisir les coordonnées et valider par OK.

Et voila le résultat, un beau carré (bien entendu cela fonctionne pour autre chose que des carrés ...)

Si il apparait finalement que vous vous êtes trompé dans une des coordonnées, il existe un module complémentaire, Numerical Vertex Edit, qui permet de modifier les coordonnées d'un sommet, on l'active avec l'icone (attention ils se ressemblent beaucoup) toujours en mode édition de la couche.

Il suffit ensuite de cliquer sur un sommet pour que la fenêtre s'affiche :

et modifier les valeurs comme on le souhaite :

Et voila le résultat :

A nouveau deux petits plugins très simples d'utilisation mais qui peuvent également être très utiles ...

Ces deux plugins sont développés par Cédric Morï.


Site officiel :NumericalDigitize
Site officiel :Numerical Vertex Edit


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

Plugin QGIS - Image boundary et Show/Hide Image : Gérer un catalogue d'images

$
0
0

Comment gérer un catalogue d'image sous QGIS, par exemple la bd ortho sur un département? Ceci est possible aisément à l'aide de deux extensions : Image boundary et Show/Hide Image.

Image boundary va permettre de générer un index de dalles qui sera composé de polygones représentant chaque dalle et dont la table attributaire aura un champ chemin et un champ image contenant le nom de chaque image.

On lance l'extension par Extensions / Image Boundary :

Il faut pointer sur le répertoire contenant les images puis lancer "Process images". On peut ensuite lancer "Create Vector Grid Layer" qui génère la couche de polygones.

Le fichier généré doit être sauvegardé. On peut ensuite le réouvrir afin de l'utiliser via Show / Hide Image pour gérer l'affichage des images :

Le premier onglet liste les catalogues disponibles dans QGIS.

Il faut sélectionner le catalogue puis cliquer sur la grille dans la carte :

Les images correspondantes s'affichent.

En cliquant à nouveau sur une image ouverte dans la carte, celle ci se masque.

Attention, ces deux plugins ne font pas partie du dépot officiel, il faut rajouter le dépôt http://pyqgis.org/repo/contributed/plugins.xml pour pouvoir installer ces plugins.


Site officiel :QGIS
Site officiel :Le dépot des plugins


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

Plugin QGIS - qgSurf, calcul des intersections entre des surfaces géologiques (planes) et un MNT

$
0
0

En cartographie géologique, en analyse structurale ou en analyse géomorphologique, il est nécessaire de déterminer la trace topographique d'une surface géologique, c'est-à-dire son intersection avec la topographie,

intersection entre des couches ou un volume géologique et la topographie, réalisé avec GRASS GIS

tracé de la limite géologique sur une carte topographique (intersection)

en fonction de son orientation et de son pendage (angle d'inclinaison de la surface considérée localement comme un plan), mesurés en un ou divers points:

Si l'on dispose d'un MNT, est-il possible de calculer automatiquement cette ligne d'intersection ?

C'est le travail realisé par Mauro Alberti qui a créé le plugin qgSurf pour Quantum GIS, basé sur son application en pur Python, gSurf. Les 2 implémentations utilisent les modules Python PyQT4, osgeo/gdal-ogr et matplotlib. Les algorithmes utilisés ont été expliqués et détaillés pour la version en Python seul à Intersezioni tra DEM e superfici planari, un tema di interesse in geologia et gSurf: una applicazione Python per calcolare interattivamente l'intersezione fra piani e DEM.

application Python gSurf

Préambules

Supposons que l'on a mesuré un plan structural sur un affleurement géologique (stratification d'une couche mesurée en pendage orienté,  20° vers 164, en bleu).

fonds topographique OpenStreetMap

On dispose aussi d'un MNT (au format ESRI .asc ou GeoTIFF):

DEM SRTM

Extension qgSurf

L'extension se lance depuis le menu Extension et son interface est la suivante:

Après avoir complété tous les éléments, le résultat est obtenu par le bouton Calculate -> ligne en blanc (ici):

Les points et la ligne résultants peuvent ensuite être exportés sous forme de fichiers shapefiles et récupérés dans QGIS:

L'option Lineaments, permet d'analyser des structures linéaires dont on voudrait connaître la direction et le pendage.

Ajustement à une des fractures:

Petits problèmes et solutions

Le principal petit problème provient du fait que les lignes et les points résultants sont extrapolés sur l'emprise du MNT. Le fait de pouvoir exporter les résultats permet facilement de régler ce problème dans le cas de plusieurs mesures.

Résultat final

Pour aller plus loin

Il est ensuite possible d'utiliser la technique exposée dans QGIS, représentation 3D des couches vectorielles (shapefiles dits 3D ou shapefiles avec attributs z) avec les modules Python Matplotlib ou Visvis à partir de la console Python pour visualiser les résultats en 3D:

Tous les traitements ont été effectués sur Mac OS X  avec QGIS, version 1.9 "master branch"  de Larry Shaffer disponible à qgis.dakotacarto.com/


Site officiel :qgSurf, a Python program for the interactive determination of intersections between DEM and planes
Site officiel :QGIS
Site officiel :plugin qgSurf


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France

Python: utilisation des couches vectorielles et matricielles dans une perspective géologique, sans logiciel SIG

$
0
0
Niveau Intermédiaire
Logiciels utilisésPython
Plateforme Windows | Mac | Linux | FreeBSD

En pratique, le titre est un peu fallacieux, car on a toujours besoin d'un logiciel SIG pour créer les couches à traiter ou pour visualiser les résultats (quoique...), mais je vais présenter ici une introduction à l'utilisation des objets géomatiques (couches vectorielles et matricielles géoréférencées)  dans des scripts Python à finalité géologique, puisque c'est mon métier.

J'en ai écrit un grand nombre basés sur les algorithmes présentés, entre autres, par R. Groshong dans 3-D Structural Geology (Springer, 2008,) ou R. Allmendinger et N. Cardozo  dans Structural Geology Algorithms Vectors and Tensors (Cambridge University Press, 2011), mais les auteurs ne tiennent pas grand compte des possibilités offertes par l'utilisation des objets géomatiques.  Je vais donc essayer de vous expliquer ici comment les traiter et/ou les combiner avec plusieurs modules Python dont le principal est osgeo (gdal et ogr), le tout sans utiliser un logiciel SIG.

En pratique, je vais  refaire en Python ce que j'avais présenté dans Grass Gis et R: superposition des couleurs d'un raster quelconque sur un profil topographique élaboré à partir d'un MNT mais en allant plus loin (en y ajoutant des valeurs structurales de stratification, par exemple). Cette première partie peut intéresser tous les Sigistes.

Mais, en soi, créer un profil topographique ne sert pas à grand-chose, surtout qu'il faut le compléter pour en faire une coupe géologique. Je présenterai en final un des résultats de mes traitements plus géologiques avec la création d'un profil aval-pendage (« down-plunge profile » ou coupe suivant le plongement de l'axe du pli, voir www.uwgb.edu/dutchs/structge/SL165DownPlunge.HTM),  à partir du script originel en MATLAB présenté dans Structural Geology Algorithms Vectors and Tensors, pp. 51-55, converti en Python par mes soins.

Nous verrons donc dans la suite:

  • Comment lire les données de type vectoriel ou matriciel (depuis un fichier shapefile ou un fichier raster, depuis une geodatabase fichier de GRASS GIS et depuis un service WMS);
  • Comment extraire les valeurs d'un pixel d'un fichier matriciel;
  • Comment croiser un fichier de type vectoriel et un fichier de type matriciel pour en extraire les données (altitude, couleurs RGB);
  • Comment créer un profil topographique en combinant une couche vectorielle, un MNT et un fichier raster ;
  • Comment ajouter les points d'intersection entre 2 fichiers vectoriels (profil et limites des objets géologiques);
  • Comment ajouter les valeurs structurales de stratification (pendage et direction) et calculer le pendage apparent sur la ligne de coupe;
  • Exemple d'application géologique.

Les résultats seront visualisés ici avec le module matplotlib (et le module visvis pour une figure), mais tous les résultats peuvent être exportés en taille réelle dans divers formats dont le vectoriel (PDF ou SVG) pour être utilisés dans d'autres logiciels.

Comment lire les données

Les fichiers de type vectoriel

Je ne reviendrai pas ici sur le cas des fichiers vectoriels de type shapefile, déjà traité sur le Portail.  Les formats que le module ogr dans osgeo peut lire sont obtenus par la commande:

ogrinfo --formats

Cela peut varier suivant les installations, mais, il s'agit de la majorité des formats classiques avec, depuis peu, ceux qui sont présents dans les geodatabases fichiers d'ESRI (FileGDB).

Ouvrir un fichier vecteur dans la geodatabase fichier de GRASS GIS n'est qu'une question de chemin:

from osgeo import ogr# ouverture d'une couche vectorielle de GRASS GISds = ogr.Open('/Users/martinlaloux/grassdata/geol/MNT/vector/ligne_de_coupe/head')couche = ds.GetLayer(0)detail = couche.GetFeature(0)geom = detail.GetGeometryRef()# noeuds de la lignefor i inrange(geom.GetPointCount()):     xy = geom.GetPoint(i)     print xy(206643.21517600652, 125181.18058575876)(201007.33432923188, 121517.85552059766)

Les fichiers de type matriciel (raster, MNT,...)

De la même manière, les formats que le module gdal dans osgeo permet de lire sont indiqués par la commande:

gdalinfo --formats

Je vais présenter ici quelques exemples:

Fichier raster géoréférencé:

Si je dispose d'un classique raster géoréférencé (vu ici dans QGIS):

La lecture du fichier se fait de la manière suivante:

from osgeo import gdalfichier = '/Users/Shared/cartes_geol/hen_raer.jpg'couche = gdal.Open(fichier)gt =couche.GetGeoTransform()bandes = couche.RasterCount# ce qui donne les paramètres de géoréférencementprint gt(258012.37107330866, 2.11668210080698, 0.0, 163176.63853988209, 0.0, -2.1168501270110074)# et le nombre de bandes de couleursprint bandes3

La transformation de ce fichier raster en vecteurs (array) numpy se fait avec la commande gdal ReadAsArray (ici pour la première bande de couleur):

bande1 = couche.GetRasterBand(1)donnee1 = bande1.ReadAsArray()etc...

Une fois que les bandes sont transformées en vecteurs, il est facile d'extraire le morceau de carte compris dans la zone limitée par un rectangle rouge et de visualiser le résultat avec la commandeimshowdu module matplotlib:

Couche MNT dans GRASS GIS:

C'est la même démarche avec une couche matricielle présente dans une geodatabase fichier de GRASS GIS, il suffit d'ajuster le chemin d'accès:

fichier = '/Users/martinlaloux/grassdata/geol/MNT/cellhd/dem10m'couche = gdal.Open(fichier)gt =couche.GetGeoTransform()bandes = couche.RasterCount # ce qui donne les paramètres de géoréférencementprint gt(41995.0, 10.0, 0.0, 172185.0, 0.0, -10.0) # et le nombre de bandes de couleursprint bandes1

(le MNT est illustré ici sous forme d'une surface ombragée dans GRASS GIS)

Service WMS

L'organisme où je travaille offre aussi un service WMS pour les cartes géologiques (geoservices.wallonie.be/arcgis/rest/services/SOL_SOUS_SOL/CARTE_GEOLOGIQUE_SIMPLE/MapServer, image vue ici avec OpenLayers). GDAL sait accéder aux servicesWMSvia un fichierde configuration XML dont les détails figurent dans www.gdal.org/frmt_wms.html. Ce fichier XMLdécrit habituellementl'URL,la projection,le type d'image et la (les) couche(s)requise(s), mais il est aussi possible de sélectionnerl'étendue désirée (et donc pas besoin de numpy ici).

En ouvrant le fichier XML, le résultat est:

fichier = '/Users/martinlaloux/wms/carte_geol.xml'couche = gdal.Open(fichier)

service WMS visualisé avec OpenLayers

Comment extraire la valeur d'un pixel

Une fois ces fichiers ouverts, comment obtenir les valeurs des rasters situés sous un objet vectoriel comme le point bleu ici (sur une carte géologique et un MNT):

Commençons par le MNT (dans GRASS ici, mais ça peut être un fichier de type ASCII Grid d'ESRI, .asc ou  GeoTIFF):

fichier = '/Users/martinlaloux/grassdata/geol/MNT/cellhd/dem10m'couche = gdal.Open(fichier)gt =couche.GetGeoTransform()bandes = couche.RasterCountprint bandes1print gt(263104.72544800001, 10.002079999999999, 0.0, 155223.647811, 0.0, -10.002079999999999)

Les valeurs de géoréférencement obtenues ont été expliquées dans Python: géoréférencement et worldfiles (tfw, jgw,pngw,...) et je ne reviendrai pas dessus . Dès lors, passer des coordonnées xy du point à celles du raster est une simple règle de trois:

x,y  = (263220.5,155110.6)rasterx = int((x - gt[0]) / gt[1])rastery = int((y - gt[3]) / gt[5])print rasterx,rastery11,11

(Attention, je ne tiens pas compte ici de la particularité de GDAL pour le géoréférencement, voir Python: géoréférencement et worldfiles (tfw, jgw,pngw,...))

)

Puisqu'il n'y a qu'une bande, la valeur du pixel sous le point est:

print couche.GetRasterBand(1).ReadAsArray(px,py, 1, 1)array([[222]], dtype=int32)

Dans le cas présent (MNT), cette valeur correspond à une altitude (222 m).

Dans le cas de la couche géologique, nous avons vu qu'il y a 3 bandes. Après avoir recalculé les valeurs px et py en fonction du raster,  les résultats sont:

bande1 = couche.GetRasterBand(1)bande2 = couche.GetRasterBand(2)bande3 = couche.GetRasterBand(3)print bande1.ReadAsArray(px2,py2, 1, 1)[[253]]print bande2.ReadAsArray(px2,py2, 1, 1)[[215]]print bande3.ReadAsArray(px2,py2, 1, 1)[[118]]

qui constituent, en pratique, les valeurs RGB de la couleur du pixel.

C'est la même démarche avec le service WMS:

fichier = 'carte_geol.xml'couche = gdal.Open(fichier)gt =couche.GetGeoTransform()bande1 = couche.GetRasterBand(1)print bande1.ReadAsArray(px,py, 1, 1)[[247]]etc.

Quelque soit le type de raster utilisé, il est donc possible d'obtenir la valeur du pixel situé sous un point de coordonnées x, y. On peut alors créer une fonction universelle qui permet d'extraire la valeur d'un pixel sous forme de liste, quel que soit le nombre de bandes. Contrairement à Python ou la valeur des indices commence à 0 dans les boucles (j ici, si trois bandes, les valeurs sont 0,1,2), celle de GetRasterBand commence à 1 (valeurs 1, 2, 3). Il n'y a donc pas de GetRasterBand(0) d'où le j+1 dans la boucle:

def Val_raster(x,y,src_ds,bandes,gt)      col=[]      px = int((x - gt[0]) / gt[1])      py =int((y - gt[3]) / gt[5])      for j inrange(bandes):         bande = src_ds.GetRasterBand(j+1)         data = band.ReadAsArray(px,py, 1, 1)         col.append(data[0][0])      return col

Application:

# MNTpx1 = int((x - gt1[0]) / gt1[1])py1 = int((y - gt1[3]) / gt1[5])# couche géologiquepx2 = int((x - gt2[0]) / gt2[1])py2 = int((y - gt2[3]) / gt2[5])# résultatsprint Val_raster(x,y,couche1, bandes1,gt1)[222]# altitude print Val_raster(x,y,couche2, bandes2,gt2)[253, 215, 118]# valeurs RGB 

Comment croiser un fichier de type vectoriel et un fichier de type matriciel pour en extraire les données.

Ligne de profil et fichiers rasters

Il est donc possible de croiser n'importe quel fichier vectoriel (en le transformant en point) avec des fichiers rasters pour en extraire les valeurs. 

Le profil topographique (en bleu) est ici figuré sur la carte géologique et sur le MNT (représenté ici par un relief ombragé provenant de GRASS GIS.

La première chose à faire est d'ouvrir le fichier vecteur (ligne bleue, couche GRASS ou fichier shapefile) avec le module ogr, puis de générer des points équidistants sur la ligne. Ogr ne permet pas encore de le faire, mais le module Shapely, oui, avec la fonction interpolate. Comme cette ligne de profil n'a pas besoin d'être droite (il est possible de traiter une ligne avec plusieurs segments), nous utiliserons la fonction Union de ogr pour n'obtenir qu'un seul objet géométrique.

# traitement de la ligne de coupefrom shapely.wkbimport loads# création d'une couche ogr LineString pour regrouper tous les segments# éventuels d'une couche ligneprofilogr = ogr.Geometry(ogr.wkbLineString)# ouverture du fichier shapefile et intégration des segments éventuelssource = ogr.Open('profilHC2.shp')cshp = source.GetLayer()for element in cshp:      geom =element.GetGeometryRef()      profilogr = profilogr.Union(geom)# transformation en géométrie Shapelyprofilshp = loads(profilogr.ExportToWkb())# création des points équidistants sur la ligne avec un pas de 20mlongueur=profilshp.lengthx = []y = []z = []# couleur RGBcouleur = []# distance pour le profil topographiquedista = []for distance inrange(0,longueur,20):         # création du point sur la ligne         point = profilshp.interpolate(distance)         xp,yp=point.x, point.y         x.append(xp)         y.append(yp)         # extraction de la valeur altitude à partir du MNT         z.append(Val_raster(paramètres de la couche MNT)[0])         # extraction des couleurs RGB à partir de la couche raster         couleur.append(Val_raster(paramètres de la couche raster))         dista.append(distance)

Ce qui permet de dessiner le profil en 3D (x,y,z) (modules matplotlib)  ou de le placer sur le MNTà titre de contrôle (module visvis):

et de dessiner le profil 2D classique (distance, z x 10):

et sans exagération des hauteurs:

Comment ajouter les points d'intersection entre 2 fichiers vectoriels (profil et limites des objets géologiques)

Il est aussi possible d'utiliser la couche vectorielle des limites des formations géologiques (en rouge ici):

Le module ogr ne dispose pas encore de la possibilité de calculer directement l'intersection entre 2 couches vectorielles (ce sera disponible dans la version 1.10) mais en transformant les 2 couches en 2 objets géométriques uniques (avec la fonction Union de ogr)  il est possible d'utiliser la fonction Intersection de ogr  (il est aussi possible de le faire avec le module Shapely):

# ouverture de la couche limites = ogr.Geometry(ogr.wkbLineString)source2 = ogr.Open('lim_form_HC2.shp')lshp = source2.GetLayer()for element in lshp:     geom =element.GetGeometryRef()     limites = limites.Union(geom)# points d'intersection entre les 2 couchesptintersec = profilogr.Intersection(limites)

avec comme résultat la série de points d'intersection:

  

et le profil résultant est (z x 10) :

et sans exagération de hauteur:

Comment ajouter des valeurs structurales de stratification (pendage et direction) en calculant le pendage apparent sur la ligne de coupe.

Si, comme sur cette carte géologique, on dispose de mesures de pendage et de direction, il y a moyen d'aller beaucoup plus loin en se basant sur les scripts Python apparentdip.py de Geologize (geo.distortions.net/search/label/apparent%20dip) qui permet de calculer le pendage apparent de la mesure sur la ligne de coupe et les intersections ou lineorientation_07.py de Scientific and Gis programming with Python and Qt (www.malg.eu/lineangle.php) qui permet de calculer les angles entre  des lignes qui se croisent et leurs intersections. Je les ai combinés en utilisant Shapely en plus d'ogr pour arriver à un script qui répond à mes besoins.

Procédure

Le script commence par créer une zone tampon (buffer) à partir de la ligne de coupe pour sélectionner automatiquement les points à retenir (il est possible de choisir la distance de la zone tampon). Je peux aussi choisir les mesures qui me conviennent en me basant sur la connaissance de la structure géologique, sans zone tampon:

Les lignes de direction des couches géologiques sont ensuite interpolées et tracées et les points d'intersection avec la ligne de profil sont calculés:

ainsi que les pendages apparents sur la coupe (en fonction des angles entre les lignes de direction et la ligne de profil):

ce qui permet de placer ces points sur la coupe avec l'inclinaison des couches:

  • sans exagération des hauteurs

 

  • et avec exagération des hauteurs (x10) et inclinaison du pendage calculée en fonction de l'exagération (= arctan(pendage x 10)):

Exemples d'application géologique:

R. Allmendinger et N. Cardozo (Structural Geology Algorithms Vectors and Tensors) ont proposé un script MATLAB pour créer des « down-plunge profile » (DownPlunge.m). Transformé en script Python, il est ici appliqué à leur exercice 3.5  de la p.65 (anticlinal de Big Elk, Idaho, USA). Une animation du processus est disponible à www.geo.cornell.edu/geology/classes/RWA/EAS_5220/problem_sets/big_elk_anticline_graphics/big_elk_anticline_animation.html

  • les données de départ sont des points situés le long des limites des couches (avec des coordonnées x,y, et z). Ils peuvent être automatiquement obtenus avec les procédures décrites:

  • ces coordonnées x,y,z  sont transformées dans le système de coordonnées du pli avec l'axe z' parallèle à l'axe du pli (26 vers 125 ici) et l'axe y' perpendiculaire à z'. Le plan de la coupe est  x'y' (changement de système de coordonnées, voir l'animation):

  • et le processus est réalisé directement dans la console Python de QGIS ou avec un script Python seul (figure précédente):

Conclusions

Pour faire cela, il est nécessaire de connaître Python, me direz-vous. Oui, certes, mais il me semble qu'il est de plus en plus nécessaire de connaître un langage de programmation au vu de la puissance des ordinateurs. C'est d'ailleurs ce que montrent R. Allmendinger et N. Cardozo (Structural Geology Algorithms Vectors and Tensors) avec leurs nombreux scripts en MATLAB qui permettent de faire beaucoup plus rapidement ce que j'ai appris à faire avec une combinaison de méthodes graphiques et de calculs de trigonométrie plane. Ils sont disponibles dans la partie « Resources » du site du livre, mais il faut l'acheter pour les remettre dans leurs contextes. Ces scripts sont très facilement transformables en scripts Python avec l'avantage de ne pas devoir payer la licence de MATLAB... (il faut aussi signaler que la plupart des scripts fonctionnent sur les clones libres de MATLAB que sont Octave (www.gnu.org/software/octave/) ou Freemat (freemat.sourceforge.net/), hormis quelques fonctions graphiques ou spécialisées)

Il est aussi possible de sélectionner les valeurs à partir d'un fichier vectoriel et d'utiliser d'autres scripts comme ceux de github.com/joferkington/mplstereonet ou de yonggeng.wordpress.com/2010/11/16/rose-diagram-code/ (entre autres) qui utilisent matplotlib.

et de les utiliser directement (ici dans la console Python de QGIS) à partir des valeurs sélectionnées (en rouge):


Tous les traitements ont été faits sur Mac OS X et les figures créées avec matplotlib et visvis hormis les captures d'écran (QGIS, GRASS GIS et OpenLayers).

Site officiel :module osgeo (gdal, ogr)
Site officiel :module Shapely
Site officiel :module numpy
Site officiel :module matplotlib
Site officiel :modile visvis
Autres Liens :3-D Structural Geology
Autres Liens :Structural Geology Algorithms, Vectors and Tensors
Autres Liens :Grass Gis et R: superposition des couleurs d'un raster quelconque sur un profil topographique élaboré à partir d'un MNT
Autres Liens :Python: géoréférencement et worldfiles (tfw, jgw,pngw,...)


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France

Python: traitement des couches vectorielles dans une perspective géologique, lecture et enregistrement des couches sous forme de dictionnaires avec le module Fiona

$
0
0
Niveau Intermédiaire
Logiciels utilisésPython
Plateforme Windows | Mac | Linux | FreeBSD

Lorsque j'essaie d'expliquer à mes collègues que Python est simple pour traiter des données géospatiales, ils me regardent d'un drôle d'œil, même s'ils connaissent le langage pour leurs traitements géologiques. Il est vrai que travailler avec des modules comme osgeo.ogr, PyQGIS ou ArcPy n'est pas une sinécure avec des while provider.nextFeature(feat), des feat.GetGeometryRef() ou des arcpy.UpdateCursor(points) et autres joyeusetés qui obligent à avoir en permanence  un aide-mémoire à côté de soi. Cela vient du fait que ces modules veulent tout faire en même temps, lire et écrire les données et les traiter, sans parler de la création des interfaces comme dans les plugins de Quantum GIS, le tout dans un joyeux mélange.... Seul Pyshpéchappe en partie à cette règle, mais demande toujours de jongler entre les géométries et les tables d'attributs. De plus, le module  rencontre de sérieux problèmes si, par mégarde, un des champs renseignés est vide (aucune donnée).

Longtemps j'ai rêvé que les traitements de données spatiales en Python soient aussi simples que pour les autres types de données. Sauvegarder sous forme de shapefile les résultats d'un traitement de données géologiques (avec des angles...) m'a souvent demandé plus de travail  que le traitement lui-même....

Pourtant, il existe une solution simple qui permet de manipuler ces données avec des structures classiques comme des dictionnaires ou des listes. Si comme moi, vous avez des scripts tout prêts et que vous voulez les utiliser tels quels, sans devoir tout modifier pour créer des résultats sous forme de fichier shapefile, par exemple, alors la solution est le module Fionade Sean Gillies.  Son seul but est simplement de lire et d'écrire les données à traiter dans un format géospatial, rien de plus, rien de moins.

Puisqu'un petit exemple vaut mieux qu'un beau discours :

import fionacouche = fiona.open('testligne.shp')rec = couche.next()print rec

dont le résultat est:

{'geometry': {'type': 'LineString', 'coordinates': [(269884.20917418826, 151805.1917153612), (270409.89083992655, 153146.21637285672), (272298.05355768028, 154047.38494269375), (272941.74539327814, 155484.96337552898), (272169.31519056071, 156117.92701386689)]}, 'id': '1',  'properties': {'faille': u'de Salinas', 'type': u'normale'}}

et on reconnait un simple dictionnaire Python dans lequel tous les éléments d'un objet spatial (géométrie et attributs) sont représentés. Plus de notions de couches et d'attributs, pas decurseurs, plus d'opérationsgéométriques ni detransformations entresystèmes de coordonnéesà gérer, avec Fiona, lire et écrire des données spatiales se résument à lire ou à placer des éléments dans un dictionnaire Python. Toutes les autres opérations sont laissées à d'autres modules Python comme Shapely,  Pyproj, Numpy, Scipy ou Math. En pratique, Fiona est particulièrement adapté à Shapely, auteur oblige...

Tout cela est rendu possible par l'utilisation du format GeoJSON car Fiona n'est en fait qu'un habillage du module ogr et ici, les utilisateurs de Windows n'ont plus d'excuse, car le module prêt à l'emploi est disponible chez Christoph Gohlke (www.lfd.uci.edu/~gohlke/pythonlibs/#fiona).

J'aborderai l'utilisation de Fiona avec des exemples d'applications géologiques:

  • Principes de Fiona, lire et écrire des fichiers;
  • Premier exemple: les azimuts ou directions d'objets géologiques linéaires;
  • Deuxième exemple: les intersections entre une ligne de coupe et les directions des couches géologiques;
  • Troisième exemple: les transformations affines, translations, rotations, mises à l'échelle et cisaillements.

Principes de Fiona

Fiona travaille avec des itérateurs et des générateurs. Comprendre comment ceux-ci fonctionnent est très important si l'on veut utiliser Python de manière productive. Il y a une multitude d'explications disponibles sur Internet et je ne vais donc pas en proposer une énième fois. Néanmoins, en simplifiant:

  • un itérateur permet de parcourir une liste d'objet en implémentant la fonction next() (ce qui a été fait dans couche.next()) qui va retourner la prochaine valeur (et l'exception StopIteration quand il n'y a plus d'élément). Vous avez déjà tous utilisé les itérateurs lorsque vous parcourez un objet « itérable » comme dans le cas des boucles for élément in sequence (voir sametmax.com/dis-papa-dis-papa-dis-moi-dis-moi-comment-cest-fait-dans-une-boucle-for/).
  • un générateur est une fonction qui va retourner les valeurs sous forme d'itérateur à l'aide de la fonction yield() (qui est elle même un itérateur, disposant de la méthode next()). Les compréhensions de listes sont en pratique des générateurs (voir sametmax.com/comment-utiliser-yield-et-les-generateurs-en-python/)

L'intérêt des générateurs est multiple, en effet les valeurs ne sont pas conservées en mémoire. Une fois qu'une valeur a été générée et envoyée, elle est perdue, ce qui facilite les traitements de grosses masses de données.

Mais ne vous inquiétez pas, tout cela est transparent dans Fiona !

Lecture des données

pour bien comprendre comment fonctionne Fiona: parcours du résultat de manière brute avec la fonction next():

Je vais ici parcourir l'itérateur renvoyé par Fiona avec la fonction next() pour bien saisir le module: la fonction  va renvoyer les divers élements de la couche shapefile sans les conserver en mémoire:

 # au niveau de la couche (type de géométrie + définition des champs)print couche.schema{'geometry': 'LineString', 'properties': {u'faille': 'str:20', u'type': 'str:20', u'id': 'int'}}print couche.crs# (crs au format Proj4){'lon_0': 4.3674866666666663, 'ellps': 'intl', 'y_0': 5400088.4380000001, 'no_defs': True, 'proj': 'lcc', 'x_0': 150000.01300000001, 'units': 'm', 'lat_2': 49.8333339, 'lat_1': 51.166667233333328, 'lat_0': 90} print couche.bounds# emprise de la couche
(269884.20917418826, 151805.1917153612, 275291.22059321037, 156117.92701386689)
 # au niveau de l'entrée (valeurs)print rec['geometry']{'type': 'LineString', 'coordinates': [(269884.20917418826, 151805.1917153612), (270409.89083992655, 153146.21637285672), (272298.05355768028, 154047.38494269375), (272941.74539327814, 155484.96337552898), (272169.31519056071, 156117.92701386689)]}print rec['geometry']['coordinates'][(269884.20917418826, 151805.1917153612), (270409.89083992655, 153146.21637285672), (272298.05355768028, 154047.38494269375), (272941.74539327814, 155484.96337552898), (272169.31519056071, 156117.92701386689)] # transformation directe en géométrie Shapelyfrom shapely.geometryimport shapegeom = shape(rec['geometry'])print geomLINESTRING (269884.2091741882613860151805.1917153612012044, 270409.8908399265492335153146.2163728567247745, 272298.0535576802794822154047.3849426937522367, 272941.7453932781354524155484.9633755289833061, 272169.3151905607082881156117.9270138668944128)
python># valeur d'un attribut
 print rec['properties']['faille']u'de Salinas'# entrée suivanterec= couche.next()print rec{'geometry': {'type': 'LineString', 'coordinates': [(270892.65971662494, 151805.1917153612), (272416.06372753991, 152856.55504683769), (274003.8369220146, 153478.79048791563), (274819.17991377192, 154283.40528241298), (275291.22059321037, 154648.16398925177)]}, 'id': '1', 'properties': {'faille': u'Arnao', 'type': u'chevauchement', 'id': None}} etc.

Si je continue indéfiniment avec couche.next(), cela va générer une exception lorsqu'il n'y aura plus d'éléments à rendre.

avec un générateur

Pour cela, je pourrai créer ma propre fonction:

def iterateur(c):    ligne =  c.next()    while ligne:       yield ligne       ligne = c.next() # traitementfor ligne in iterateur(couche):     print ligne

Elle ne permet toujours que de lire les éléments, sans soulever l'exception StopIteration mais si je veux les conserver je dois utiliser une compréhension de liste. MaiscommeFiona fournit son propre générateur nommé collection, je continue avec celui-ci:

iterateur  = fiona.collection('testligne.shp')# sans conservation en mémoire des élémentsfor i in iterateur:print i{'geometry': {'type': 'LineString', 'coordinates': [(269884.20917418826, 151805.1917153612), (270409.89083992655, 153146.21637285672), (272298.05355768028, 154047.38494269375), (272941.74539327814, 155484.96337552898), (272169.31519056071, 156117.92701386689)]}, 'id': '0', 'properties': {'faille': u'de Salinas', 'type': u'normale', 'id': None}}{'geometry': {'type': 'LineString', 'coordinates': [(270892.65971662494, 151805.1917153612), (272416.06372753991, 152856.55504683769), (274003.8369220146, 153478.79048791563), (274819.17991377192, 154283.40528241298), (275291.22059321037, 154648.16398925177)]}, 'id': '1', 'properties': {'faille': u'd' Arnao', 'type': u'chevauchement', 'id': None}}# avec conservation en mémoire des éléments dans une liste (par compréhension de liste)iterateur  = fiona.collection('testligne.shp')data = [elem for elem in iterateur]print data[{'geometry': {'type': 'LineString', 'coordinates': [(269884.20917418826, 151805.1917153612), (270409.89083992655, 153146.21637285672), (272298.05355768028, 154047.38494269375), (272941.74539327814, 155484.96337552898), (272169.31519056071, 156117.92701386689)]}, 'id': '0', 'properties': {'faille': u'de Salinas', 'type': u'normale', 'id': None}},{'geometry': {'type': 'LineString', 'coordinates': [(270892.65971662494, 151805.1917153612), (272416.06372753991, 152856.55504683769), (274003.8369220146, 153478.79048791563), (274819.17991377192, 154283.40528241298), (275291.22059321037, 154648.16398925177)]}, 'id': '1', 'properties': {'faille': u'd'Arnao', 'type': u'chevauchement', 'id': None}}]

Écriture

L'écriture est tout aussi facile, il suffit de créer un dictionnaire de le remplir:

from shapely.geometryimport LineStringimport fiona# deux géométries Shapelylignes = [LineString([(272830.63,155125.73),(273770.32,155467.75)]),LineString([(273536.47,155914.07),(272033.12,152265.71)])]# définition du schéma général de la couche avec un attribut # ou création du dictionnaireschema = {'geometry': 'LineString','properties': {'titi': 'int'}}# écriture dans un nouveau shapefilewith fiona.collection('monshp.shp', 'w', 'ESRI Shapefile', schema)as couche:    for ligne in lignes:        # remplissage du dictionnaire schema        elem = {}        # utilisation de la fonction mapping de Shapely        elem['geometry'] = mapping(ligne)        # valeur de l'attribut (un seul ici)         elem['properties'] = {'titi': 145}        # écriture de l'élément dans le fichier        couche.write(elem)

Le module ogr sur lequel se base Fiona a de gros problèmes lorsqu'il s'agit de modifier une couche, il vaut mieux alors en faire une copie. Avec Fiona, c'est un jeu d'enfant:

from shapely.geometryimport mapping, shapeimport fiona# Lecture du fichier shapefile originelwith fiona.collection('monshp.shp', 'r')as entree:     # le fichier de sortie a le même schema -> copie du dictionnaire     schema = entree.schema.copy()     # écriture du nouveau fichier shapefile     with fiona.collection('monshp_copie.shp', 'w', 'ESRI Shapefile', schema)as sortie:         for elem in entree:             sortie.write({'properties': elem['properties'],'geometry': mapping(shape(elem['geometry']))})

Cela nous sera très utile dans la suite.

Premier exemple: les azimuts ou directions d'objets géologiques linéaires

Trouver les azimuts ou directions d'objets géologiques linéaires est une tâche courante en géologie pour les traitements postérieurs. Or si calculer la direction d'une ligne simple (A) est facile,  ce n'est pas le cas du calcul des azimuts des segments de lignes plus complexes (B):

La démarche est alors la suivante:

  1. lire le fichier shapefile originel
  2. récupérer les segments de ligne et calculer leurs azimuts
  3. les insérer dans un nouveau champ d'un nouveau fichier shapefile contenant aussi les attributs de l'original

Puisque je dispose déjà d'une fonction permettant de calculer l'azimut d'une ligne à partir de deux points (géométrie Shapely) en fonction de l'axe N-S, je vais la réutiliser (je travaille dans un système de coordonnées cartésiennes et je veux toutes mes valeurs entre 0 et 180°):

def azimut(point1, point2):       '''Retourne l'azimuth de la ligne entre 2 points shapely'''       angle = math.atan2(point2.x - point1.x, point2.y - point1.y)       returnmath.degrees(angle)if angle>0 elsemath.degrees(angle) + 180

Je dispose aussi d'un générateur qui me permet de traiter les éléments dans une liste par paires (tiré de stackoverflow.com/questions/1257413/iterate-over-pairs-in-a-list-circular-fashion-in-python)

def paires(liste):     '''parcourt une liste par paires'''     for i inrange(1, len(liste)):         yield liste[i-1], liste[i]

je vais l'utiliser pour extraire les segments de ligne: 

with fiona.collection('testligne.shp', 'r')as entree:    # copie du schema de la couche et création d'un nouveau champ 'azimut'    schema = entree.schema.copy()    schema['properties']['azimut'] = 'int'    # création d' une nouvelle couche avec le schéma résultant    with fiona.collection('testligne_azim.shp', 'w', 'ESRI Shapefile', schema)as sortie:        for ligne in entree:        # utilisation de la fonction paire() pour extraire les segments de lignes            for seg_start, seg_end in paires(ligne['geometry']['coordinates']):                # création d'une ligne en fonction des points des segments                line_start = Point(seg_start)                line_end = Point(seg_end)                segment = LineString([line_start.coords[0],line_end.coords[0]])                # copie des attributs d'entrée et ajout de la valeur résultante                # de la fonction azimut()                elem = {}                elem['properties'] = ligne['properties']                elem['properties']['azimut'] = azimut(line_start, line_end)                elem['geometry'] = mapping(segment)                sortie.write(elem)

et le résultat est:

 

Si l'on veut rajouter la longueur de chaque segment, il suffit d'insérer le résultat de la fonction suivante dans une nouvelle clé du dictionnaire:

def distance_euclid2pt(p1, p2): '''distance euclidienne entre 2 points shapely'''    vect_x = p2.x - p1.x    vect_y = p2.y - p1.y    returnmath.sqr(vect_x**2 + vect_y**2))

ou, plus simplement avec la fonction length de Shapely.

 return LineString([p1.coords[0],p2.coords[0]]).length

 

Deuxième exemple: les intersections entre une ligne de coupe et les directions des couches géologiques

Je vais vous montrer ici comment résoudre une partie de la procédure de « Comment ajouter des valeurs structurales de stratification (pendage et direction) en calculant le pendage apparent sur la ligne de coupe » dans Python: utilisation des couches vectorielles et matricielles dans une perspective géologique, sans logiciel SIG sans passer par des couches intermédiaires (lignes en pointillés noirs) et en utilisant une fonction que j'avais déjà, sans la modifier:

def ptor2ptfin(p, angle, dist):    ''' calcul de la position du point situé à une distance dist du point p (Shapely) dans la direction angle'''    dist_x, dist_y = (dist * sin(radians(angle)),dist * cos(radians(angle)))    xfinal, yfinal = (p.x + dist_x, p.y + dist_y)    return Point(xfinal,yfinal)   with fiona.collection('tesligne_coupe.shp', 'r')as coupe:    # comme il n'y a qu'une seule ligne de coupe, j'utilise next()    geomcp = shape(coupe.next()['geometry'])    # ouverture de la couche avec des mesures structurales    with fiona.collection('struct_point.shp', 'r')asstruct:
        schema = struct.schema.copy()        # création directe des points d'intersection entre        # la ligne de coupe et les directions structurales        with fiona.collection('pt_intersect.shp', 'w', 'ESRI Shapefile', schema)as sortie:            for pt instruct:                geompt = shape(pt['geometry'])                angle = pt['properties']['pend_orienté']                # calcul du point situé à une distance de 1000 m                # suivant la direction indiquée par le point (pendage orienté - 90)                geomptres = ptor2ptfin(geompt, angle-90, 1000)                # création de la ligne virtuelle reliant les 2 points                ligne_virt = LineString([ geomptres.coords[0],ptres.coords[0]])                # point d'intersection entre cette ligne virtuelle et la ligne de coupe                pt_inters = ligne_virt.intersection(geomcp)                ptres = {}                # copie des attributs des points d'origine                ptres['properties'] = pt['properties']                ptres['geometry'] = mapping(pt_inters)                sortie.write(ptres)

Obtenir le pendage apparent sur la ligne de coupe ne consiste ensuite qu'à ajouter le résultat du calcul d'une fonction dans le dictionnaire.

Troisième exemple: les transformations affines, translations, rotations, mises à l'échelle et cisaillements

Le module Shapely dispose depuis sa dernière version de tous les éléments permettant d'effectuer les transformations affines en 2D ou en 3D (github.com/Toblerity/Shapely/blob/master/shapely/affinity.py). Cela permet toutes les combinaisons possibles avec les objets Shapely, même en utilisant des matrices de transformation. Pour vous donnez une idée de ce qu'il est possible de faire, je vous conseille d'examiner le script github.com/Toblerity/Shapely/blob/master/shapely/tests/test_affinity.py. Les algorithmes utilisent les principes déjà exposés dans Les transformations affines (avec NumPy) ou la signification géométrique d'un fichier worldfile (.tfw, .jgw,...) pour le géoréférencement 

Combiné avec Fiona, cela devient un vrai plaisir pour un géologue.

exemple de rotation d'un fichier shapefile d'un angle de 35°:

with fiona.collection('testligne.shp', 'r')as entree:    schema = entree.schema.copy()    with fiona.collection('rotation35.shp', 'w', 'ESRI Shapefile', schema)as sortie:       for ligne in entree:          geom = shape(ligne['geometry'])          rotation = affinity.rotate(geom, 35.0)          sortie.write({'properties': ligne['properties'],'geometry': mapping(rotation)})

Résultat:

 

mais il est aussi possible d'utiliser de vraies matrices de transformations affines 2D et 3D:

matrix2d = (0.422, 0.022,            0.052,-0.998,            0.905, 0.0474)with fiona.collection('testligne.shp', 'r')as entree:    schema = entree.schema.copy()    with fiona.collection('matrix.shp', 'w', 'ESRI Shapefile', schema)as sortie:        for ligne in entree:            geom = shape(ligne['geometry'])            aff = affinity.affine_transform(geom, matrix2d)            sortie.write({'properties': ligne['properties'],'geometry': mapping(aff)})

Résultat:

 

Je peux donc transformer le « down-plunge profile » présenté dans Python: utilisation des couches vectorielles et matricielles dans une perspective géologique, sans logiciel SIG en fichier shapefile:

Il y a donc, théoriquement, moyen de « géoréférencer » un fichier shapefile par transformation affine.

Conclusions

Sean Gillies dans son tutoriel de Fiona (toblerity.github.com/fiona/manual.html) souligne ses avantages et ses désavantages:

In what cases would you benefit from using Fiona?

  • If the features of interest are from or destined for a file in a non-text format like ESRI Shapefiles, Mapinfo TAB files, etc.
  • If you’re more interested in the values of many feature properties than in a single property’s value.
  • If you’re more interested in all the coordinate values of a feature’s geometry than in a single value.
  • If your processing system is distributed or not contained to a single process.

In what cases would you not benefit from using Fiona?

  • If your data is in or destined for a JSON document you should use Python’s json or simplejson modules.
  • If your data is in a RDBMS like PostGIS, use a Python DB package or ORM like SQLAlchemy or GeoAlchemy. Maybe you’re using GeoDjango already. If so, carry on.
  • If your data is served via HTTP from CouchDB or CartoDB, etc, use an HTTP package (httplib2, Requests, etc) or the provider’s Python API.
  • If you can use ogr2ogr, do so.

Mais tel quel, il me permet d'utiliser tous mes scripts existants, sans modification et de sauver simplement tous les résultats sans devoir me rappeler toutes les fioritures de ogr ou de PyQGIS. Transformer un fichier de données en fichier shapefile ne demande que quelques lignes de code... Pour voir toutes les autres possibilités de Fiona qui n'ont pas été abordées ici, il suffit de chercher sur le blog de Sean Gillies (sgillies.net/blog).

Ah, et les projections diront certains, il n'y a pas moyen d'utiliser les systèmes de projection, c'est donc rédhibitoire...  Et non, car ce n'est pas le but de Fiona, comme déjà souligné, mais comme il laisse à d'autre modules le soin de faire le travail (pyproj ici), d'une manière tout aussi simple:  sgillies.net/blog/1112/coordinate-reference-systems-for-fiona/sgillies.net/blog/1125/fiona-pyproj-shapely-in-a-functional-style/ ou github.com/sgillies/Fiona/blob/master/examples/with-pyproj.py.

Tous les traitements ont été faits sur Mac OS X avec la version 0.9.1 de Fiona.

Site officiel :Fiona
Site officiel :pyproj
Autres Liens : Python: utilisation des couches vectorielles et matricielles dans une perspective géologique, sans logiciel SIG
Autres Liens :Les transformations affines (avec NumPy) ou la signification géométrique d'un fichier worldfile (.tfw, .jgw,...) pour le géoréférencement


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France


Comment trouver les zones qui correspondent à des critères d'altitudes, de pentes et d'orientations ?

$
0
0
Niveau Débutant
Logiciels utilisésQGIS
Plateforme Windows | Mac | Linux

Voici une question posée (un peu différement) de XxpavelxX sur le forumsig : http://www.forumsig.org/showthread.php/36322-Suppression-de-micro-polygones

En réalité la question initiale n'était pas celle là, mais la problématique correspond à cela.

Souvent pour répondre à ce genre de questions, une solution est de travailler sur des vecteurs, comme évoquée dans la discussion. Cette solution technique est un peu lourde, car elle nécessite de vectoriser plusieurs rasters, de faire des sélections, des intersections, et on se retrouve souvent avec des micro polygones (correspondant à un pixel) qu'il faut tenter de regrouper, etc. Bref, vous l'avez compris pour moi cette solution n'est pas idéale.

Il existe une autre méthode déjà évoquée sur le portailsig, notamment par Martin Laloux dans "GRASS GIS pas à pas pour les débutants: 6 - les masques (MASK) et leurs utilisations, rapport avec les régions et l'algèbre des cartes" ou encore "GRASS GIS : introduction à l'algèbre des cartes (map algebra, r.mapcalc (et r3.mapcalc))", l'algèbre de cartes.

Dans ce tutoriel, je vais essayer de vous montrer comment répondre à la question suivante avec cette méthode et donc avec du traitement raster.

Question :

Trouver les zones répondant aux critères suivants :

  • Altitudes inférieures à 1200 mètres,
  • Pentes inférieures à 30 %, (critère adapté par rapport à la question initiale pour que cela fonctionne avec mon jeu de données)
  • Expositions de nord ouest à nord est.

Nous allons travailler sur la bd alti au pas de 250 mètres de l'Ile de la Réunion (un peu d'exotisme ne peut pas faire de mal ...) que l'on peut trouver gratuitement en téléchargement sur le site de l'IGNhttp://professionnels.ign.fr/sites/default/files/Reunion_MNT250_ASC.zip

Nous utiliserons QGIS, mais la plupart des logiciels SIG permettant de traiter les rasters feraient l'affaire.

Etape 1 - Produire les rasters nécessaires :

Nous avons besoin de 3 rasters, le MNT (nous venons de le télécharger), un raster de pentes et un raster d'orientations.

QGIS possède une extension native, "Analyse Raster de terrain" permettant de faire cela, il suffit de l'activer dans le gestionnaire d'extensions, pour qu'un sous menu "Analyse de terrain" apparaisse dans le menu "Raster". On trouvera quelques informations sur cette extension dans le manuel de QGIS http://download.osgeo.org/qgis/doc/manual/qgis-1.7.2_user_guide_fr.pdf , page 191. On notera notamment que la pente est données en degrès et que l'orientation est donnée comme suit :

"Aspect : Calcule l’exposition (en degrés dans le sens horaire inverse et en commençant par 0 pour une direction nord)"

Pour la pente :

Raster / Analyse de terrain / Pente :

Pour les orientations :

Même principe, Raster / Analyse de terrain / Aspect :

Nous avons tous les éléments nécessaires pour commencer nos traitements :

Etape 2 - Extraire de chaque raster les zones répondant aux critères

Pour extraire les zones qui nous intéressent nous allons utiliser la calculatrice raster. Dans QGIS, on la trouve dans Raster / Calculatrice raster. Cette calculatrice nous permet de faire des opérations sur des rasters et cela s'avère très puissant, ici nous ne verrons que des opérations simples.

Le principe, on donne une couche de résultat, l'emprise que l'on souhaite utiliser pour le traitement, puis dans la partie Expression, il faut remplir la formule à utiliser.

Ici nous cherchons à obtenir un masque, c'est à dire que pour chaque raster, nous souhaitons obtenir un nouveau raster composé de 0 et de 1, les pixels de valeur 1 correspondront à une zone qui valident le critère recherché.

Ainsi pour l'altitude, nous cherchons les altitudes inférieures à 1200 mètres, la formule sera :

DEPT974@1 < 1200

ici DEPT974 est notre MNT, @1 spécifie sur quelle bande portera l'opération, ici nos rasters ne sont composés que d'une seule bande.

 

Pour la pente :

Pente974@1 < 30

Pour les orientations, on considère que les valeurs recherchées, vont de 0 à 30 ° et de 330 à 360 °:

Orientation974@1 > 330 OR Orientation974@1 < 30

Nous nous retrouvons avec 3 masques, un pour les altitudes, un pour les pentes et un pour les orientations correpondants aux critères précisés dans la question.

Afin de pouvoir obtenir les zones qui correspondent à ces trois critères cumulés, nous allons multiplier ces 3 masques, en effet, lorsque 0 d'un des masques sera multiplié avec un 0 ou un 1, on obtiendra toujours un 0 et donc la zone sera exclue :

altinInf1200@1 * orienationNO-NE@1 * penteInf30@1

XX

=

Nous obtenons finalement un raster composé de 0 et de 1. Les pixels de valeur 1 (ici en blanc) représentent les zones qui valident tous les critères évoqués et on répond donc à la question initiale. Vous verrez que ces opérations sont très rapides en réalité.

Bien sur ici, l'exemple est assez simple, mais l'algèbre de cartes est très puissant, il permet énormément de choses, on pourrait par exemple reclasser un raster, utiliser des opérateurs plus complexes, calculer des indices (par exemple l'indice NDVI) etc.

J'espère que ce petit tutoriel, vous permettra d'appréhender simplement l'algèbre de cartes et d'en comprendre son intérêt.

Site officiel :QGIS
Autres Liens :GRASS GIS pas à pas pour les débutants: 6 - les masques (MASK) et leurs utilisations, rapport avec les régions et l'algèbre des cartes
Autres Liens :GRASS GIS : introduction à l'algèbre des cartes (map algebra, r.mapcalc (et r3.mapcalc))


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

Plugins QGIS - le format WKT ou la géométrie amusante avec QuickWKT et Plain Geometry Editor

$
0
0
Niveau Débutant
Logiciels utilisésQGIS
Plateforme Windows | Mac | Linux | FreeBSD

Je suppose que vous connaissez tous les formats WKT ou Well-Known-Text et son équivalent en binaire, le WKB ou Well-Known Binary, définis par l’Open Geospatial Consortium (OGC), sinon un petit rappel sur Wikipedia: Well-known text.

L'intérêt de ces formats  est qu'ils sont  reconnus par:

  • tous les logiciels SIG;
  • toutes les bases de données géospatiales;
  • toutes les librairies géospatiales, quelque soit le langage de programmation.

Ces formats constituent  donc un formidable outil d'échange entre tous les éléments cités mais ils sont rarement utilisés pour entrer ou modifier les géométries dans des logiciels SIG.

C'est le but de ces deux plugins:

  • QuickWKT permet d'entrer les géométries aux formats WKT et WKB et de les visualiser dans QGIS;
  • Plain Geometry Editor  permet d'éditer les géométries existantes au format WKT.

Mais avant de les analyser, une petite présentation de ce qu'il est possible de faire dans QGIS avec ces formats s'impose, car leurs possibilités sont sous-estimées dans la création des couches.

Utilisation du format WKT pour créer des couches dans QGIS

Il est déjà possible d'importer des géométries au format WKT avec le menu « Ajouter une couche de texte délimité» et l'option Champ WKT mais il y a moyen d'aller beaucoup plus loin dans la Console Python avec un simple script qui crée une couche mémoire (memory layer) à partir de n'importe quelle chaîne au format WKT, ici sans attributs et sans projection (CRS). Pour traiter un ensemble de couches ou une couche MULTIxxx, ils suffit de créer une boucle.

# fonction de création d'une couche temporaire QGISdef couche_wkt(wkt,nom):    geom = QgsGeometry.fromWkt(wkt)    type = typeMap[geom.type()]    # puisque le type de géométrie est déjà signale dans le format    couche = QgsVectorLayer(type, nom,"memory")    pr = couche.dataProvider()    elem= QgsFeature()    elem.setGeometry(geom)    pr.addFeatures([ elem ])    couche.updateExtents()    QgsMapLayerRegistry.instance().addMapLayers([couche])     # type de couchetypeMap = {0: "Point", 1: "LineString", 2: "Polygon"}# polygone au format WKTtest = 'POLYGON((30 10,30 40,50 40,50 10,30 10))'# création de la couche temporairecouche_wkt(test,'polygone')

 et le résultat est est une belle couche avec le polygone.:

Comme souligné, le format WKT est un formidable outil d'échange et je vais l'illustrer avec l'utilisation du module Python Shapely qui permet, dans sa dernière version, d'effectuer les rotations, les translations ou les cisaillements en 2D et en 3D. Il permet aussi d'utiliser le format binaire WKB, plus rapide.

from shapely import affinityfrom shapely.wktimport loads# transformation en polygone shapelytest90 = loads(test)# rotation du polygone de 90°rot = affinity.rotate(test90, 90)couche_wkt(rot.wkt,'polygone90')

résultat:

En conclusion, tout peut être fait avec cette méthode, même les devoirs de mes fils ... (avec l'exemple de traitement mathématique de Pascal Leroux dans: points intersection courbes et présenté dans Python: le module Shapely, géométries, prédicats spatiaux, analyse spatiale, matrices de Clementini (DE-9IM), traitements de shapefiles ou autres, et représentation avec matplotlib.

QuickWKT

Ce plugin permet d'effectuer cette démarche sans programmation et de manière plus complète que ce que j'ai présenté dans mon script. Il gère aussi bien les formats WKT, que les formats (E)WKT (avec projection) et WKB. Une fois installé il se trouve dans le menu Extension.

Une fois lancé, il propose d'insérer quelques types de géométries à titre d'exemple (la dernière version accepte aussi les géométries de type MULTIxxx, et GEOMETRYCOLLECTION.)

Et il suffit de coller notre polygone dans le cadre pour obtenir le résultat:

Il y a moyen d'entrer plusieurs types de géométries à la suite l'une de l'autre dans le cadre:

  • une couche mémoire différente sera créée par géométrie (n géométries = n couches)
  • ou une seule couche par type de géométrie en cochant le bouton Create new layer for every geometry type. (n types de géométries = n couches)

Plain Geometry Editor

Ce plugin effectue la démarche inverse, c'est-à-dire le traitement de toutes les géométries existantes au format WKT. Une fois installé il se trouve aussi dans le menu Extension.

  • la couche doit être en mode Édition
  • une fois le plugin activé, il se manifeste par une croix qu'il suffit de placer sur l'objet édité;
  • une fenêtre s'ouvre alors avec la géométrie de l'objet au format WKT:

Ce qui fait la force du plugin est qu'il permet de modifier la géométrie en direct en changeant les valeurs dans le cadre:

Conclusions

À vous de jouer car une fois maîtrisés, ces plugins qui, apparemment ne paient pas de mine, se révèlent redoutables, si vous ne voulez pas programmer, et très amusants à utiliser.

Pour moi qui programme en Python, QuickWKT est aussi devenu indispensable car il me permet de vérifier très rapidement les résultats obtenus (un simple copier-coller au lieu d'utiliser le module Python matplotlib, par exemple)

  • QuickWKT fonctionne sur les versions 1.8 et master de QGIS, utilisée ici;
  • Plain Geometry Editor n'a pas encore été adapté au nouvel API Python de la version master et ne fonctionne que sur la version 1.8, utilisée ici.

Tous les traitements ont été  effectués sur Mac OS  X. 

Site officiel :Python: le module Shapely, géométries, prédicats spatiaux, analyse spatiale, matrices de Clementini (DE-9IM), traitements de shapefiles ou autres, et représentation avec matplotlib


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France

Plugin QGIS - Group Stats

$
0
0

Quand on a l'habitude d'utiliser ArcGIS, il y a une fonctionnalité intéressante nommée "récapituler". Celle ci permet d'obtenir des statistiques sur un champ d'une table attributaire.

Cette fonctionnalité n'existe pas de base sur QGIS, mais le plugin GroupStats le permet (et même plus encore).

Une question récente sur le forumsig de mimichaib dont le sujet était :

Je possède une couche avec des linéaires de haies sur plusieurs années et je souhaiterai savoir faire des pourcentages sur ces linéaires. Combien de pourcents en moins (ou en plus) d'une année sur l'autre, combien de nouvelles haies plantées, de disparues non remplacées ?

permet d'illustrer l'utilisation du plugin Group Stats.

Si l'on part d'une couche linéaire (schématiquement représentée ici, une couleur par année) correspondant à plusieurs années de recensement et dont les parties linéaires communes se superposent parfaitement.

Il est possible d'intersecter chaque ligne avec l'outil d'intersection dans vecteur / outil de geotraitement / intersection.

Une fois que nos lignes sont découpées en tronçons individualisés, le plugin Group Stats nous permet aisément de calculer la somme des longueurs des différents tronçons en fonction de l'année de recensement.

De ces données, le calcul de pourcentage sera très simple dans n'importe quel tableur.

Bien entendu il est possible de demander d'autres calculs statistiques que la somme, vous pouvez calculer une moyenne, le décompte d'entités, la valeur maximum, la médiane, la valeur minimum, l'écart type et la variance.

Il est également possible d'obtenir d'autres informations, à l'instar d'un tableau croisé dynamique. Imaginons que l'on utilise la bd geofla de l'ign des communes de Rhone Alpes :

Les communes sont représentées en fonction de leur statut, c'est à dire :

  • Commune simple
  • Chef lieu de canton
  • Sous-préfecture
  • Préfecture
  • Préfecture de région

Le plugin Group Stats peut nous permettre de calculer la moyenne de la population de chaque type de statut ci dessus, en fonction de leur département d'appartenance :

Attention la population est donnée en millier d'habitants.

Il est possible ensuite de copier coller ces résultats ou encore de les exporter en csv via le menu data.

Ce plugin n'est pas forcément très simple d'utilisation au premier abord, mais il est particulièrement intéressant et j'espère qu'il vous sera utile.


Site officiel :Group Stats
Site officiel :Tutoriel d'Anita Graser


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

GRASS GIS pas à pas pour les débutants: 12 - les tables attributaires (DBF, SQLite, PostGIS, MySQL, ODBC)

$
0
0

Pour le Sigiste lambda qui aborde GRASS GIS, le moins que l'on puisse dire est que la gestion des tables attributaires paraît encore assez éloignée du confort des logiciels SIG auxquels il est habitué (jusqu'il y a peu, elles ne se manipulaient qu'en ligne de commande ou avec de multiples fenêtres graphiques...). Mais la version 6.4 a amené une interface relativement plus classique avec l'utilisation de wxPython en lieu et place de Tcl/Tk et la future version 7 a une interface plus aboutie. À l'inverse, passer d'un système de base de données à un autre est d'une facilité déconcertante (DBF vers SQLite, PostgreSQL, etc.) grâce au DBMI(DataBase Management Interface), utilisé par GRASS GIS).


Ces tables permettent d'obtenir toutes les fonctionnalités d'un SIG:

Le sujet sera abordé de la manière suivante:

  • Principes: le DBMI et les manières de s'en passer avec les liens vers des données externes (v.external);
  • Les divers types de bases de données qui peuvent être utilisés;
    • caractéristiques dans GRASS GIS;
    • les commandes disponibles.
  • Les interfaces graphiques;
  • En ligne de commande pour aller plus loin;
    • définition de la base par défaut d'un secteur (LOCATION) ou d'un jeu de données (MAPSET) en lieu et place des fichiers dbf;
    • connexion d'une couche vectorielle à une table attributaire spécifique;
    • changement de SGBD, migration des tables attributaires;
    • traitements (requêtes, etc.).
  • Changements avec la version 7 de GRASS GIS
    • conversion des couches vectorielles au format de la version 6 vers celui de la version 7;
    • conversion des couches vectorielles au format de la version 7 vers celui de la version 6.
  • Conclusions

Principes

Le format de stockage actuel par défaut de ces tables attributaires est le DBF (reliquat du passé, ce n'est plus le cas avec la future version 7 où c'est SQLite).

GRASS GIS peut aussi utiliser d'autres bases de données, plus puissantes. Quel que soit le format, les traitements restent les mêmes, car toutes les procédures ont été formalisées dans une couche d'abstraction uniquenomméeDBMI(DataBase Management Interface).

Le schéma suivant constitue une synthèse de toutes les possibilités:

Il peut paraître un peu compliqué de prime abord, mais nous allons en détailler quelques principes dans la suite.
Mais avant de les examiner,  remarquons tout de suite qu'il y a moyen de s'en passer si l'on veux simplement examiner une couche, sans l'importer. C'est le but de
la commande v.external (qui va vous expliquer le pourquoi des flèches PostGIS vers OGR et vers PostgreSQL, par exemple).

  • en ligne de commande:

  • à partir du menu Link external formats (v.external ou r.external)

  • dans le cas des couches vectorielles, elle permet de visualiser dans GRASS GIS des fichiers shapefiles, des tables PostGIS ou SpatiaLite, en lecture seule et avec/sans topologie (pas une topologie GRASS, mais celle de la source, si elle en dispose, comme dans les dernières versions de PostGIS ou de SpatiaLite, par exemple).  Cela vous permet néanmoins:
    • de consulter les données sans les importer;
    • d'effectuer rapidement des transformations avec tous les modules de GRASS (comme la transformation d'un fichier shapefile en KML, GML, etc. ou avec les rasters, un fichier GeoTIFF  en coordonnées x,y, z avec la commande r.out.xyz, par exemple);
  • v.external avec une couche PostGIS et une couche SpatiaLite:


  • si l'on veut modifier les géométries et créer de nouveaux objets et/ou une topologie GRASS, tout en gardant les attributs dans les fichiers dbf, ou les tables PostGIS ou SpatiaLite, il est alors nécessaire de passer par la manière classique pour importer la couche et la reconstruire selon les principes de GRASS (géométries/topologies dans GRASS, beaucoup plus strictes que celles de PostGIS ou de SpatiaLite, attributs dans PostGISou SpatiaLite);

Les divers types de bases de données qui peuvent être utilisés

Ceux-ci sont résumés dans la figure suivante :

 

Leurs caractéristiques générales sont reprises dans le tableau suivant (repris de « SQL support in GRASS GIS») :

dbffichiers dbfhttp://shapelib.maptools.org/dbf_api.html
sqlitefichier et tables SQLitehttp://sqlite.org/
pgtables PostgreSQLhttp://postgresql.org/
mysqltables MySQLhttp://mysql.org/
mesqltables MySQL intégréeshttp://mysql.org/
odbcUnixODBC. (PostgreSQL, Oracle, etc.)http://www.unixodbc.org/

(le format spécifique mesql qui permet d'utiliser des tables MySQL sans serveur ne sera pas abordé ici)

Caractéristiques dans GRASS GIS

Les formats DBF et SQLite sont matérialisés par des fichiers (voir « SQLite - SpatiaLite: le pourquoi du comment»), les autres non. Hormis le format DBF, tous les autres concernent des SGBD. Les possibilités de traitement SQL avec DBF sont très limitées, voire inexistantes (en plus des nombreuses contraintes comme les noms de champs limités à 10 caractères). La connexion ODBC permet théoriquement d'utiliser d'autres SGBD comme Oracle, Microsoft Access, FileMaker Pro, etc. Pratiquement, ce n'est pas aussi évident que ça ...

 

  • au format DBF (n fichiers dbf):

  • au format SQLite (n tables dans un fichier) :

  • il ne faut pas confondre les bases SQLite de GRASS GIS, qui ne contiennent que les attributs des couches vectorielles, avec les bases SQLite/SpatiaLite.

Les commandes disponibles

Elles sont dissociées en deux séries de commandes, la création et la gestion d'une table et l'utilisation de celle-ci une fois qu'elle est associée à une couche vectorielle. Notons que tout celaest effectué automatiquement dans le format défini par défaut (DBF, SQLite, etc.) lors d'une importation avec v.in.ogr ou lors de la numérisation d'une nouvelle couche:

création automatique d'une table lors de la numérisation

C'est cependant très utile car il est possible d'associer plusieurs tables dans des formats différents à une couche vecteur:

  • db.*   permet de créer une base de données et de la gérer. C'est la démarche effectuée en cochant Create attribute table dans le dialogue précédent. Les commandes disponibles sont db.columns (liste des colonnes), db.copy, db.drivers, db.login, db.tables (liste des tables), db.connect, db.describe, db.execute (requêtes SQL), db.select (requêtes SQL), db.test. db.in.ogr et db.out.ogr permettent d'importer ou d'exporter les tables non spatiales 
  • avec GRASS 6.4.x:

  • v.db.*   permet d'associer une table à une couche vectorielle et de l'utiliser avec celle-ci (ajout/suppression d'enregistrements, modification, etc.). C'est ce qu'on utilise, par exemple, pour modifier la table créée précédemment. Les commandes sont v.db.addcol, v.db.droptable, v.db.update, v.db.addtable, v.db.reconnect.all v.db.connect, v.db.select 
  • avec GRASS 6.4.x:

  • avec GRASS 7.0

  • les couches vectorielles d'un même jeu de données peuvent parfaitement avoir des attributs placés dans des SGBD différents. Une même couche vectorielle peut avoir différentes tables attributaires (notion de layer, déjà vue).
  • par exemple, si  db.connect spécifie la base par défaut d'un jeu de données,  il est tout à fait possible de spécifier une autre base pour une couche vectorielle spécifique à l'aide de la commande v.db.connect.

 Les interfaces graphiques

GRASS, depuis les versions 6.4.x, offre une interface graphique centralisée qui permet d'effectuer la plupart des traitements.

  • cette interface est accessible depuis le « Layer Manager »:

 

  • gestion des enregistrements:

  • gestion des champs:

  • gestion de la base utilisée pour une table: format DBF

  • gestion de la base utilisée pour une table: format SQLite:

  • tous les traitements se font sous forme de requêtes SQL qu'il est possible de construire à partir d'un « Constructeur SQL » : attention, db.query permet de traiter n'importe quelle table alors que v.db.query est réservé aux traitements sur les tables attributaires d'une couche vectorielle (liens avec la géométrie/topologie):
  • avec GRASS 6.4.x:

  • avec GRASS 7:

 

En ligne de commande pour aller plus loin

Malgré ces belles interfaces, il est utile d'expliquer les procédures en ligne de commande. Cela permet de comprendre réellement ce que l'on fait et sur quoi sont basées ces interfaces graphiques.

Définition de la base par défaut d'un secteur (LOCATION) ou d'un jeu de données (MAPSET) en lieu et place des fichiers dbf.

  • On utilise la commande  db.connect du menu Database/Manage databases/Connect:

 

  • dans le cas de SQLite, le fichier mabase.db sera créé dans le répertoire par défaut (il y a moyen de spécifier un répertoire spécifique). Toutes les tables attributaires des nouvelles couches vectorielles seront placées dans ce fichier/base de données ;
  • dans les autres cas, la même opération sera effectuée au sein des bases de données spécifiées ;
  • il y a toujours moyen de connaître le type de base de données utilisé par défaut avec la commande db.connect -p et de tester la connection avec db.test.

Connexion d'une couche vectorielle à une table attributaire spécifique

  • pour connecter une couche vectorielle à une table attributaire spécifique dans une base de données, il faut utiliser la commande v.db.connect du menu Database/Vector database connection/Set vector map_database connection (rappelons que cette procédure est réalisée automatiquement lors d'une importation avec v.in.ogr ou lors de la numérisation d'une nouvelle couche).
  • les commandes:

  • la clé de jonction est spécifiée par la variable key (de type numérique entier) et le type de base utilisé pour une couche par la variable layer. Il est tout à fait possible d'avoir une table dbf dans la couche 0, une autre d'un autre type dans la couche 1 et ainsi de suite (voir Les « geodatabases » de GRASS GIS, structure générale (LOCATION, MAPSET) et conséquences pratiques (changement de système de projection, etc.) );
  • la vérification du type de table se fait avec la commande v.db.connect -p.

Changement de SGBD, migration des tables attributaires

Comme déjà soulignée, une des grandes forces de GRASS GIS est la possibilité de passer d'un format de table à un autre de manière presque transparente grâce au DBMI(DataBase Management Interface). En pratique, cela se fait avec la commande db.copy du menu Database/Manage databases/Copy table suivie de la commande v.db.connect pour reconnecter la couche vectorielle:

  • db.copy permet, en général, de copier les tables (attributaires ou non) de n'importe quel système de base vers un autre.

traitements

avec db.*

Tous les traitements se font avec les requêtes SQL, sous forme de chaîne de caractères ou de fichier SQL:

avec v.db.*

Tous les traitements se font avec des requêtes SQL, variables suivant les modules utilisés:

Changements avec la version 7 de GRASS GIS

Il est déjà possible d'utiliser des versions beta de cette version en cours de développement:

Dans cette version:

  • le format des couches vectorielles a été profondément modifié (d'où le v.build, nécessaire);
  • le format de base de données par défaut est SQLite ( et plus DBF);
  • les algorithmes de traitement ont été significativement améliorés au niveau de la vitesse de traitement et des ressources mémoires de calcul, ce qui offre la possibilité d'analyser des ensembles de données énormes (là où la plupart des autres échouent).

Conversion des couches vectorielles au format de la version 6 vers celui de la version 7:

Voir grasswiki.osgeo.org/wiki/Convert_all_GRASS_6_vector_maps_to_GRASS_7 pour toutes les couches vectorielles d'un jeu de données (MAPSET):

Mais, il est tout à fait possible de continuer à utiliser le format DBF si on le désire, le seul v.build suffit.

Conversion des couches vectorielles au format de la version 7 vers celui de la version 6.x:

Comme GRASS GIS version 6.4.x peut utiliser des bases SQLite, il n'y a que la topologie à changer:

Conclusions

J'espère que la gestion des tables attributaires vous paraîtra maintenant un peu plus claire. Cela reste cependant encore éloigné des possibilités offertes par Quantum GIS ou ESRI ArcGIS  mais cela évolue (personnellement travailler en ligne de commande me parait plus rapide et plus formateur que de pousser sur des boutons, surtout que tout peut être fait en Python).

Tous les traitements ont été effectués sur Mac OS X avec diverses versions de GRASS GIS, 6.4.2, 7.0 et Inkscape pour les figures. 


Site officiel :Database management in GRASS GIS
Site officiel :SQL support in GRASS GIS
Autres Liens :Les « geodatabases » de GRASS GIS, structure générale (LOCATION, MAPSET) et conséquences pratiques (changement de système de projection, etc.)
Autres Liens :GRASS GIS : géométries, topologies et conséquences pratiques (vecteurs, rasters, volumes)
Autres Liens :SQLite - SpatiaLite: le pourquoi du comment


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France

PyQGIS: des géométries, des vecteurs, de l'algèbre vectorielle ou des cosinus directeurs...

$
0
0
Niveau Intermédiaire
Logiciels utilisésQuantum GIS (QGIS)
Plateforme Windows | Mac | Linux | FreeBSD

Traiter des points, des lignes ou des courbes, ça ne vous rappelle rien ? C'est un domaine que vous avez pourtant abordé lors de vos études secondaires dans les cours de géométrie vectorielle et de trigonométrie (du moins je l'espère...).

Quel rapport avec les logiciels SIG ? Ils traitent le même genre de données (on parle bien de couches vectorielles) et donc les mêmes solutions peuvent leur être apportées.

Si je regarde les questions qui sont posées sur les forums géomatiques ou sur gis stackexchange, il est possible de constater que, dans ce domaine, c'est toujours les mêmes questions qui reviennent (car la plupart ne peuvent pas nécessairement être résolues avec les menus ou les boutons du logiciel utilisé ou par un plugin, donc problème...):

  • comment créer une ligne à partir d'un point et un azimut;
  • comment obtenir l'azimut d'une ligne;
  • comment calculer l'angle entre 2 lignes;
  • comment calculer la distance minimum entre un  point et une ligne;
  • comment obtenir la droite perpendiculaire entre un point et une ligne;
  • comment créer des points médians ou des points équidistants sur une ligne;
  • comment découper une ligne en segments;
  • comment appliquer un changement d'échelle, une translation ou une rotation à un objet;
  • etc.

Les algorithmes nécessaires pour répondre à toutes ces questions ont pourtant été développés par, notamment, Paul Bourke dès 1998 (paulbourke.net/geometry/) pour les objets 2D et 3D. Il en a même proposé des implémentations en divers langages.

De surcroît, les vecteurs sont utilisés dans bien d'autres domaines et il y a une multitude de tutoriels et de cours (universitaires ou non) qui expliquent, analysent et/ou décortiquent le sujet et apportent des solutions. Il suffit d'aller se servir, mais personne ne semble faire le lien, chacun restant cloisonné dans son domaine...

Comme géologue, j'utilise depuis longtemps des vecteurs dans d'autres spécialités que la géomatique, surtout ceux qui concernent les objets 3D. Mais, malheureusement, QGIS, avec PyQGIS, ne le permet pas encore (voir QGIS, représentation 3D des couches vectorielles (shapefiles dits 3D ou shapefiles avec attributs z) avec les modules Python Matplotlib ou Visvis à partir de la console Python ). C'est pourquoi j'emploie, de préférence, le module Python Shapely.

Après avoir présenté ici les principes, je les appliquerai dans un article suivant pour illustrer quelques applications sur un plan 2D avec PyQGIS à partir des diverses réponses fournies par moi et d'autres sur le ForumSIG et sur gis stackexchange.

Je ne parlerai pas ici des attributs pour me focaliser sur les simples objets géométriques et ne pas créer de difficultés supplémentaires. Je précise aussi que je travaille dans un système de projection cartésien où la géométrie euclidienne a un sens (distances, etc.).

La géométrie avec PyQGIS

Comme déjà soulignés (Python: traitement des couches vectorielles dans une perspective géologique, lecture et enregistrement des couches sous forme de dictionnaires avec le module Fiona), les tutoriels sur PyQGIS ne brillent pas par leur clarté (pour un amateur de Python et d'autres modules géospatiaux), poussant, de plus, les gens a créer tout de suite des plugins, sans bien maîtriser PyQGIS.

Pourtant, le traitement des objets géométriques n'a, en soi, rien de bien compliqué (www.qgis.org/pyqgis-cookbook/geometry.html).

L'objet fondamental est le point, caractérisé par une paire de coordonnées x, y. Une ligne ou un polygone sont composés de plusieurs points:

  • un point en géométrie PyQGIS se construit avec la commande: QgsPoint(x,y);
  • la construction d'une ligne est basée sur ces points: QgsGeometry.fromPolyline([QgsPoint(x1,y1),QgsPoint(x2,y2)]));
  • de même qu'un polygone qui doit être fermé: QgsGeometry.fromPolygon([[QgsPoint(x1,y1),QgsPoint(x2,y2), QgsPoint(x3,y3),QgsPoint(x1,y1)]]);
  • une polyligne est composée de plusieurs lignes et donc de plusieurs points;
  • etc.

Les vecteurs

En géométrie euclidienne, les vecteurs ressemblent à ces éléments de géométrie: ils peuvent aussi être définis  par une paire de coordonnées x,y,z s'ils décrivent une position dans l'espace. La différence réside dans l'interprétation géométrique et les opérations qui s'y rattachent. Pour simplifier, un vecteur est considéré comme une flèche ayant pour extrémités un point de départ et un point d'arrivée.

Dans le cas des points QGIS (plan en 2 dimensions), ils peuvent être considérés:

  • soit comme des vecteurs nuls, vecteurs particuliers dont la longueur est nulle, c'est-à-dire dont l'origine est confondue avec l'extrémité;
  • soit comme des vecteurs normaux avec une origine (point(0,0)) et une extrémité (coordonnées x,y du point).

C'est cette dernière conception qui sera utilisée, car beaucoup plus intéressante comme nous allons le voir. Dans la suite, ne vous étonnez pas du type de coordonnées utilisé (0.5, etc.) mais, toutes les figures ont été réalisées sur QGIS avec le script présenté dans la deuxième partie, crea_mem_layer.py et s'il est appliqué à des coordonnées plus réalistes,  le résultat sera moins clair pour les explications (étiquettes, etc.).

  • points QGIS (QgsPoint(0.5,-1), ...)  et vecteurs  correspondants:

 

  • Vecteur correspondant à la ligne QgsGeometry.fromPolyline([QgsPoint(0.5,-1.0),QgsPoint(1.5,0.5)];

  • Les coordonnées d'un vecteur sont obtenues par la paire x2-x1, y2-y1, notée entre crochets. Dans le cas du vecteur équivalent à un point QGIS, ce sont donc les mêmes que celles du point (x-0, y-0).

Suivant cette conception, il n'y a donc aucune différence entre un vecteur « point » de QGIS et un vecteur « ligne », tous deux ayant une longueur et une direction.

Définitions

Mathématiquement, un vecteur est un élément d'un espace vectoriel qui répond à un certain nombre de propriétés (fr.wikipedia.org/wiki/Espace_vectoriel#D.C3.A9finitions) et les nombreux cours ou tutoriels qu'on trouve sur Internet. Il est formellement défini par trois composantes:

  • sa direction
  • son sens ou son orientation
  • sa longueur nommée magnitude ou norme

De ce fait, un vecteur peut être déplacé n'importe où dans l'espace, à condition que ses trois composantes, direction, sens et longueur ne changent pas. Ainsi tous les vecteurs de la figure suivante sont égaux, même si les positions des points sont différentes, à la différence des vecteurs géomatiques qui ne sont que des cas particuliers nommés vecteurs de position ou vecteurs de coordonnées (il y a bien d'autres domaines ou la notion de vecteur n'a aucune composante de position comme les vecteurs en physique et autres disciplines, vecteurs de forces, de contraintes, de déformation, etc., voir une très belle illustration de ces concepts en géologie dans les articles de Pierre Thomas sur Planet-Terre, Une bélemnite cisaillée : déformation et contraintes ou En avril, les poissons se font failler):

Vecteur unitaire ou normalisé, vecteurs de position, vecteurs de direction

Un vecteur unitaire ou vecteur normalisé est simplement un vecteur dont la longueur ou la magnitude est égale à 1 (en rouge, vecteur unitaire de la ligne bleue).

 Quel est son intérêt ?

  • il va permettre de nombreux traitements comme la création de points équidistants sur une ligne (simple multiplication du vecteur unitaire).
  • de plus, comme le même vecteur peut être défini de diverses manières:
    • par des coordonnées cartésiennes x,y, (z) (vecteur de position);
    • par des coordonnées polaires (d,θ) ou des coordonnées sphériques (d,φ, θ) (vecteur de direction);
    • par les cosinus directeurs cosa,cosb, cosc (d.cos(α),  d.cos(β),(d.cos(γ)) (vecteur de direction);

  • Il est facile de constater que dans ce cas:
    • il est possible à tout moment de passer des coordonnées cartésiennes à des coordonnées angulaires de direction (attention, sans indication sur le sens ou l'orientation du vecteur);
    • le vecteur devient, en coordonnées polaires: x=cos(θ) et y= sin(θ);
    • et avec les cosinus directeurs : x = cos(α) et y = cos(β).

Cette particularité est surtout utilisée dans le cas des vecteurs 3D car la direction univoque obtenue peut directement être utilisée, en géologie par exemple, pour tous les traitements d'angles:

En résumé

Ce qu'il faut retenir, c'est qu'à l'inverse des objets purement géométriques, il est possible d'ajouter, de soustraire, de multiplier ou de diviser ces vecteurs et toutes ces opérations ont un sens géométrique simple. Sans le savoir, la plupart d'entre nous effectuent des traitements vectoriels dans nos logiciels SIG mais, de manière non formalisée (voir The mathematics of GIS de Wolfgang Kainz, fichier PDF).

Comme géologue, la meilleure approche du sujet que j'ai eu vient de deux articles proposés dès 1999 par H.L. Vacher dans la revue « Journal of Education Geology » devenue « Journal of Education Geoscience » dans la série Computational geology avec notamment (fichiers PDF):

L'application géologique des vecteurs sous forme de cosinus directeurs est amplement illustrée par E. de Kemp pour la modélisation géologique 3D (voir Grass Gis et Paraview: la modélisation géologique 3D pour les démunis, ou les enthousiastes...),  R. Groshong dans 3-D Structural Geology (Springer, 2008,) ou R. Allmendinger et N. Cardozo  dans Structural Geology Algorithms Vectors and Tensors (Cambridge University Press, 2011) avec, en particulier, l'analyse des tenseurs

Algèbre vectorielle avec PyQGIS

Qui dit algèbre vectorielle en Python pense à Numpy mais l'utiliser implique de transformer les données en tableaux (array) numpy, ce qui n'est pas le but ici. Il faut plutôt se tourner vers des modules équivalents comme Euclid, pyplane ou les très nombreuses classes Vector ou Vecteur que l'on peut trouver sur Internet comme celles de nliautaud.fr/wiki/articles/vecteurs, code.activestate.com/recipes/52272-vector-a-list-based-vector-class-supporting-elemen/,   gist.github.com/mcleonard/5351452, etc., qui n'ont, à priori, aucun lien avec les traitements géomatiques (et oui, tout existe déjà, il suffit de se servir...). Nous n'utiliserons que le module math, présent dans la distribution standard de Python, et le sujet sera abordé sans formalisme mathématique avancé pour ne pas « noyer » le lecteur (comme dans mon cas...).

Formellement pourtant, il faudrait traiter des vecteurs et non des géométries QGIS. Mais, comme une ligne QGIS allant de (x1, y1) à (x2, y2) est représentée par le vecteur [x2 - x1, y2- y1], il est possible de  « tricher » et d'assimiler celui-ci  à un point virtuel QgsPoint((x2 - x1, y2- y1) pour les calculs,  même s'il ne sera jamais figuré.

Longueur d'un vecteur:

La  longueur (magnitude, norme) est calculée par une fonction euclidienne classique et, du fait de la définition retenue, un vecteur « point » de QGIS a une longueur (de l'origine (0, 0) au point (x, y)):

def mag(point):#magnitude d'un vecteurreturnmath.sqrt(point.x()**2 + point.y()**2)

C'est la même chose avec un vecteur « ligne » (la fonction précédente n'est qu'une simplification de dist(point, origine)):

def longueur_theorique(point1,point2):#théorique: distance euclidienne entre 2 pointsreturnmath.sqrt((point2.x()-point1.x())**2 + (point2.y()-point1.y())**2)

mais, comme PyQGIS possède une fonction pour le faire (sqrDist, carré de la distance):

def dist(point1,point2):#PyQGIS: distance euclidienne entre 2 points#returnmath.sqrt(point1.sqrDist(point2))

Azimut d'un vecteur

L'azimut d'un vecteur se calcule aussi suivant la fonction euclidienne avec ajustements en fonction de l'intervalle voulu (de 0 à 360°):

def azimut_theorique(point1, point2):#théorique: calcul de l'azimut entre 2 points,intervalle de 0 à 180°,iciangle = math.atan2(point2.x() - point1.x(), point2.y() - point1.y())returnmath.degrees(angle)if angle &gt; 0 elsemath.degrees(angle) + 180

Encore une fois PyQGIS possède une fonction pour le faire (azimuth, résultat directement en degrés):

def azimut(point1,point2):# PyQGIS: azimut entre entre 2 points (en degrés)return point1.azimuth(point2)

d'où l'azimut d'un vecteur « point » ( = azimut(origine, point))

def azimut_pt(point):origine = QgsPoint(0,0)return origine.azimuth(point)

Addition, soustraction, multiplication

Ce sont des fonctions triviales très faciles à implémenter:

def diff(point2, point1):# soustraction de 2 vecteursreturn QgsPoint(point2.x()-point1.x(), point2.y() - point1.y())
def som(point2,point1):# addition entre 2 vecteursreturn QgsPoint(point2.x()+point1.x(), point2.y()+point1.y())

Il est aussi possible d'effectuer des sommes ou des soustractions  avec un scalaire:

def som_k(point, dx,dy):return QgsPoint(point.x()+dx, point.y()+dy)

De la même manière, un vecteur peut aussi être multiplié ou divisé par un scalaire (changement d'échelle):

def vecxscal(point,k):# multiplication d'un vecteur par un scalairereturn QgsPoint(point.x()*k, point.y()*k)

PyQGIS a une fonction équivalente (multiply) mais elle traite l'objet géométrique lui-même au lieu de fournir un nouvel objet comme résultat (= vecteur original x k) :

def vecxscal_qgis(point,k):# PyQGIS: multiplication d'un vecteur par un scalaire'''returnQgsPoint.multiply(k)

Norme et normalisation d'un vecteur

Pour rappel, normaliser un vecteur consiste à ramener sa longueur à 1 (vecteur unitaire):

def norm(point1, point2):# normalisation d'un vecteurpoint = diff(point2,point1)m = mag(point)return QgsPoint(point.x()/m, point.y()/m)

et pour un simple vecteur « point » (= norm(origine, point)):

def normpt(point):# normalisation d'un vecteur point'''m = mag(point)return QgsPoint(point.x()/m, point.y()/m) 

Produit scalaire

Le produit scalaire (dot product) entre 2 vecteurs donne, comme son nom l'indique, un scalaire comme résultat (voir Produit scalaire). En pratique, celui-ci est le cosinus de l'angle entre les 2 vecteurs multiplié par les longueurs de ces vecteurs. Parmi ses nombreuses propriétés, il permet donc de calculer l'angle entre les 2 vecteurs et leur orthogonalité: il est égal à 0 si les vecteurs sont perpendiculaires.

def dot_product(point1, point2):# produit scalaire de 2 vecteursreturn(point1.x()*point2.x() + point1.y()*point2.y())

Calcul de l'angle entre deux vecteurs:

a = QgsPoint(2,3)b = QgsPoint(5,4)math.degrees(math.acos(dot_product(a,b)/(mag(a)*mag(b))))17.650124219930088

Mais avec PyQGIS, ceci peut être fait plus simplement avec:

azimut_pt(b) - azimut_pt(a)17.65012421993012

Produit vectoriel et déterminant

Le produit vectoriel (cross product) est uniquement défini dans l'espace euclidien tridimensionnel. Le résultat est en effet un nouveau vecteur qui est perpendiculaire au plan contenant les 2 vecteurs (c'est le pôle d'un plan en géologie). Il peut être calculé avec Shapely mais, pas avec PyQGIS (pas d'accès à la coordonnée z).

Avec Shapely:

def cross_product(point1, point2):# produit vectoriel avec shapelyreturn Point(point1.y*point2.z - point1.z*point2.y, point1.z*point2.x - point1.x*point2.z, point1.x*point2.y - point1.y*point2.x)

Mais, que se passe-t-il si nous considérons 2 vecteurs situés sur un plan  (2D, z=0) dans l'espace 3D;

A = Point(2,3,0)B = Point(5,4,0)cp = cross_product(A,B)print(cp.x, cp.y, cp.z)(0.0, 0.0, -7.0)

Le résultat sera sera bien un vecteur perpendiculaire à ce plan (d'où x= 0 et y = 0) où seule la valeur z a un sens mais, apparemment peu utile avec PyQGIS.

Le même résultat peut être obtenu avec le déterminant de ces vecteurs dans l'espace 2D. Son résultat est un scalaire égal à cette valeur z (je vous laisse le soin de trouver la démonstration mathématique de cette propriété) et cette fonction peut être implémentée dans PyQGIS:

def det(point1, point2):# déterminant de 2 vecteursreturn(point1.x*point2.y) - (point1.y*point2.x)det(A,B)-7.0

Et ce déterminant a une signification, voir Déterminant de deux vecteurs dans le plan euclidien dans Déterminant (mathématiques):

  • il permet de vérifier si  2 lignes sont parallèles (vecteurs colinéaires): résultat = 0;
  • il permet aussi de calculer l'angle entre les vecteurs;
  • il permet de calculer l'aire de la surface comprise entre ces vecteurs.

Coordonnées polaires, cosinus directeurs

Transformation vecteur(x, y) vers vecteur  (d, θ) en fonction de l'azimut

Il s'agit de la transformation des coordonnées cartésiennes en  coordonnées  polaires. la manière classique se fait avec la pente: atan(dy/dx):

def car_pol_théorique(point):r = mag(point)angle = math.degrees(math.atan2(point.y(),point.x()))return r, angle

mais comme:

car_pol_theorique(QgsPoint(5,4))(6.4031242374328485, 38.659808254090088)90 - azimut_pt(QgsPoint(5,4))38.659808254090088

il est possible de le calculer en fonction de l'azimut (angle =  atan(dx/dy), directement accessible avec PyQGIS:

def car_pol(point):return mag(point),90 - azimut_pt(point)

et l'inverse:

def pol_car(dist, angle):return QgsPoint(dist *math.cos(math.radians(angle)),dist *math.sin(math.radians(angle)))

vecteur(x, y) vers vecteur(cosa, cosb), cosinus directeurs

Pour rappel, les cosinus directeurs d'un vecteur sont les cosinus des angles entre le vecteur et chaque axe (cosa = d.cos(α), cosb = d.cos(β)).

def cosdir(point):# cosinus directeurs d'un vecteur point(x,y)cosa = point.x() / mag(point)cosb = point.y()/ mag(point)return cosa,cosb

Le résultat est un pur vecteur de direction:

cosa,cosb = cosdir(QgsPoint(5,4))(0.55470019622522915, 0.83205029433784372)# retour à l'azimut et aux coordonnées polaires définies précédemmentmath.degrees(math.atan2(cosa,cosb))51.340191745909912# azimut du vecteur point = azimut_pt(QgsPoint(5,4))math.degrees(math.atan2(cosb/cosa))38.659808254090088# angle θ des coordonnées polaires

Il y a aussi moyen de calculer des cosinus directeurs à partir de l'azimut et du pendage en 3D d'une mesure structurale géologique, méthode utilisée, entre autres, par E. de Kemp pour la modélisation géologique 3D mais, comme PyQGIS n'autorise pas la manipulation de la valeur z, il n'est possible de le faire qu'avec l'azimut:

def cosdir_azim(azim):az = math.radians(azim)cosa = math.sin(az)cosb = math.cos(az)return cosa,cosb

Le résultat donnera toujours un vecteur unitaire, ce qui facilite grandement la vie pour une multitude de traitements. Le résultat avec des cosinus élimine les petits problèmes rencontrés avec la fonction tangente.

Rotations, translations, cisaillements

Il est possible d'effectuer tous ces traitements avec PyQGIS, il suffit d'aller se servir dans les nombreuses classes Vector ou Vecteur que l'on trouve sur Internet (voir aussi Rotation vectorielle). Cela peut se faire avec les coordonnées cartésiennes ou avec les cosinus directeurs (matrices 2D de translation, de rotation et de cisaillement, sans utiliser Numpy). Voici un exemple de rotation orthogonale et de rotation isométrique après translation de la ligne bleue, avec les nouvelles possibilités d'étiquetage de la version QGIS master:

 

Mais, depuis que la dernière version du module Shapely dispose de toutes ces fonctions (github.com/Toblerity/Shapely/blob/master/shapely/affinity.py), je l'utilise. Ce sujet étant rarement abordé dans les discussions sur QGIS, je ne le traiterai pas ici (il existe un plugin pour le faire Affine Transformations mais, il est difficile à utiliser si on n'en connait pas bien les principes). Notez que toutes ces opérations permettent de « géoréférencer » une couche vectorielle.

Conclusions

Nous voici armés pour la deuxième partie, l'application tous ces éléments dans PyQGIS: des géométries, des vecteurs, de l'algèbre vectorielle ou des cosinus directeurs, exemples d'applications.  Toutes les fonctions ont été regroupées dans le script algèbre_vect_PyQGIS.py.

Tous les traitements ont été effectués sur Mac OS X, avec le script crea_mem_layer.py (origine des figures, hormis celles en 3D) et plusieurs versions de QGIS master.

Site officiel :QGIS, représentation 3D des couches vectorielles (shapefiles dits 3D ou shapefiles avec attributs z) avec les modules Python Matplotlib ou Visvis à partir de la console Python
Site officiel :Python: traitement des couches vectorielles dans une perspective géologique, lecture et enregistrement des couches sous forme de dictionnaires avec le module Fiona
Site officiel :Grass Gis et Paraview: la modélisation géologique 3D pour les démunis, ou les enthousiastes...
Site officiel : 3-D Structural Geology
Site officiel :Structural Geology Algorithms, Vectors and Tensors
Site officiel :PyQGIS: des géométries, des vecteurs, de l'algèbre vectorielle ou des cosinus directeurs, exemples d'applications


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France

PyQGIS: des géométries, des vecteurs, de l'algèbre vectorielle ou des cosinus directeurs, exemples d'applications.

$
0
0
Niveau Intermédiaire
Logiciels utilisésQuantum GIS (QGIS)
Plateforme Windows | Mac | Linux | FreeBSD

python

Après avoir présenté les algorithmes et les  fonctions en Python dans la première partie, PyQGIS: des géométries, des vecteurs, de l'algèbre vectorielle ou des cosinus directeurs..., nous allons en appliquer certains pour illustrer quelques applications sur un plan 2D avec PyQGIS à partir des diverses questions/réponses fournies par moi et d'autres sur le ForumSIG et sur gis stackexchange:

  • comment créer une ligne à partir d'un point et un azimut (cosinus directeurs);
  • comment créer des points équidistants sur une ligne (normalisation, multiplication ou cosinus directeurs);
  • comment trouver le point médian sur une ligne ou un segment de ligne (somme, multiplication);
  • comment découper une ligne en segments équidistants (normalisation, multiplication, somme, cosinus directeurs);
  • comment obtenir l'azimut d'une ligne (azimut d'un vecteur);
  • comment calculer l'angle entre 2 lignes (produit scalaire);
  • comment calculer la distance minimum entre un point et une ligne (produit scalaire);
  • comment obtenir la droite perpendiculaire entre un point et une ligne (produit scalaire).

Visualisation dans QGIS

Avant de commencer, il est nécessaire de pouvoir visualiser ces vecteurs/géométries dans QGIS de manière simple à partir de la console Python. Cela se fait en utilisant la particularité des « memory layers » (couches virtuelles, en mémoire).

Comme j'ai souvent besoin de créer des points et des lignes pour visualiser mes traitements à partir d'angles, j'ai créé une classe Python qui me permet d'effectuer simplement toutes les manipulations de création de couches et d'affichage dans QGIS (la classe complète me permet aussi de traiter les géométries Shapely dans QGIS et d'ajouter/modifier des éléments dans les tables attributaires):

crea_mem_layer.py

  1. #!/usr/bin/python
  2. # encoding: utf-8
  3. """
  4. QGIS: fonction et classe permettant de créer des couches virtuelles
  5. (memory layers)
  6. Martin Laloux 2012
  7. """
  8. from qgis.coreimport*
  9.  
  10. class Crea_couche(object):
  11. """création d'une couche virtuelle (memory layer) de type Point, LineString
  12. ou Polygon,ajout des géométries et affichage"""
  13. # création de la virtuelle à partir du nom et du type géométrique
  14. def__init__(self,nom,type):
  15. self.type=type
  16. self.nom = nom
  17. self.couche = QgsVectorLayer(self.type, self.nom , "memory")
  18. self.pr = self.couche.dataProvider()
  19. def create_point(self,point):
  20. #ajout d'un point
  21. self.seg = QgsFeature()
  22. self.seg.setGeometry(QgsGeometry.fromPoint(point))
  23. self.pr.addFeatures([self.seg])
  24. self.couche.updateExtents()
  25. def create_ligne(self, point1,point2):
  26. #ajout d'une ligne
  27. self.seg = QgsFeature()
  28. self.seg.setGeometry(QgsGeometry.fromPolyline([point1,point2]))
  29. self.pr.addFeatures([self.seg])
  30. self.couche.updateExtents()
  31. def create_poly(self,points):
  32. #ajout d'un polygone
  33. self.seg = QgsFeature()
  34. self.seg.setGeometry(QgsGeometry.fromPolygon([points]))
  35. self.pr.addFeatures([self.seg])
  36. self.couche.updateExtents()
  37. @property
  38. def aff_couche(self):
  39. #fin de l'édition et affichage de la couche
  40. QgsMapLayerRegistry.instance().addMapLayers([self.couche])
  41.  

Ce qui permet, en plaçant ce script dans le path de Python (PYTHONPATH):

from crea_mem_layer import Crea_couche# création d'une couche pointpt = Crea_couche("points", "Point")# ajout d'un point à la coucheline_start = QgsPoint(50,50)pt.create_point(line_start)# ajout d'un autre point à la coucheline_end = QgsPoint(100,150)pt.create_point(line_end)# fin de l'édition et affichage de la couchept.aff_couche# création d'une couche ligneli = Crea_couche("ligne", "LineString")# ajout d'une ligne à la coucheli.create_ligne(line_start,line_end)# fin de l'édition et affichage de la coucheli.aff_couche# création d'une couche polygonespol = Crea_couche("poly", "Polygon")points = [QgsPoint(60,60),QgsPoint(60,80),QgsPoint(80,80),QgsPoint(80,60),QgsPoint(60,60)]pol.create_poly(points)# fin de l'édition et affichage de la couchepol.aff_couche

Résultats dans QGIS:

Nous voici armés pour appliquer les algorithmes et fonctions de géométrie vectorielle analysés dans le précédent article et résumés dans le script algèbre_vect_PyQGIS.py.

Exemples d'applications

Comment créer une ligne à partir d'un point et un azimut ? (cosinus directeurs)

Il est possible, comme souvent enseigné, d'utiliser la transformation des coordonnées cartésiennes en coordonnées polaires (voir point suivant), mais c'est beaucoup plus direct d'utiliser les cosinus directeurs (cosdir_azim(azimut)) qui renvoient un vecteur unitaire. Il suffit donc de le multiplier par la longueur voulue (voir [QGIS] Créer des points autour d'un autre point aux coordonnées connues).

À partir de la ligne obtenue par le traitement précédent (line_start, line_end):

# calcule la position du point situé à une distance  et un  azimut du point pazimut = 120longueur = 50# cosinus directeurs de l'azimutcosa, cosb = cosdir_azim(azimut)# point résultantresultat = QgsPoint(point.x()+(longueur*cosa), point.y()+(longueur*cosb))# création des couches point et ligne résultantespt = Crea_couche("pt_120_50", "Point")pt.create_point(resultat)pt.aff_coucheli = Crea_couche("l_120_50", "LineString")li.create_ligne(line_end,resultat)li.aff_couche

Résultat: 

Et une simple boucle permet de continuer le cheminement éventuel:

 

Comment créer des points équidistants sur une ligne (normalisation, multiplication ou cosinus directeurs)

Ici, les utilisateurs de Shapely sont avantagés, car ils disposent d'une fonction pour générer des points équidistants sur une ligne (ligne.interpolate(distance), voir How to create equidistant points in QGIS?. C'est dans la continuité du point précedent.

Le nouvel API Python de la version master dispose aussi d'une telle fonction geom.interpolate(distance), voir Generating chainage (distance) nodes in QGIS de Nathan Woodrow (avec les « lourdeurs » de PyQGIS). Il est tout aussi facile de les créer directement en utilisant l'algèbre vectorielle:

Générer des points équidistants

1. par normalisation et multiplication

Le principe est simple, puisque suivant la conception retenue, un vecteur « point » a une direction (si je veux changer l'angle, voir le point précédent), il suffit donc de:

  1. calculer le vecteur unitaire (normalisation, fonction normpt(point));
  2. le multiplier par n fois l'intervalle voulu (fonctions vecxscal(point, k)et som(point1, point2)).
# vecteur unitaire: normalisation du vecteurpoint = QgsPoint(50,50)norme= normpt(point)serie = Crea_couche("normalisation", "Point")#  ajout de 10 fois du vecteur normalisé x ifor i inrange(5,100,5):serie.create_point(som(point,vecxscal(norme,i)))serie.aff_couche(pt_layer)
2. par les cosinus directeurs

Mais il est beaucoup plus intuitif de le faire avec les cosinus directeurs (qui renvoient un vecteur unitaire, pour mémoire) mais, cette fois-ci, à partir des coordonnées cartésiennes (cosdir(point)):

# cosinus directeur du point de départcosa,cosb = cosdir(point)# traitementserie = Crea_couche("cosdir", "Point")# points équidistants de 5 m sur une longueur de 100 mfor i inrange(5,100,5):serie.create_point(QgsPoint(point.x() + (i * cosa), point.y() + (i*cosb)))serie.aff_couche

 Résultat:

Générer des points équidistants sur une ligne:

Dans le cas de points générés sur une ligne, il y a une composante supplémentaire qui intervient, la longueur de la ligne. Les points générés doivent être situés sur la ligne et limités par sa longueur:

Application sur une polyligne QGIS existante:

couche = qgis.utils.iface.activeLayer()def paires(list):# Itération par paires dans une listefor i inrange(1, len(list)):yieldlist[i-1], list[i]intervalle = 5# création de la couche virtuellept_mid  = Crea_couche("mid5", "Point")#traitement sur les éléments sélectionnésfor elem in couche.selectedFeatures():ligne = elem.geometry()for seg_start, seg_end in paires(ligne.asPolyline()):line_start = QgsPoint(seg_start)line_end = QgsPoint(seg_end)# point médian du vecteur lignepointm =diff(line_end, line_start)# cos directeurs de la lignecosa,cosb = cosdir(pointm)# longueur du vecteur (ligne)long = dist(line_end, line_start)for i inrange(intervalle,long,intervalle):pt_mid.create_point(QgsPoint(line_start.x() + (i * cosa), line_start.y() + (i*cosb)))pt_mid.aff_couche

Si la longueur de la ligne n'est pas un multiple de l'intervalle, il est possible de choisir entre un dernier segment inférieur à l'intervalle (à gauche) ou supérieur à l'intervalle (à droite):

.

Tant avec la fonction de Shapely, avec celle du nouvel API de PYQGIS  ou avec celle présentée ici, il est possible de constater quelques petits problèmes comme ceux rencontrés au sommet supérieur:

polyligne,             avec l'algorithme proposé,               avec Shapely

C'est lié au fait que la boucle traite les résultats par segments et non sur l'entièreté de la ligne. Si l'on veut corriger le tir, il serait nécessaire de travailler sur la longueur totale de la polyligne.

Comment découper une ligne en segments équidistants

La réponse est évidente, il suffit de transformer les résultats précédents en lignes (de point à point).

Comment trouver le point médian sur une ligne ou un segment de ligne (somme, multiplication):

Cela peut se faire avec le plugin Sextante (algorithmes fTools) mais, il est intéressant de présenter l'algorithme avec les fonctions som(point1, point2) et vecxsal(point, k) car il permet de trouver d'autres points que le point médian sur une ligne (il suffit de modifier la valeur k).  Je vous invite à consulter ma réponse sur gis.stackexchange à How to find the middle point of a line in QGIS:

def mid(point1, point2):return vecxscal(som(point1,point2),0.5)

L'inverse, comment obtenir l'azimut d'une ligne

Cela pourrait se faire en insérant dans un champ de la calculatrice de champs (voir How to add Direction and Distance to attribute table?):

(atan((xat(-1)-xat(0))/(yat(-1)-yat(0)))) * 180/3.14159 + (180 *(((yat(-1)-yat(0)) < 0) + (((xat(-1)-xat(0)) < 0 AND (yat(-1) - yat(0)) >0)*2)))

mais si une ligne est composée de plusieurs segments, le résultat sera l'azimut moyen des segments. Si l'on veut les azimuts de tous les segments, il faut découper la ligne en segments et utiliser la fonction azimut(point1, point2).

Pour un traitement complet, je vous invite à consulter mes réponses sur gis.stackexchange à How do I find vector line bearing in QGIS or GRASS? et How to add Direction and Distance to attribute table?à partir de polylignes.

Comment calculer l'angle entre 2 lignes (produit scalaire):

Cela a été vu avec le produit scalaire entre vecteurs (dot_product(point1, point2)) ou avec la différence entre les azimuts des 2 vecteurs, voire avec le déterminant.

Comment obtenir la droite perpendiculaire entre un point et une ligne et/ou calculer la distance minimale entre un  point et une ligne (produit scalaire).

Le problème posé sur How to draw perpendicular lines in QGIS?était « comment trouver  la ligne perpendiculaire entre le point bleu et la ligne rouge et sa longueur ? »:

La  solution a été donnée par Paul Bourke (Minimum Distance betweena Point and a Line? ) en utilisant le produit scalaire et ces propriétés d'orthogonalité. Cette distance est en effet la longueur du vecteur perpendiculaire depuis ce point vers la ligne ou le segment de ligne. (voir  ma réponse à How to draw perpendicular lines in QGIS?) . Cela se fait avec le carré de la distance ( fonction sqrDist de PyQGIS):

def distance_min_point_ligne(point, line_start, line_end):# Calcul de la distance minimale entre un point et un segment de ligne et intersection# sqrDist de la ligne (fonction PyQGIS = longueur) de la ligne **2)distcar = line_start.sqrDist(line_end)# distance minimaleu = ((point.x() - line_start.x())*(line_end.x() - line_start.x()) + (point.y() - line_start.y())*(line_end.y() - line_start.y()))/(distcar)# point d'intersection sur la ligneix = line_start.x() + u *(line_end.x() - line_start.x())iy = line_start.y() + u *(line_end.y() - line_start.y())return QgsPoint(ix,iy)

 et le traitement (après avoir créé une couche avec un point de coordonnées (30,120):

#création de la ligne perpendiculaireli = Crea_couche("perp", "LineString")li.create_ligne(point,distance_min_point_ligne(point, line_start, line_end))li.aff_couche(line_layer)# distance minimale (longueur de la ligne résultante)sqrt(point.sqrDist(distance_min_point_ligne(point, line_start, line_end)))49.193495504995376

Résultat:

<br>

La preuve de l'orthogonalité est fournie par le résultat du produit scalaire entre les 2 vecteurs:

point_res = distance_min_point_ligne(point, line_start, line_end)point_res(74,98)dot_product(diff(point,point_res), diff(line_end,line_start)) 0

D'autres solutions ont été proposées comme celles de Nearest neighbor between a point layer and a line layer ou Elevation profile 10 km each side of a line avec Shapely.

Et tout le reste...

Et ce n'est pas tout, car avec l'algèbre vectorielle vous pouvez traiter beaucoup d'autres choses, comme les unions, les intersections, etc. (mais c'est déjà disponibles de manière standard dans la plupart des logiciels SIG), toutes les transformations angulaires comme les rotations ou les changements d'axes ou de système de projection. Il est même possible de traiter les géométries 3D avec le module shapely, comme illustré sur le Portail dans QGIS, représentation 3D des couches vectorielles (shapefiles dits 3D ou shapefiles avec attributs z) avec les modules Python Matplotlib ou Visvis à partir de la console Python ou des photos d'objets 3D comme le montre l'article récent DigiFract: A software and data model implementation for flexible acquisition and processing of fracture data from outcrops de N.J. Hardebol et G. Bertotti, 2013, Computers & Geosciences, v. 54, p. 326–33, qui utilise QGIS et l'algèbre vectorielle avec le module Shapely pour traiter des photos de parois ou d'affleurements géologiques (je vous conseille d'aller examiner les figures, librement disponibles pour comprendre la démarche).

Conclusions

J'espère que ces quelques traitements vous auront donné ou redonné l'envie d'utiliser l'algèbre vectorielle et, surtout,  l'envie d'aller « farfouiller » ailleurs, car, comme l'a souligné Sean Gillies, le traitement des données spatiales n'a rien de particulier vis-à-vis des autres types de données (« I want it to refute the notion that "spatial is special" and have no gotchas, no surprise » Fiona makes reading and writing data boring), hormis la complexité des explications...

Cela n'est pas limité aux vecteurs. À titre d'exemple, je voulais tester une méthode de lissage d'une couche par courbes de Bézier, méthode non disponible dans QGIS. Une rapide recherche m'a conduit à bezier_smooth.py de Matthew Perry (perrygeo) et à moi les courbes de Bézier cubiques ou quadratiques (en rouge sur la figure) avec la possibilité d'utiliser des points de contrôle (programmés):

 

Mais, il est vrai que du fait de mon travail, j'accorde plus d'importance aux géométries qu'aux traitements géomatiques classiques et que la connaissance préalable du module Shapely facilite grandement les choses (il est beaucoup plus clair pour les traitements, de même que le module gvsig de gvSIG, voir gvSIG 2.0: Scripting, exploit your gvSIG (III): Generate a polygon from a course ).

Pourquoi ne pas en faire un plugin, réclameront certains ?

Car tout ne peut pas être transformé en plugin, ma classe Python, spécialisée, me permet d'ajouter successivement des géométries sans passer par une interface graphique qui ralentirait le processus, ou d'utiliser un script Python. Je trouve aussi que l'utilisation de PyQt4 « obscurcit » tout...

Cela signifie aussi qu'un logiciel SIG comme QGIS peut être utilisé pour faire autre chose que de la géomatique pure, comme l'apprentissage de la géométrie, des vecteurs ou même, les traitements divers sur des images non géographiques, comme les photographies (utilisation en géologie (voir plus haut) ou en archéologie, par exemple, voir Exemple d’utilisation de Photoscan/QGIS pour la création d’un modèle géoréférencé 2.5D ).

Tous les traitements ont été effectués sur Mac OS X, avec le script crea_mem_layer.py (origine des figures) et plusieurs versions de QGIS master.

Site officiel :PyQGIS: des géométries, des vecteurs, de l'algèbre vectorielle ou des cosinus directeurs...
Site officiel :QGIS, représentation 3D des couches vectorielles (shapefiles dits 3D ou shapefiles avec attributs z) avec les modules Python Matplotlib ou Visvis à partir de la console Python


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France

GeoJSON, nouveau lingua franca en géomatique ?

$
0
0

Il y quelques années,  Howard Butler, Martin Daly, Allan Doyle, Sean Gillies, Tim Schaub et Christopher Schmidt (quelques grands noms de la planète géomatique OpenSource) se sont mis en tête de créer le format le plus simple possible pour représenter et échanger les données géospatiales (géométries et attributs), avec en vue, les services WEB.

Leur choix s'est tout de suite porté vers le format JSON (de préférence au format XML) et ainsi est né  GeoJSON dont la première mouture a été publiée en 2008 en une page: The GeoJSON Format Specification.

  • le formatprend en charge toutes les géométries de l'OGC (tag "geometry"):
    • {"type": "Point", "coordinates": [50.0, 70.0]}
    • {"type": "LineString","coordinates": [[50.0, 70.0], [80.0, 40.0]]}
    • {type": "Polygon","coordinates": [[[50.0, 70.0], [80.0, 40.0], [20.0, 10.0], [30.0, 60.0], [50.0, 70.0]]]}
    • plus les MultiPoints, les MultiLineString, les MultiPolygon, ...,  et les géométries 3D.
  • il prend aussi en charge les attributs qui peuvent être de plusieurs types (tag "properties"):
    • {"prop0": "valeur0"}
    • {"prop1": {"moi": "lui"}}
  • ensuite tout est assemblé suivant le principe d'une collection d'objets (tag "type"= "FeatureCollection"comprenant plusieurs features):
    • { "type": "FeatureCollection",
        "features": [
          { "type": "Feature",
            "geometry": {"type": "Point", "coordinates": [50.0, 70.0]},
            "properties": {"prop0": "valeur0", "prop1": "valeur1", ...,"propn": "valeurn"}
            ]},
          { "type": "Feature",
            "geometry": "type": "Point", "coordinates": [80.0, 40.0],
             ...
          ]}
  • à l'inverse des fichiers shapefiles ou des bases de données géospatiales, plusieurs types de géométries  peuvent coexister dans une FeatureCollection:
    • { "type": "FeatureCollection",
        "features": [
          { "type": "Feature",
            "geometry": {"type": "Point", "coordinates": [50.0, 70.0]},
            "properties": {"prop0": "valeur0"}
            },
          { "type": "Feature",
            "geometry": {"type": LineString","coordinates": [[50.0, 70.0], [80.0, 40.0]]}
            "properties": {"prop0": "valeur3","prop1": "valeur4"}
            },
          { "type": "Feature",
             "geometry": {type": "Polygon","coordinates": [[[50.0, 70.0], [80.0, 40.0], [20.0, 10.0], [30.0, 60.0], [50.0, 70.0]]]},
             "properties": {"prop0": "valeurn","prop1": {"moi": "lui"}
           },
          ....
           ]}

       
  • peuvent aussi y figurer le système de projection, de diverses manières (tag "crs"), et un cadre (Bounding box,tag"bbox")
    • { "type": "FeatureCollection",
      "bbox": [100.0, 0.0, 105.0, 1.0],
      "features": [...]}

Réactions

  • Trop simple, trop lourd, enfantin, pas sérieux, ridicule, faux,...,  ont affirmé certains (voir, entre autres, les réactions sur la liste QGIS-Developer à la proposition de kimaidou Get rid of Shapefiles ! Go GeoJSON ! ), mais générique, facile à lire et à écrire quelque soit le langage de programmation, sérialisable, portable, et autodéfini;
  • Paradoxalement, son succès a été très rapide. En peu de temps, il a été adopté par Google, Bings Map, Yahoo Maps, OpenLayers, Leaflet, CartoDB, D3js, App coverage d'Apple (JavaScript), GitHub, GeoServer (serveur cartographique) ou GeoDjango (serveur cartographique Python), Safe Sofware FME, de multiples librairies Python (voir plus bas) et les bases de données NoSQL comme MongoDB ou CouchDB qui utilisaient déjà le format JSON (voir Le NoSQL dans le domaine géospatial : MongoDB avec JavaScript ou Python, ArcGIS et Quantum Gis et Le NoSQL dans le domaine géospatial : CouchDB et GeoCouch (Couchbase), shp2geocouh, importation de shapefiles et serveur cartographique).
  • De plus, grâce à GDAL/OGR, Quantum GIS, PostGIS ou SpatiaLite peuvent lire ou écrire des couches GeoJSON;
  • Toutes les autres implémentations du format sont reprises dans GeoJSON users (Java, Ruby, php, etc.)

Pourquoi ?

  • Essentiellement du fait de sa simplicité pour la représentation (simple fichier texte) et le transfert de données (format JSON, adapté à Internet), bien qu'il ne soit pas certifié ISO19107 (OGC) comme le souligne ironiquement Sean Gillies dans GeoJSON is Spectacularly Wrong,Yet Somehow Right Enough.

Avec JavaScript:

Quelques exemples simples illustreront son intérêt:

  • Edit GeoJSON de Tom MacWright: dessinez sur la carte et récupérez les données;
  • GeoJSONLint composer ou  coller, validez et visualisez vos couches GeoJSON en «live»;
  • Wikigeo vous permet de chercher les articles de Wikipedia qui fournissent des coordonnées géographiques;
  • uMap qui permet de créer des cartes personnalisées à partir d'OpenSteetMap, soit en les dessinant, soit en les important (GeoJSON) (voir exemple à partir d'une couche géologique introduite dans ce format à GeoJSON import);
  • et maintenant GitHub, placez un fichier texte au format GeoJSON dans un de vos «repository» et il apparait directement sur un fond OpenStreetMap (voir mon exemple à exemple de fichier GeoJSON) ;
  • même plus, vous pouvez  récupérer cette couche dans QGIS (voir: GeoJSON on GitHub: Now What?)

Sans parler des très nombreux exemples sur Internet:

Avec Python (GeoJSON et __geo_interface__):

Si l'on regarde la structure du format JSON, il est facile de constater qu'elle correspond à un dictionnaire Python (dict= {}) qui peut être manipulé en tant que tel. Rien d'étonnant à ce que le format ait été adopté par la plupart des modules géospatiaux:

Mais, pour la plupart, cela ne concerne encore que le tag "geometry"et pas les autres. C'est pourquoi Sean Gillies a proposé le __geo_interface__ (GeoJSON-like protocol for geo-spatial (GIS) vector data) qu'il a implémenté dans Fiona (lecture et écriture, voir Python: traitement des couches vectorielles dans une perspective géologique, lecture et enregistrement des couches sous forme de dictionnaires avec le module Fiona) et dans Shapely (implémentés ensuite dans PySAL, pygeoif, PyShp et ogr):

import fionaf = fiona.open('point.shp')f.next(){'geometry': {'type': 'Point', 'coordinates': (161821.09375, 79076.0703125)}, 'id': '0', 'properties': {u'DIP_DIR': 120, u'STRATI_TYP': 1, u'DIP': 30}}f.next()['geometry']['coordinates'](161485.09375, 79272.34375)f.next()['properties']['DIP']55

qui peut maintenant être parfaitement adapté au module PyShp (voir  PyShp as Fiona (with the GeoJSON-like protocol))::

import shapefiledef records(fichier):    # génerateur    reader = shapefile.Reader(fichier)    fields = reader.fields[1:]    field_names = [field[0]for field in fields]    for sr in reader.shapeRecords():        geom = sr.shape.__geo_interface__        atr = dict(zip(field_names, sr.record))        yielddict(geometry=geom,properties=atr)  a = records('point.shp')a.next(){'geometry': {'type': 'Point', 'coordinates': (161821.09375, 79076.0703125)}, 'properties': {'DIP_DIR': 120, 'STRATI_TYP': 1, 'DIP': 30}}a.next()['geometry']['coordinates'](161485.09375, 79272.34375)a.next()['properties']['DIP']55

et au nouvel API de PyQGIS:

layer = qgis.utils.iface.activeLayer()def records(layer):    fields = layer.pendingFields()    field_names = [field.name()for field in fields]    for elem in layer.getFeatures():          geom= elem.geometry()          atr = dict(zip(field_names, elem.attributes()))          yielddict(geometry=geom.exportToGeoJSON(),properties=atr)c = records(layer)c.next(){'geometry': u'{ "type": "LineString", "coordinates": [ [164917.66073716, 115230.16694565], [170806.15565476, 116401.17445767] ] }', 'properties': {u'id': 1,u'test':u'ligne'}}

et ogr avec .ExportToJson()...

Une simple boucle pour obtenir la représentation GeoJSON complète d'une couche, un copier-coller dans les exemples en JavaScript présentés et l'affaire est réglée...

Problèmes et solutions présentées (TopoJSON)

Indépendamment des réactions déjà soulignées, très vives par moments, le principal problème est, vous vous en doutez, la taille des fichiers textes résultants. Diverses solutions ont été proposées, (comme How to minify GeoJSON files?), mais la plus convaincante est le format TopoJSON de Mike Bostock, extension de GeoJSON qui se base sur les relations topologiques de ESRI ArcInfo  (un seul arc pour la limite commune entre 2 polygones, par exemple). Le gain de taille est significatif (environ 80%) mais le format est récent et donc encore relativement peu utilisé.  Tout ce que l'on peut dire est que la boucle est bouclée, de ArcInfo aux shapefiles et.... retour.

Conclusions

"A remarkably simple feature, which i think significantly disrupts the traditional geospatial market" est un avis parmi d'autres sur le format GeoJSON (feomike.github.io/post/thoughts-on-disruption.htm). D'autres sont beaucoup plus négatifs, notamment les « professionnels » des SIGs et des SGBD pour qui c'est trop « simple ». C'est vrai, pas besoin de serveur cartographique, de REST ou de SOAP, de ..., un simple fichier texte et du JavaScript suffisent.

C'est en effet bien Internet et la cartographie en ligne qui ont assuré le succès inespéré du format et les utilitaires de conversion abondent, payants ou non: de KML vers GeoJSON (et vice-versa) et  de (ce que vous voulez) vers GeoJSON alors qu'avec ogr2ogr:

ogr2ogr -f "GeoJSON" -lco votre.geojson votre.shp

Son adoption récente par ESRI dans ArcPy (à ne pas confondre avec l' ESRI Json de l'API REST) lui apportera sans doute une certaine reconnaissance « officielle »:

import arcpygeojson_point = {"type": "Point", "coordinates": [50.0, 75.0]}point = arcpy.AsShape(geojson_point)

petit emprunt à Shapely...

from shapely.geometryimport shapegeojson_point = {"type": "Point", "coordinates": [50.0, 75.0]}point = shape(geojson_point)print pointPOINT (50.0075.00)

Mais il faut garder en mémoire son objectif: pas de styles ou d'autres fioritures ici, ce n'est qu'un format d'échange qui se veut universel, comme l'est le format WKT, mais bien plus complet et plus souple à utiliser.

GeoJSON is not Lat, Lng
    To match OGR
    To match KML
    It surprises the naive
    It annoys the expert
    It is what it is
The Web loves JSON because it is
    minimal, portable, textual
    self-describing
    a subset of JavaScript
    (and other languages with Map literals)
The Little Format that Could
    GeoJSON gets many things right
    It gets some things wrong
    Requirements, opinions, and tastes differ
    GeoJSON is flexible and evolvable
    It's been a valuable experiment in standards making

GeoJSON is Spectacularly Wrong,Yet Somehow Right Enough.

Les gens de chez Safe Software (FME) ne se sont pas trompés en résumant la situation dans Spatial Data Formats: ‘Esperanto’ vs ‘Lingua Franca’ et en adoptant dès le début le format (d'où le titre de l'article).


Site officiel :Le NoSQL dans le domaine géospatial : MongoDB avec JavaScript ou Python, ArcGIS et Quantum Gis
Site officiel :Le NoSQL dans le domaine géospatial : CouchDB et GeoCouch (Couchbase), shp2geocouh, importation de shapefiles et serveur cartographique
Site officiel :Python: traitement des couches vectorielles dans une perspective géologique, lecture et enregistrement des couches sous forme de dictionnaires avec le module Fiona


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Partage des Conditions Initiales à l'Identique Commerciale 2.0 France


OGR: que la force soit avec les formats virtuels

$
0
0
Niveau Intermédiaire
Logiciels utilisésOGR
Plateforme Windows | Mac | Linux | FreeBSD

Beaucoup de Sigistes connaissent ou du moins ont entendu parler du format virtuel de GDAL (GDAL Virtual Format Tutorial ) pour les fichiers de type raster et de ses multiples possibilités (The power of GDAL virtual formats ou Image Mosaicking with GDAL), mais il me semble que peu d'entre eux connaissent ce même format pour les couches vectorielles.

Principes:

Les caractéristiques sont exposées dans OGR Virtual Format. Tout comme celui pour les rasters, il s'agit d'un simple fichier au format XML. Ce fichier VRT (.vrt) se traite comme n'importe quel  fichier vectoriel (fichier shapefile, etc.):

<OGRVRTDataSource>
    <OGRVRTLayer name="points">
        <SrcDataSource>/chemin/points.csv</SrcDataSource>
        <GeometryType>wkbPoint</GeometryType>
        <LayerSRS>EPSG:31370</LayerSRS>
        <GeometryField encoding="PointFromColumns" x="x" y="y"/>
    </OGRVRTLayer>
</OGRVRTDataSource>

Dans le cas présent, il s'agit d'un simple fichier CSV qui pourrait être ouvert avec un logiciel SIG comme QGIS plus simplement. Mais, il est possible d'aller plus loin comme:

  • la création directe d'un fichier 3D, non reconnu come tel par QGIS, mais bien par des logiciels comme GRASS GIS (toujours à partir d'un simple fichier CSV):
<OGRVRTDataSource>
    <OGRVRTLayer name="points">
        <SrcDataSource>/chemin/points3D.csv</SrcDataSource>
        <GeometryType>wkbPoint25D</GeometryType>
        <LayerSRS>EPSG:31370</LayerSRS>
        <GeometryField encoding="PointFromColumns" x="x" y="y" z="z"/>
    </OGRVRTLayer>
</OGRVRTDataSource>
  • le changement de système de projection de la couche (depuis la version 1.10 de GDAL):
<OGRVRTDataSource>
    <OGRVRTWarpedLayer>
        <OGRVRTLayer name="test">
            <SrcDataSource>/chemin/test.shp</SrcDataSource>
        </OGRVRTLayer>
        <TargetSRS>EPSG:4326</TargetSRS>
    </OGRVRTWarpedLayer>
</OGRVRTDataSource>
  • la redéfinition des noms et des formats des colonnes (depuis la version 1.7 de GDAL);
  • le découpage d'une couche en fonction d'un polygone (depuis la version 1.7 de GDAL, voir plus loin);
  • la possibilité d'utiliser des indexes spatiaux;
  • la possibilité de charger plusieurs couches à la fois (comme avec GDAL);
  • la possibilité de découper une couche en fonction des attributs;
  • ....

De plus, tous les types de support peuvent être traités: fichiers textes, fichiers shapefiles, bases de données, base de données spatiale, avec ODBC, etc.

avec une base SQLite non spatiale:

<OGRVRTDataSource>
    <OGRVRTLayer name="Formations">
        <SrcDataSource>/chemin/test.sqlite</SrcDataSource> 
        <SrcLayer>Form</SrcLayer> 
        <GeometryType>wkbPoint</GeometryType>
            <LayerSRS>EPSG:31370</LayerSRS>
        <GeometryField encoding="PointFromColumns" x="lng" y="lat"/> 
    </OGRVRTLayer>
</OGRVRTDataSource>

avec un fichier dbf:

<OGRVRTDataSource>
    <OGRVRTLayer name="Formations">
        <SrcDataSource>/chemin/test.dbf</SrcDataSource> 
        <GeometryType>wkbPoint</GeometryType>
        <LayerSRS>EPSG:31370</LayerSRS>
        <GeometryField encoding="PointFromColumns" x="lng" y="lat"/> 
    </OGRVRTLayer>
</OGRVRTDataSource>

avec un fichier comprenant une géométrie au format WKT (voirGDAL and OGR Virtual Format, reading *.csv file and creating shapefile of lines):

<OGRVRTDataSource>
       <OGRVRTLayer name="Linestest">
           <SrcDataSource SEPARATOR="SEMICOLON">/chemin/wkt.csv</SrcDataSource>
           <SrcLayer>wkt</SrcLayer>
           <GeometryType>wkbLineString</GeometryType>
           <GeometryField encoding="WKT" field="lignes"/>
       </OGRVRTLayer>
</OGRVRTDataSource>

avec des données provenant de ArcSDE (voir Connecting to ArcSDE databases)

<OGRVRTDataSource>
       <OGRVRTLayer name="layer name in qgis"> 
           <SrcDataSource>SDE:servername,5151,,user,pwd,SCHEMA.TABLENAME,SDE.DEFAULT</SrcDataSource> 
           <SrcLayer>SCHEMA.TABLENAME</SrcLayer> 
           <GeometryType>wkbMultiPolygon</GeometryType> 
           <LayerSRS>+proj=tmerc +lat_0=39.66666666666666 +lon_0=-8.131906111111112 +k=1 +x_0=200180.598 +y_0=299913.01 +ellps=intl +units=m +towgs84=-223.237,110.193,36.649 +no_defs</LayerSRS>
       </OGRVRTLayer> 
</OGRVRTDataSource>

Je ne continuerai pas avec les possibilités basiques du format pour me focaliser sur la partie la plus intéressante, les traitements avec des requêtes SQL.

Requêtes SQL

Depuis la version 1.10 de GDAL, les commandes sont de 2 types:

OGR SQL

En introduction, commençons par un cas simple qui pourrait être réglé par le logiciel SIG mais que nous traiterons ici avec un fichier virtuel. Il s'agit d'une carte géologique dont je veux extraire le tracé d'une formation (BUR ici) particulière. J'utiliserai QGIS pour visualiser les résultats.

Le fichier vrt est alors le suivant:

<OGRVRTDataSource>
    <OGRVRTLayer name="BUR Burnot">
        <SrcDataSource>/chemin/varform.shp</SrcDataSource>
        <SrcSQL>SELECT * from varform WHERE "VAR_FORM" = "BUR"</SrcSQL>
    </OGRVRTLayer>
</OGRVRTDataSource>

Et le résultat est:

En pratique, tous les exemples illustrés dans SQL dans OGR peuvent être utilisés.

  • découper une zone d'un shapefile en fonction d'un polygone;
<OGRVRTDataSource>
    <OGRVRTLayer name="clip">
        <SrcDataSource>/chemin/varform.shp</SrcDataSource>
        <SrcRegion clip="true">POLYGON((170504.41570972 116282.08448781,170576.34282934 113909.22348016,175203.46747074 114069.66549297,175169.19847681 116087.90004614,170504.41570972 116282.08448781))</SrcRegion>
    </OGRVRTLayer>
</OGRVRTDataSource>

Résultat:

  •  il y a aussi moyen d'effectuer des opérations géométriques simples comme l'union entre 2 couches:

Le fichier est alors le suivant (union des BUR entre les 2 couches):

<OGRVRTDataSource>
	<OGRVRTUnionLayer name="union_couche_BUR">
		<OGRVRTLayer name="couche1">
			<SrcDataSource>/chemin/biesm.shp</SrcDataSource>
			<SrcSQL>SELECT * from biesm WHERE "VAR_FORM" = "BUR"</SrcSQL>
		</OGRVRTLayer>
		<OGRVRTLayer name="couche2">
			<SrcDataSource>/chemin/goz.shp</SrcDataSource>
			<SrcSQL>SELECT * from goz WHERE "VAR_FORM" = "BUR"</SrcSQL>
		</OGRVRTLayer>
	</OGRVRTUnionLayer>
</OGRVRTDataSource>

Résultat:

  • quelques autres fonctions sont aussi disponibles comme la sélection en fonction des surfaces/aires: 
<OGRVRTDataSource>
    <OGRVRTLayer name="surfaces">
        <SrcDataSource>/chemin/varform.shp</SrcDataSource>
        <SrcSQL>SELECT * FROM varform  WHERE OGR_GEOM_AREA > 10000000</SrcSQL>
    </OGRVRTLayer>
</OGRVRTDataSource>

Résultat:

SQLite SQL dialect

Ce nouveau format, apparu dans la version 1.10 de GDAL, permet d'utiliser le SQL utilisé par SQLite (SQL As Understood By SQLite), mais aussi toutes les fonctions spatiales de SpatiaLite (SpatiaLite 4.0.0: SQL functions reference list ).

A titre de démonstration, nous allons appliquer plusieurs traitements à une même couche (simplification, zone tampon, enveloppe convexe (Convex hull):

<OGRVRTDataSource>
    <OGRVRTLayer name="BUR">
        <SrcDataSource>/chemin/bur.shp</SrcDataSource>
    </OGRVRTLayer>
    <OGRVRTLayer name="BUR_simplifié">
        <SrcDataSource>/chemin/bur.shp</SrcDataSource>
        <SrcSQL dialect="sqlite">SELECT Simplify(geometry,10) from bur</SrcSQL>
    </OGRVRTLayer>
    <OGRVRTLayer name="BUR_tanpon">
        <SrcDataSource>/chemin/bur.shp</SrcDataSource>
        <SrcSQL dialect="sqlite">SELECT Buffer(geometry,200) from bur</SrcSQL>
    </OGRVRTLayer>
    <OGRVRTLayer name="BUR_convexhull">
        <SrcDataSource>/chemin/bur.shp</SrcDataSource>
        <SrcSQL dialect="sqlite">SELECT ConvexHull(geometry) from bur</SrcSQL>
    </OGRVRTLayer>
</OGRVRTDataSource>

L'ouverture de cette couche vrt dans QGIS conduit au dialogue suivant:

Et en sélectionnant toutes les couches, le résultat est:

Attention, tout n'est pas possible. Ainsi si je veux extraire les limites de polygones (ExteriorRing()), cela ne marchera pas s'il y a plusieurs polygones dans une couche:

<OGRVRTDataSource>
    <OGRVRTLayer name="multipolygones">
        <SrcDataSource>/chemin/bur.shp</SrcDataSource>
        <SrcSQL dialect="sqlite">SELECT ExteriorRing(Geometry) from bur</SrcSQL>
    </OGRVRTLayer>
    <OGRVRTLayer name="polygone">
        <SrcDataSource>/chemin/bur1.shp</SrcDataSource>
        <SrcSQL dialect="sqlite">SELECT ExteriorRing(Geometry) from bur1</SrcSQL>
    </OGRVRTLayer>
</OGRVRTDataSource>

 

Seul le deuxième résultat donnera une couche valide. L'autre ne fournira qu'une table non spatiale:

Ceci veut dire que l'on peut parfaitement se servir de ces formats virtuels pour créer des tables non spatiales à sa guise (pour les jointures, etc.).

À quoi cela peut-il bien servir ?

Tout ça, c'est intéressant, me direz-vous, mais à quoi cela peut-il servir en pratique puisque tout peut être fait par un logiciel SIG ?

Cela permet de « bidouiller » ce que l'on veut à partir de couches vectorielles avant traitement dans un logiciel SIG ou pour les traitements sans logiciel SIG:

Avec GDAL/OGR seul:

  • transformation directe en un autre format:
ogr2ogr -f KML -t_srs EPSG:4326 test.kml test.vrt
gdal_grid -zfield "z" -a invdist:power=2.0:smoothing=1.0 -txe 85000 89000 -tye 894000 890000 *-outsize 400 400 -of GTiff -ot Float64 -l dem test.vrt test.tiff

 Avec Python:

Avec d'autres programmes:

Conclusions

Dans la série « Comment traiter les données SIG sans logiciel SIG», ce format remporte certainement la palme.

Tous les traitements ont été réalisés sur Mac OS X avec la version 1.10 de GDAL de KyngChaos

Site officiel :GDAL/OGR


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

QGIS 2.0 Script pour exporter un "atlas par valeur unique"

$
0
0
Type : script python pour scriptrunner
Licence : GPL

Suite à une discussion sur le forum, j'ai retrouvé le code d'un plugin python QGIS que j'avais commencé mais jamais fini.
Après quelques adaptations ,je fournis ici un script (QGIS >= 2.0) pour réaliser un export de mise en page par valeur unique d'une couche.
Ce script s'utilise avec l'extension script runner (cf. l'article de gene sur le portail).

Réglages et particularités

  • La couche utilisée est celle sélectionnée dans le contrôle des couches.
  • Le dossier de sortie des exports est le sous dossier (par rapport au projet qgis) export (à créer avant de lancer le script s'il n'existe pas).
  • Le nom de champ de valeurs uniques utilisé est "UV" (il doit être créé si besoin).
  • Le format de sortie est png.
  • Le composer (mise en page) utilisé est le premier.

Expressions utilisables dans la mise en page

Les expressions suivantes sont utilisables dans les zones de textes de mise en page :

$UV_VALUE()Valeur unique
$UV_PAGE()Numéro de valeur unique courante
$UV_NUM()Nombre de valeurs unique
$UV_NUM_ROWS()Nombre de lignes dans la table attributaires pour la valeur unique courante
$UV_PROJECT_PATH()Chemin du dossier contenant le projet QGis (utile pour créer le chemin d'une image en mode html)
$UV_FIELD(NOM_CHAMP)Affiche la valeur du champ pour cette valeur unique (si plusieurs lignes, la 1ère ligne est prise en compte)
$UV_MAX(NOM_CHAMP)Valeur maximale du champ pour la valeur unique en cours
$UV_MIN(NOM_CHAMP)Valeur minimale du champ pour la valeur unique en cours
$UV_CONCAT(NOM_CHAMP)Concaténation des valeurs du champs pour cette valeur unique

 

Projet exemple

Un projet de test basé sur la couche des départements (IGN® GeoFla) permet de voir ce qu'on peut faire avec le script (notamment avec les expressions de mises en page et le rendu html).

Le script est ici utilisé pour obtenir une carte par région :

exemple


EDIT : mise à jour avec ajout d'une version du script pour QGIS 1.x







Les modules Python à finalités géospatiales: quid, quando, ubi ?

$
0
0
Niveau Débutant
Logiciels utilisésPython
Plateforme Windows | Mac | Linux | FreeBSD

Beaucoup de sigistes commençant à utiliser Python sont un peu déroutés devant la profusion de modules à finalités géospatiales dont ils ont entendu parler et n'en voient souvent pas l'intérêt. Certains sont aussi un peu perdus devant des messages ou des erreurs signalés par leur logiciel SIG lorsqu'ils veulent installer/utiliser une extension:

ImportError: No module named shapely.wkb

« Flute, c'est quoi shapely.wkb ? Ce n'est pas dans les extensions QGIS, vite une question sur forumSIG et/ou sur Georezo et/ou sur GIS StackExchange pour avoir une réponse»

Et non, Shapely n'est pas une extension de QGIS mais un simple module géospatial Python, car en pratique:

  • il est possible d'utiliser Python dans son logiciel SIG en se limitant aux  modules fournis (ArcPy, PyQGIS, grass, ...);
  • il est possible d'utiliser Python sans logiciel SIG (avec les modules géospatiaux);
  • il est aussi possible d'utiliser un module géospatial tiers dans son logiciel SIG.

Le problème est qu'il y a un très grand nombre de modules Python à finalités géospatiales (voir Pypi:GIS) et que si je vais sur GitHub par exemple, il s'en crée un presque tous les jours...

À l'occasion des 10 ans du forumSIG et puisque j'ai commencé sur le Portail par là (Python - Interrogation d'un service cartographique ), je vais essayer de les classer de manière pratique:

« quel module utiliser si je veux... »

en me limitant aux fondamentaux, c'est-à-dire les plus utilisés à l'heure actuelle. Il y a quelques constantes cependant:

  • la plupart d'entre eux sont liés plus ou moins directement au  principal module scientifique sur Python: Numpy (soit basé sur, soit compatible avec le type de données de numpy, les arrays). Cela garantit la suite des traitements possibles;
  • la classification peut être redondante dans le sens où un même module peut effectuer beaucoup de choses alors qu'un autre est limité à un seul traitement (par exemple, PySAL peut lire nativement un fichier shapefile et le traiter de même que matplotlib.basemapCartopy et  mapnik peuvent aussi le lire nativement et le représenter graphiquement);
  • la documentation, surtout francophone, est comment dire, hum.... aux abonnés absents.
  • hormis exception, je ne soulignerai ici que l'utilisation du module sur le Portail (je sais, je sais, c'est réducteur, voire de l'auto promotion, mais comme ça je ne vexe personne et puis c'est la fête du Forum/Portail après tout...).

Il y a cependant un module « universel » dans le sens où il peut presque tout faire:

le module osgeo

  1. c'est la version Python de la  librairie C/C++ GDAL/OGR;
  2. il est subdivisé en plusieurs sous-modules, osgeo.ogr, osgeo.gdal, et osgeo.osr dont la signification sera donnée dans la suite;
  3. il est compatible avec Numpy;
  4. il est relativement difficile d'accès pour celui qui débute en Python.

Au milieu des nombreux tutoriels à son sujet, je ne pourrai pas passer sous silence:

Protocole de standardisation: la geo_interface

Avant de commencer, signalons qu'un protocole de standardisation a éte proposé par Sean Gillies. Il est basé sur le format GeoJSON et est de plus en plus disponible dans les modules géospatiaux (dont ArcPy et PyQGIS), voir:

Je signalerai donc chaque fois qu'il est disponible.

Les modules basiques: je voudrais lire et écrire des données géospatiales

Ils sont fondamentaux, car sans possibilité de les lire... La majorité des autres modules de traitements  se basent donc sur ceux-ci.

je veux lire ou écrire des données provenant de fichiers vectoriels

le module osgeo.ogr:

  1. Il permet de lire et d'écrire tous les formats vectoriels supportés par la librairie ( OGR: Vector Formats);
  2. il y a facilement moyen d'implémenter la geo_interface;
  3. il permet aussi de faire de nombreux autres traitements.

Il a été abordé sur le Portail dans:

le module Fiona

C'est le petit dernier qui monte:  c'est un habillage du module  osgeo.ogr  pour le rendre plus « pythonesque », on ne manipule que des dictionnaires Python, suivant l'adage de Sean Gillies, son auteur, que le traitement des données spatiales n'a rien de particulier vis-à-vis de celui des autres données.

  1. son but est de lire et écrire les formats supportés par OGR (fichiers);
  2. il est directement basé sur la librairie GDAL/OGR et non sur le module osgeo.ogr;
  3. il est compatible avec Numpy et dispose de la geo_interface;
  4. une fois qu'on a compris sa logique, il est très facile à utiliser.

Il a été présenté sur le Portail dans:

le module pyshp (shapefile)

Il est écrit en pur Python (aucune dépendance).

  1. il ne traite que la lecture et l'écriture des fichiers shapefile;
  2. il n'a rien à voir avec osgeo ni Numpy;
  3. il dispose de la geo_interface depuis ses dernières versions;
  4. c'est le plus facile à utiliser lorsqu'on débute en Python.

Il a été abordé sur le Portail dans

Autres

D'autres modules cités dans la suite pour autre chose permettent aussi de le faire nativement (comme PySAL, matplotlib.basemap  ou mapnik). Il en existe aussi d'autres, abordés sur le Portail (Python et les Shapefiles), mais ils sont de plus en plus anecdotiques.

je veux lire ou écrire des données provenant de tables spatiales (SGBD, NoSQL)

Encore une fois, le maître choix pour lire/écrire les données spatiales est le module osgeo.ogr:

le module osgeo.ogr

  1. il dispose des pilotes (drivers) pour traiter la majorité des bases de données spatiales.
  2. cela veut dire qu'il peut être utilisé seul.

le module psycopg2

  1. C'est le module le plus utilisé pour lire et écrire les données de  PostgreSQL/PostGIS.
  2. Il y en a d'autres (modules pour traiter les données PostgreSQL), mais beaucoup moins populaires.

le module sqlite3

  1. c'est le module pour accéder à aux bases SQLite. Il est livré en standard dans les distributions de Python

le module pyspatialite

  1. il est, comme son nom l'indique, destiné à traiter les données des bases SQLite/Spatialite
  2. il y a moyen de s'en passer en utilisant le module précédent (voir SQLite - SpatiaLite: le pourquoi du comment).

autres

Il y a, bien entendu, des modules pour traiter toutes les bases spatiales et même les bases NoSQL

Ils ont été abordés sur le Portail:

je veux lire des données provenant d'un service WFS

Ici encore, il faut utiliser le module osgeo.ogr.

je veux lire ou écrire des fichiers de type raster ou provenant de services WMS ou ...

Ici, il y a théoriquement du choix avec les modules traditionnels qui permettent de lire les fichiers images (comme Python Imaging Library ou PIL« forkée » dans Pillow) mais la solution la plus appropriée est

le module osgeo.gdal.

  1. Il permet de lire tous les formats supportés dans GDAL Raster Formats;
  2. il tient compte du géoréférencement de l'image.

Les quelques autres modules restent marginaux.

je veux lire des données « spécifiques »

Il y a aussi des modules plus spécialisés et non liés aux précédents qui permettent de lire des formats plus spécifiques, comme le module officiel d'OpenStreetMap OsmApi, mais il y en a d'autres (Pypi: OpenStreetMap) ou ceux qui permettent de lire directement des fichiers KML.

je veux m'occuper des projections de mes données

Je peux le faire avec les modules osgeo.osr ou pyproj. Le sujet a été traité sur le Portail:

Je voudrais traiter les données

Une fois que les données ont été lues, le monde Python offre un vaste choix pour les traiter. Il faut distinguer ici ceux qui permettent de traiter les géométries vectorielles, ceux qui permettent de traiter les données matricielles (rasters) et ceux qui veulent tout traiter.

je veux traiter les géométries vectorielles

le module Shapely

  1. il est basé sur la librairie C++ GEOS et permet, schématiquement, d'effectuer tous les traitements géométriques « à la PostGIS » en Python;
  2. il ne lit pas et n'écrit pas les données et ne s'occupe pas des projections.
  3. mais comme la geo_interfaceest implantée, il n'y a aucun problème pour utiliser osgeo.ogr, Fiona, pyshp ou PyQGIS pour suppléer à ce problème;
  4. pyproj peut être utilisé pour les projections avant/après traitement;
  5. intialement limité au plan cartésien 2D, ses dernières versions permettent d'effectuer des translations, cisaillements ou rotations en 2D et 3D;
  6. il est directement compatible avec Numpy;

Il existe une version très simplifiée de Shapely ( "shapely ultralight" ), le module PyGeoIf, destiné à fournir une geo_interfaceà d'autres modules qui en sont dépourvus (comme cela a été le cas dans pyshp).

Sur le portail, il a été abordé dans

le module osgeo.ogr

  1. les  traitements sont aussi disponibles, mais ils sont  beaucoup plus complexes à utiliser.
  2. il a l'avantage d'être indépendant pour lire et écrire les données.

autres

Les géométries étant récupérées en format Python, il est donc possible d'utiliser tous les autres modules comme Numpy ou Scipy pour effectuer des traitements spécifiques (TIN, Voronoï, etc.). Il existe d'autres modules plus spécialisés que je n'aborderai pas ici.

Sur le portail, l'utilisation de  ces modules a été traitée dans:

je veux faire de la statistique géospatiale

le module PySAL

  1. il permet une multitude de traitements de statistiques géospatiales;
  2. il permet aussi de lire et d'écrire des fichiers shapefiles;
  3. il est totalement intégré avec le module Shapely (Using PySAL with Shapely for GIS Operations);
  4. il est compatible avec Numpy et dispose de la geo_interface.

je veux traiter les données matricielles (rasters)

Ici, vous avez l'embarras du choix: comme déjà signalé, il y une multitude de modules permettant de traiter ce type de fichier (dont Numpy) mais il n'y a que le module osgeo.gdal qui tient compte du géoréférencement des images. Il y a quelques autres modules plus spécialisés, basés sur ce dernier.

le module osgeo.gdal

  1. il possède d'origine une multitude de traitements disponibles;
  2. le passage vers un format Numpy est enfantin.

les modules Numpy et Scipy

  1. Puisque les fichiers rasters ne sont que des matrices de pixels, il est aussi possible de les utiliser directement avec leurs très grands nombres de traitements disponibles.

les modules dérivés:

Tous les autres modules sont basés sur ces derniers comme:

- le module Python raster stats, basé sur Numpy et  osgeo.gdal. Il permet de faire des statistiques zonales

- le module Pyresample est basé sur Scipy. Il est destiné à reprojeter les rasters sur base des kd-tres

je voudrais représenter mes données sans logiciel SIG:

Puisqu'elles ont été transformées en objets Python, il est théoriquement possible d'utiliser n'importe quel module qui permet de le faire ( Pypi: Plotting ) et il y en a beaucoup...

Dans les faits, il n'y en a que quelques-uns qui ressortent:

le module matplotlib

  1. c'est le module graphique le plus complet et le plus utilisé;
  2. sa finalité est scientifique (à la Matlab);
  3. il permet la représentation 2D ou 3D des données vectorielles ou matricielles;
  4. il dispose aussi de nombreux traitements propres;
  5. il est compatible avec Numpy.
  • il a été abondamment utilisé sur le Portail.

le module matplotlib.basemap

  1. c'est une extension de matplotlib (Matplotlib Basemap Toolkit) qui permet de représenter des données géospatiales munies d'une projection sur une carte (2D) ou un globe (mais toujours en 2D);
  2. il permet aussi de lire nativement les shapefiles
  • il n'a pas été abordé sur le Portail mais comme il est important dans le monde universitaire, je me dois de citer une des meilleures séries de tutoriels que je connaisse: celle du sismologue belge Thomas Lecocq à Tutorials

 module Descartes

  1. c'est une extension de matplotlib qui permet de mieux représenter les surfaces des objets géométriques (2D)
  2. il dispose de la geo_interface

module Cartopy

  1. il se veut une solution intégrée puisqu'il se propose de lire les données vectorielles avec le module pyshp, de les traiter avec les modules Shapely et Numpy et de les représenter avec le module matplotlib (2D);
  2. il gère les projections de manière non standard avec sa propre implémentation basée sur la librairie PROJ4 (Cartopy projections);
  3. les seuls rasters qu'il peut figurer sont ceux du serveur de tuiles Mapquest;
  4. ils travaillent sur la possibilité de représenter les données SRTM (Shuttle Radar Topography Mission) et GSHHS (Global Self-consistent, Hierarchical, High-resolution Shoreline).

module mapnik

Mapnik est un monde en soi dont le seul but est, caricaturelement, de créer les plus belles représentations cartographiques possibles (Mapnik is about making beautiful maps). Il utilise le module Pycairo et sait aussi lire nativement les shapefiles. La geo_interface. peut facilement être implémentée.

module Mayavi

  1. c'est LE module de représentation scientifique 3D;
  2. il est basé sur la librairie VTK et permet de représenter toutes les données vectorielles et matricielles en 3D;
  3. il offre aussi une gamme importante de traitements propres;
  4. il est, bien évidemment, compatible avec Numpy
  5. il est relativement complexe à utiliser.

autres

Il y a toujours moyen d'utiliser d'autres modules comme Visvis (3D) mais c'est moins fréquent.

Conclusions

Oufti, j'espère vous avoir apporté quelques éclaircissements qui vous permettront de choisir le module adéquat ou l'enchaînement de modules adéquat pour faire ce que vous voulez en Python, avec ou sans logiciel SIG (et Shapely ou matplotlib sont bien utilisés par des extensions de QGIS...)

Site officiel :Python


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

PyQGIS (QGIS 2): les coupes géologiques (colorisation d'un profil topographique élaboré à partir d'un MNT en fonction des couleurs d'un raster et placement des points d'intersection avec les limites des couches géologiques)

$
0
0
Niveau Intermédiaire
Logiciels utilisésQGIS 2.0
matplotlib (module Python)
Plateforme Windows | Mac | Linux | FreeBSD

Pour les géologues, créer une coupe géologique à partir d'un logiciel SIG classique est un problème récurrent, surtout lorsque il ne dispose pas d'une application qui gère la 3D (comme GRASS GIS ou ArcGIS avec des extensions très chères). Il s'agit de:

1) créer un profil topographique en fonction d'une ligne de coupe définie et d'un MNT.

  • il y a des extensions/plugins QGIS pour le faire comme Profile Tool (qui utilise le module Python matplotlib, comme d'autres extensions);
  • il y a des commandes avec GRASS GIS ou SAGA GIS seuls ou dans la Toolbox du menu « Traitements » (ex extension Sextante, maintenant intégrée dans QGIS);
  • il y a bien entendu moyen de combiner divers logiciels;
  • cela n'aura jamais la souplesse désirée alors qu'il y a moyen de faire exactement ce que l'on veut en Python (voir plus bas).

2) colorier le profil en fonction des couleurs d'une carte et/ou adjoindre les points d'intersection entre les limites géologiques et cette ligne.

Cela a été présenté sur le Portail dans:

 

3) reporter les valeurs de pendages apparents des couches géologiques sur l'axe de la coupe (calculés en fonction de l'angle entre la direction de la coupe  et la direction de la couche, et éventuellement de l'exagération des hauteurs).

  • cela a été présenté sur le Portail en Python dans les articles cités

4) dessiner la coupe par interprétation géologique.

  • il est toujours plus rapide de le faire à la main;
  • cela est presque impossible à mettre en oeuvre sans un logiciel spécialisé;
  • j'essaie de le faire par la méthode des kinks (Construct a Fold Cross-Section Using the Kink Method) mais c'est une espèce de défi personnel...

5) utiliser la méthode de « down-plunge profile »

figure reprise de Steve Dutch dans Construct a Down-Plunge Profile

  • cela a été présenté sur le Portail en Python dans les articles cités.

 

Il m'a souvent été demandé comment porter les scripts Python pour le faire directement dans QGIS (collègues ou sur le ForumSIG dans [QGIS 2.x] Coupe geologique avec qgis2). J'avais utilisé les modules Python osgeo (GDAL/OGR), Shapely et Fiona dans les scripts originaux du fait des « lacunes » de la précédente version de PyQGIS et d'un manque de souplesse. Mais depuis le nouvel API (Qgis 2 : changement de l'API python) la tâche a été grandement facilitée puisque certaines fonctions ont été ajoutées et les traitement simplifiés (sans la « pollution » de PyQt4).

Si vous n'aimez pas la console Python et n'avez pas le courage de vous atteler à la confection d'une extension, le nouveau menu « Traitements »  permet de créer très facilement des scripts dans la Toolbox avec une interface simple et de les exécuter avec un clic:

Principes

Tout d'abord le script de traitement des couches complet, très court, et qui sera détaillé dans la suite, sans:

  • la manière de sélectionner les données (4 couches suffisent, une ligne de coupe, les limites des couches géologiques, un MNT et un fichier raster: une carte géologique), qu'il suffit d'ajouter en fonction d'un traitement dans la console ou de la création d'un script pour la Toolbox;
  • la création des figures, qu'il suffit d'ajouter en fonction de vos besoins.

traitement_couches.py

  1. # traitement de la ligne de coupe et intégration des segments éventuels (=UNION)
  2. geomcoupe = QgsGeometry.fromWkt('GEOMETRYCOLLECTION EMPTY')
  3. for elem in coupeori.getFeatures():
  4. geomcoupe = geomcoupe.combine(elem.geometry())
  5. # longueur de la ligne de coupe
  6. longueur=geomcoupe.length()
  7. # traitement des limites des couches géologiques (polylines)
  8. limites = QgsGeometry.fromWkt('GEOMETRYCOLLECTION EMPTY')
  9. for elem in lim.getFeatures():
  10. limites = limites.combine(elem.geometry())
  11.  
  12. # valeur d'un pixel sous un point
  13. def Val_raster(point,raster):
  14. return raster.dataProvider().identify(point, QgsRaster.IdentifyFormatValue).results().values()
  15.  
  16. # calcul des des valeurs sur la ligne de coupe
  17. x,y,z,couleur,dista = map(list,zip(*[(geomcoupe.interpolate(distance).asPoint().x(), geomcoupe.interpolate(distance).asPoint().y(),Val_raster(geomcoupe.interpolate(distance).asPoint(),MNT)[0], Val_raster(geomcoupe.interpolate(distance).asPoint(),carte_geol),distance)for distance inxrange(0,longueur,20)]))
  18.  
  19. # intersection entre la la ligne de coupe et les limites des coupes
  20. ptintersec = geomcoupe.intersection(limites)
  21. # calcul des valeurs des points d'intersection sur la ligne de coupe
  22. origine = geomcoupe.interpolate(0)
  23. xd,yd,zd,distd = map(list,zip(*[(point.x(),point.y(),Val_raster(point,MNT)[0],QgsGeometry.fromPoint(point).distance(origine))for point in ptintersec.asMultiPoint()]))

Pour ce faire, nous reprendrons le plan général de la version en Python seul, de la manière la plus simple possible et sans toutes les fioritures qui complexifient la tâche dans les tutoriels sur PyQGIS. Comme vous pouvez le voir, il y a moyen de le faire en un minimum de lignes de code avec les fonctions de programmation fonctionnelle comme map() ou les compréhensions de liste (voir La compréhension de liste en Python, une syntaxe moderne pour map() et filter()). Quelques exemples de traitements, utilisés dans le script présenté, seront signalés en italique.

Comme je n'ai pas développé l'aspect graphique (avec le module matplotlib) dans les scripts originaux, il sera, cette fois-ci, détaillé.

Accéder aux couches vectorielles ou matricielles

  • dans la console Python

Le parcours des éléments (géométrie et attributs) est direct, sans sélection préalable:

macouche = qgis.utils.iface.activeLayer()for elem in macouche.getFeatures():     geom= elem.geometry()     attr =elem.attributes()      (traitements)

Mais il est pénible de devoir sélectionner à chaque fois une couche active pour y accéder dans le script,  il est beaucoup plus pratique d'utiliser un dictionnaire des couches activées sur le canevas:

canvas= qgis.utils.iface.mapCanvas()# sélection des couches affichéescouches = dict((k.name(),i)for(i, k)inenumerate(canvas.layers()))print couches{u'ligne_coupe': 1, u'lim_form': 0, u'MNT': 2, u'carte_geol': 3}

Ceci permet de sélectionner directement une couche par son nom:

coupeori = canvas.layer(couches['ligne_coupe'])# ou/au lieu decoupeori = canvas.layer(1)

La grande différence réside dans le fait que dans le cas de l'API 1.x, le résultat est un itérable (liste, par exemple) alors que dans l'API 2.x, c'est un générateur/itérateur (comme dans Fiona) Quelle est la différence ?

  • l'entièreté de la liste doit être chargée en mémoire avant de la parcourir avec une boucle for, par exemple;
  • dans un générateur, les valeurs sont générées une par une (c'est la seule chose en mémoire, une valeur) et une fois affichée, cette valeur n'existe plus. C'est ce qui permet de traiter les grandes masses de données (voir par exemple Comment utiliser yield et les générateurs en Python ?).

Il est donc parfaitement possible de faire:

coupeori = canvas.layer(couches['ligne_coupe'])elem = coupeori.getFeatures().next()geomcoupe= elem.geometry()....

et d'appliquer la geo_interface, voir dans Les modules Python à finalités géospatiales: quid, quando, ubi ?

  • dans un script de Traitements (processing, ex Sextante)

Il y a ici moyen de créer une interface simple qui permet le choix des couches à traiter et de les récupérer dans le script:

# interface##ligne_coupe=vector##lim_form= vector##MNT=raster##carte_geol= raster#Traitementsfrom qgis.coreimport*coupeori = processing.getobject(ligne_coupe)lim = processing.getobject(lim_form)MNT = processing.getobject(MNT)carte_geol = processing.getobject(carte_geol)

(Attention, dans les versions master 2.1, il s'agit de .getObject)

Ceci permet, lorsqu'on clique sur le bouton d'obtenir le dialogue suivant:

Comment extraire la valeur d'un pixel d'un raster sous un point

PyQGIS offre maintenant une solution beaucoup plus simple que celle vue avec les autres modules pour extraire la valeur d'un pixel sous un point:

# nombre de bandesMNT.bandCount()1Lcarte_geol.geol.bandCount()3L# valeur d'un pixel sous un pointMNT.dataProvider().identify(QgsPoint(229774,111171), QgsRaster.IdentifyFormatValue).results(){1: 221.0}carte_geol.dataProvider().identify(QgsPoint(229774,111171), QgsRaster.IdentifyFormatValue).results(){1: 180.0, 2: 142.0, 3: 125.0}

Le résultat obtenu est un dictionnaire avec comme clé, la bande, et comme valeur, l'élement recherché (altitude dans le cas d'un MNT, couleur RGB dans le cas d'un couche multibandes). Il est donc très facile de réécrire la fonction  Val_raster() établie dans les scripts originaux.

def Val_raster(point,raster):
      return raster.dataProvider().identify(point, QgsRaster.IdentifyFormatValue).results().values()

Comment croiser un fichier de type vectoriel et un fichier de type matriciel pour en extraire les données.

Nous reprenons le script présenté dans les articles cités. Le nouvel API de PyQGIS dispose maintenant, lui aussi, de la fonction interpolate pour générer des points équidistants sur une ligne (il n'y donc plus besoin du module Shapely pour cet aspect). Plus le nombre de points est élévé, plus la représentation sera précise.

# traitement de la ligne de coupe# création d'une couche vide pour regrouper tous les segments# éventuels d'une couche ligne (= UNION)geomcoupe = QgsGeometry.fromWkt('GEOMETRYCOLLECTION EMPTY')# intégration des segments éventuels (=UNION)for elem in coupeori.getFeatures():      geomcoupe = geomcoupe.combine(elem.geometry())#-------------------------------------# puisque c'est un générateur, et dans le cas d'une ligne simple,sans segment,# il n'y a pas besoin de boucleelem = coupe.getFeatures().next()geomcoupe= elem.geometry()longueur = geomcoupe.length()#-------------------------------------# création des points équidistants sur la ligne avec un pas de 20 mlongueur=geomcoupe.length()x = []y = []z = []# couleur RGBcouleur = []# distance pour le profil topographiquedista = []for distance inxrange(0,longueur,20):         # création du point sur la ligne         point = geomcoupe.interpolate(distance)         point = point.asPoint()         xp,yp=point.x(), point.y()         x.append(xp)         y.append(yp)         # extraction de la valeur altitude à partir du MNT         z.append(Val_raster(point,MNT)[0])         # extraction des couleurs RGB à partir de la couche raster         couleur.append(Val_raster(point,carte_geol))         dista.append(distance)

Ce qui en programmation fonctionnelle peut se faire en une seule ligne:

x, y,z, couleur,dista = map(list,zip(*[(geomcoupe.interpolate(distance).asPoint().x(), geomcoupe.interpolate(distance).asPoint().y(),Val_raster(geomcoupe.interpolate(distance).asPoint(),MNT)[0], Val_raster(geomcoupe.interpolate(distance).asPoint(),carte_geol),distance) for distance inxrange(0,longueur,20)]))

Comment obtenir les points d'intersection entre 2 fichiers vectoriels (profil et limites des objets géologiques) et les placer sur la coupe géologique.

L'API 2.x ne dispose pas de la possibilité de calculer directement l'intersection entre 2 couches vectorielles, mais comme dans le script original, en transformant les 2 couches en 2 objets géométriques uniques, il est possible d'utiliser la fonction intersection.

# ouverture de la couche limites = QgsGeometry.fromWkt('GEOMETRYCOLLECTION EMPTY')for elem in lim.getFeatures():     limites = limites.combine(elem.geometry())# points d'intersection entre les 2 couchesptintersec = geomcoupe.intersection(limites)

Il faut maintenant les placer sur la coupe géologique (distance, z). Cela se fait en calculant la distance des points à partir du point d'origine de la coupe  avec la fonction point.distance(point) (puisqu'ils sont sur la ligne de coupe).

# origine de la coupeorigine = geomcoupe.interpolate(0)# x,y si représentation 3D souhaitéexd = []yd = []zd = []distd = []for point in ptintersec.asMultiPoint():         xd.append(point.x())         yd.append(point.y())         zd.append(Val_raster(point,MNT)[0])         distd.append(QgsGeometry.fromPoint(point).distance(origine))

Ce qui en programmation fonctionnelle peut se faire en une seule ligne:

xd, yd,zd,distd = map(list,zip(*[(point.x(),point.y(),Val_raster(point,MNT)[0],QgsGeometry.fromPoint(point).distance(origine)) for point in ptintersec.asMultiPoint()]))

Comment représenter les coupes avec le module matplotlib

C'est ici, apparemment, que se posent le plus de problèmes pour ceux qui ont essayé le portage. Il est vrai que matplotlib est un monde en soi qui permet de tout faire, mais qui n'a rien à voir avec les SIGs. C'est de la représentation graphique pure et il faut bien connaître Python.

Pour la représentation de la ligne de coupe en 3D:

C'est le plus direct, car le script fournit les valeurs x, y et z des points générés le long de la coupe:

from mpl_toolkits.mplot3d.axes3dimport*import matplotlib.pyplotas pltfig = plt.figure()ax = Axes3D(fig)# ligne reliant les points, de couleur rougeax.plot3D(x,y,z, c='r')plt.show()

Résultat (il y a moyen de faire tourner le diagramme et de le sauver sous divers formats, dont le SVG, vectoriel):

Si l'on veut utiliser les couleurs de la carte géologique, matplotlib permet de le faire à partir de tuples avec les valeurs R, G, B de chaque point. C'est ce qui a été obtenu dans la liste couleurs, mais matplotlib utilise des valeurs de 0 à 1 et donc:

color = [[j/255.0 for j  in i]for i in couleur]fig = plt.figure()ax = Axes3D(fig)points 3D iciax.scatter3D(x,y,z, c = tuple(color))plt.show()

Résultat:

Avec matplotlib, il n'est pas encore possible de bien contrôler le rapport entre l'échelle des x et celle des y en 3D. Il faut alors passer par un module comme visvis.

import visvisf = visvis.gca()m = visvis.plot(x,y,z, lc='k', ls='', mc='g', mw=2, lw=2, ms='.')# z x 1f.daspect = 1,1,1# z x 10f.daspect = 1,1,10

Pour la coupe 2D:

Ici c'est le couple (distance, z) qui est pris en compte. Pour utiliser les couleurs du raster dans la figuration d'une ligne, je vais donner une couleur à chaque segment basée sur la couleur d'un de ses points limite.

Je le ferai ici avec toutes les enjolivures que je vous laisse découvrir, c'est comme ça que j'ai appris,  à coup d'essais et d'erreurs.

fig = plt.figure()ax = fig.gca()# dessin des segments entre chaque pointfor i inrange(len(dista)-1):      ax.plot([dista[i],dista[i+1]],[z[i],z[i+1]], c=tuple(color[i+1]),linewidth=2)  # enjolivures# échelle de l'axe y x 10ax.set_aspect(10)xrange = [0, 7500]yrange = [200, 400]ax.set_xlim(*xrange)ax.set_ylim(*yrange)ax.set_yticks([200,300,400])ax.set_xlabel('Distance (m)')ax.set_ylabel('Altitude x 10 (m)')ax.set_title('N                                                                    S')plt.show()

Résultat:

Pour placer les intersections avec les limites des formations géologiques

Il suffit de rajouter au script précédent la ligne:

ax.scatter(distd,zd,s=200, marker='|')

qui permettra de dessiner une ligne verticale à la place du point.

Pour placer les angles des pendages

C'est un peu plus complexe et je ne présenterai ici que les principes:

  • tout comme dans les scripts originaux, il faut aller chercher l'angle du pendage dans la table d'attributs;
  • le pendage apparent doit être calculé en fonction de l'angle entre la direction de la couche (table d'attributs aussi) et l'azimut de la ligne de coupe (calcul déjà présenté sur le Portail) et éventuellement de l'exagération des hauteurs;

Ensuite on utilise le principe de représentation des angles de matplotlib:

angles = [0,10,20,30,40,50,60,70,80,90]ax = plt.gca()ax.plot(x,y)for i inrange(len(x)):      ax.scatter(x[i],y[i],s=300, marker=(2,0,(angles[i])))

Résultat final:

Conclusions

Vous savez maintenant:

  • ajouter au début du script traitement_couches.py, la sélection des couches nécessaires, en fonction d'une utilisation dans la console ou de la création d'un script pour la Toolbox;
  • ajouter à la fin du script, la figuration souhaitée  en fonction de vos besoins.

Quels sont les avantages de cette démarche ?

  • le traitement peut être effectué directement dans QGIS, soit dans la console, soit avec un script placé dans la Toolbox du menu « Traitements ».

Quels sont les désavantages de cette démarche  par rapport à un script en Python seul comme ceux présentés auparavant ?

  • la limitation du traitement aux couches activées sur le canevas;
  • les couches provenant d'un WMS ou de GRASS GIS par l'extension GRASS ne sont pas prises en compte lorqu'il s'agit d'extraire une valeur (aucune valeur dans mon cas);
  • vous ne pouvez donc pas aller « piocher » où vous voulez, sans tenir compte de l'origine, comme avec les scripts en Python seul.

Que faudrait-il améliorer/ajouter (surtout dans le cas d'un script dans la Toolbox) ?

  • la gestion des erreurs comme lorsqu'une mauvaise couche a éte choisie, ce qui provoquera une erreur;
  • la possibilité de choisir divers paramètres et le type de représentation désiré;
  • et donc une standardisation de la table d'attributs (pour les valeurs de pendage et de direction);
  • ....

Tous les traitements ont été faits sur Mac OS X avec la version QGIS Dufour de KyngChaos et testés avec succès sur:

  • la versions QGIS Dufour standalone pour Windows;
  • la version QGIS Dufour pour Ubuntu;
  • la version QGIS 2.1 master (attention, il s'agit de .getObject);
  • le module matplotlib fait « planter » QGIS dans la version script Toolbox sur Mac OS X, pas dans la version console (dans mon cas).
Site officiel :QGIS


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

Python + Leaflet = Folium, ou comment créer des cartes interactives simplement.

$
0
0
Niveau Débutant
Logiciels utilisésFolium (module Python)
Python version >= 2.7 (sauf petites modifications)
Plateforme Windows | Mac | Linux

+    =Folium

Vous voulez visualiser directement les résultats de vos traitements Python sur une carte interactive avec Leaflet, alors adoptez Folium de wrobstory (Python 2.7.x, mais il est possible de le faire fonctionner en Python 2.6.x moyennant quelques petites modifications dans les scripts). Le module a déjà été rapidement présenté dans la revue de presse du 24 mai 2013 de GeoTribu (Folium : Python et Leaflet sont dans un bateau). Il utilise les librairies JavaScript Leaflet.js et d3js pour fournir le résultat.

Principes

Pour débuter, rien de plus simple, quelques lignes de code suffisent pour créer un fichier html :

import foliummacarte = folium.Map(location=[50.7517,5.9106], zoom_start=13)macarte.create_map(path='macarte.html')

et en l'ouvrant avec un navigateur, vous avez accès à Leaflet:

Il est possible de remarquer ici:

  • que le fond de carte provient d'OpenStreetMap (par défaut), qu'il est centré sur les coordonnées en WGS84, 50.7517,5.9106 et que j'ai choisi un zoom de départ de 13;
  • d'autres fonds sont possibles (tuiles OpenStreetMap, Stamen Terrain, Stamen Toner, Cloudemade et Mapbox avec une clé, par défaut), ou n'importe quel fond supporté par Leaflet:
tuiles = r'http://{s}.tiles.mestuiles.com/{z}/{x}/{y}.png'

Rajouter un popup montrant les coordonnées en cliquant sur la carte est un jeu d'enfant:

stamen = folium.Map(location=[50.7517,5.9106], tiles='Stamen Toner', zoom_start=13)stamen.lat_lng_popover()stamen.create_map(path='monstamen.html)

Ajouter des données vectorielles

L'intérêt est évidemment l'ajout de données vectorielles sur ce fond. Cela peut se faire de deux manières:

  • avec des simples marqueurs (avec popup ici). Plusieurs sont disponibles, voir Folium: Circle Markers par exemple:
monpoint = folium.Map(location=[50.7517, 5.9106],zoom_start=13)monpoint.simple_marker(location=[50.7517, 5.9106], popup='3290005')monpoint.create_map(path='point.html')

autres marqueurs:

# fichier au format GeoJSONmongeojson = "aachen.geojson"carte = folium.Map(location=[50.63423,5.8303],tiles='Mapbox Bright', zoom_start=12)carte.geo_json(geo_path=mongeojson)carte.create_map(path='aachen.html')

- les polygones:

- ou avec des couleurs en fonction d'une variable (voir plus bas):

 - les polylignes:

Enjolivures

Les nombreuses possibilités sont illustrées sur le site du module et sur le blog de l'auteur (wrobstory’s blocks);

  • combiner l'ensemble avec d'autres modules Python comme vincent, du même auteur, qui offre les capacités de visualisation de JavaScript en Python:

  • et le plus bel exemple est fourni par le travail de François Massot dans réserve parlementaire dont le projet a pour but de croiser différentes données (réserve parlementaire de 2011, données GEOFLA de l'IGN (communes), résultats des élections municipales pour les communes de plus de 3500 habitants et activité des députés) pour analyser les données de la réserve parlementaire.

(petits) inconvénients

Le seul problème que j'ai rencontré est dans la symbologie des données qualitatives. Elle ne peut être obtenue qu'en « trichant » du fait des jointures de Pandas. Je m'explique:

  • pour représenter une carte de couleurs en fonction d'une variable qualitative ("id" ou "CODE" dans le fichier GeoJSON ci-dessous):

  • il n'y a pas moyen d'utiliser directement ce fichier GeoJSON et je suis obligé de passer par une table intermédiaire du type: 

 

  • la classification des couleurs se fait sur ce champ "code" avec la jonction entre cette table et le fichier GeoJSON, avec Pandas. Le résultat est un fichier JSON (créé automatiquement par Folium) qui sera utilisé par d3js pour créer la carte:

import pandas as pdgeo = 'form.geojson'table_sigles = 'sigles.csv'# transformation du fichier csv en dataframe pandassigles = pd.read_csv(table_sigles)map = folium.Map(location=[50.63423,5.8303],zoom_start=13)map.geo_json(geo_path= geo, data=sigles,             columns=['sigle', 'code'], #colonnes de la table csv             key_on='feature.id', # lien avec le fichier GeoJSON             fill_color='YlGn', fill_opacity=0.9, line_opacity=0.4, # couleur pour chaque élément du fichier GeoJSON             legend_name='Sigles')map.create_map(path='cretace.html')

 

Il est aussi encore impossible de créer un popup sur les données GeoJSON (à ma connaissance) comme dans le cas des fichiers GeoJSON placés dans GitHub (voir mon crétacé.geojson), ou plus généralement dans Using GeoJSON with Leaflet: "onEachFeature")

Conclusions

Tout cela ne doit pas masquer le grand intérêt de ce module et sa facilité d'utilisation:

Tous les traitements ont été effectués sur Mac OS X avec Python 2.6 ou Python 2.7

Site officiel :Folium


Creative Commons License
licence Creative Commons Paternité-Pas d'Utilisation Commerciale-Pas de Modification 2.0 France

Viewing all 43 articles
Browse latest View live