Index de l'article

Contenus web

Vous vous rappelez l'encart de texte au sujet du Massif des Écrins, accompagnant la carte que nous avons générée plus haut ? En dur dans le code, nous l'avons volontairement mis de côté au moment de générer nos cartes en série. En effet afficher toujours le même texte dans nos 341 cartes n'avait pas d'intérêt.

Et bien nous allons maintenant enrichir nos cartes avec des contenus dynamiques, en lien avec les données cartographiées, qui iront remplir cet encart de texte. Et nous irons chercher ces contenus sur le web.

Plusieurs méthodes existent, APIs et web-scraping notamment, souvent avec l'utilisation de fichier JSON ou XML.

  • Note personnelle : ici prévoir exposé sur les APIs

Il nous faudra donc faire un lien avec nos données, et ce de façon dynamique bien sûr.

Dans ce chapitre, nous ferons nos tests directement depuis la console Windows, ouvrez donc la console Python dans l'interface de commande Windows (CMD python).

Pip

Petite parenthèse : Pip est un installateur d'extensions pour Python, vous allez sans doute devoir vous y intéresser, et lui-même l'installer pour la suite de ce tutoriel. Je vous laisse faire.

Module requests

Si besoin d'installer requests (dans le shell Windows, pas Python) :

pip install requests

XML OSM

Exemple de lien vers le fichier XML de la base de données OSM, suffixé par l'id OSM (que justement, nous possèdons pour tous nos sommet) :

import requests
 
MyOsmId = '582066938'
r = requests.get("https://www.openstreetmap.org/api/0.6/node/"+MyOsmId)
 
print(r.text)

Notez l'importation nécessaire du module en haut de fichier.

Clé-valeur

Nos sommets n'ont pas de donnée attributaire sur leur altitude. Mais il semble que les XML d'OSM la connaissent ! En effet le fichier contient un tableau associatif avec un ensemble de clé-valeur dont ele (elevation en anglais).

Nous allons parser le fichier XML afin de récupérer l'altitude dans une jolie variable.

Parsing

Grâce à une condition if, nous allons récupérer l'altitude, à condition que le fichier XLM contienne le tag ele, sinon nous n'affichons rien.

import requests
import xml.etree.ElementTree
 
MyOsmId = '582066938'
r = requests.get("https://www.openstreetmap.org/api/0.6/node/"+MyOsmId)
root = xml.etree.ElementTree.fromstring(r.content)
 
for child in root.iter('tag'):
    if child.attrib['k'] == 'ele':
        print (child.attrib['v']+" mètres")

Nous sommes donc capable de récupérer l'altitude, bien, maintenant utilisons-là dans le titre de nos cartes générées :

  1. import datetime
  2. import requests
  3. import xml.etree.ElementTree
  4.  
  5. mylayer = QgsProject.instance().mapLayersByName("peaks")[0]
  6. ...
  7. for feat in mylayer.getFeatures():
  8. ...
  9. # Récupérer altitude sur API OSM
  10. r = requests.get("https://www.openstreetmap.org/api/0.6/node/"+id_peak)
  11. root = xml.etree.ElementTree.fromstring(r.content)
  12.  
  13. for child in root.iter('tag'):
  14. if child.attrib['k'] == 'ele':
  15. altitude = ", "+child.attrib['v']+" mètres"
  16.  
  17. # Titre
  18. title = QgsLayoutItemLabel(layout)
  19. title.setText(layoutName+altitude)
  20. title.setFont(QFont("Verdana", 28))
  21. title.adjustSizeToText()
  22. layout.addLayoutItem(title)
  23. title.attemptMove(QgsLayoutPoint(5, 4, QgsUnitTypes.LayoutMillimeters))
  24. ...

Précédé d'une virgule, notre variable altitude n'a plus qu'à aller se concaténer à la suite de notre variable layoutName dans le titre de nos cartes. Ainsi les enregistrements pour lesquels nous ne connaissons pas l'atitude n'afficheront pas de caractère inopportun à la suite du titre.

  • Note personnelle : on pourrait aller plus loin en ajoutant dans les sources des cartes le nom du contributeur OSM ayant saisi ou modifié l'entité sommitale cible.

Encodage

Il serait bon d'encoder notre caractère spécial è dans le mot mètres, car ici il est affiché en tant que pure chaîne, et dans certains cas spéciaux nous pourrions avoir de mauvaises suprises.

import html
...
            altitude = ", "+child.attrib['v']+html.unescape(" mètres")
...
  • Note personnelle : faire aussi l'encodage du fichier lui-même, et parler de l'encodage des fichiers Python.

Wikidata

Certains sommets possèdent un id Wikidata (dans le champ OTHER_TAGS). Exemple de lien vers donnée Wikidatasuffixé par un id Wikidata :

Pour tester, allez chercher le contenu d'une page page HTML d'osm.wikidata.link :

  1. import requests
  2. r = requests.get("https://osm.wikidata.link/Q726652")
  3. print(r.text)

Un peu lent mais bien fourni. Les page wikidata.org se prêtent tout de même mieux au web-scraping (les contenus y sont mieux organisés, à l'intérieur de jolies balises).

Module requests-html

Si besoin, installez requests-html (dans le shell Windows, pas Python) :

pip install requests-html
Pour une installation à l'université de Cergy (Python 2) :
py -m pip install requests-html

Récupérer des données Wikipédia

Les contenus y sont si bien organisé que nous allons pouvoir récupérer le lien vers la page Wikipédia du sommet, et même cibler celle en français.

Dans la console Python de Windows :

  1. import requests
  2. from requests_html import *
  3.  
  4. session = HTMLSession()
  5.  
  6. MyWikiId = 'Q726652'
  7. r = session.get("https://www.wikidata.org/wiki/"+MyWikiId)
  8.  
  9. my_content = r.html.find('.wikibase-sitelinkview-link-frwiki a', first=True)
  10. #print(my_content)
  11.  
  12. my_link = my_content.xpath('//a/@href')
  13. print(my_link)

 Nous nettoyons ensuite le lien pour le rendre utilisable par le module requests :

  1. import requests
  2. from requests_html import *
  3.  
  4. session = HTMLSession()
  5.  
  6. MyWikiId = 'Q726652'
  7. r = session.get("https://www.wikidata.org/wiki/"+MyWikiId)
  8. my_content = r.html.find('.wikibase-sitelinkview-link-frwiki a', first=True)
  9.  
  10. #print(my_content)
  11. my_link = my_content.xpath('//a/@href')
  12.  
  13. #print(my_link)
  14.  
  15. my_link_string = str(my_link).replace('[', "").replace(']', "").replace("'", "")
  16. print(my_link_string)
  17.  
  18. r2 = session.get(my_link_string)
  19.  
  20. print(r2.text)

Mais c'est finalement les pages Wikipédia dont les contenus ne sont plus suffisament bien organisés... Ce serait sans doute tout de même possible en multipliant les conditions, mais pas très efficace.

La bibliothèque (library) wikipedia de Python

Essayons avec la bibliothèque wikipedia de Python, faîte expressément pour ça !

Si besoin de l'installer (dans le shell Windows, et non Python) :

pip install wikipedia
Pour une installation à l'université de Cergy (Python 2) :
py -m pip install wikipedia

Et maintenant dans la console Python de l'invite de commande Windows :

  1. import wikipedia
  2.  
  3. wikipedia.set_lang("fr")
  4. w = wikipedia.summary("Aiguille Dibona", sentences=3)
  5.  
  6. my_page = wikipedia.page("Aiguille Dibona")
  7.  
  8. #print (my_page.url)
  9. print (w)

Héhé, c'est bien plus simple hein ?

Installation de modules Python dans QGIS

Mais nous avons un problème. En effet la bibliothèque wikipedia de Python n'est pas forcément accessible depuis QGIS, même si vous l'avez déjà installé sur Windows.

Testez la seule importation du module wikipedia depuis la console Python de QGIS pour en avoir le cœur net.

import wikipedia

Cela tient à ce que la version de Python utilisée par QGIS n'est pas exactement la même, puisque sous Windows QGIS embarque sa propre installation de Python.

Pour installer un module tierce, il vous faudra passer par le shell d'OSGeo4W. Suivez ce tutoriel pour installer un module sur la version de Python utilisée par votre QGIS sous Windows.

Dans le shell d'OSGeo4W :

py3_env
python -m pip install wikipedia

Pour une installation à l'université de Cergy (droits administrateur)
python -m pip install wikipedia --user

Une fois la bibliotheque wikipedia installée, les codes précédents exécuté depuis QGIS fonctionneront également.

Bien, nous n'avons plus qu'à l'adapter à notre code de génération de cartes.

Ignorer les exceptions

Nous allons ajouter un mot-clé à notre recherche, pour éviter les contenus sans rapport à certains noms de sommet (recherche textuelle oblige), mais surtout nous mettons la recherche dans un try, afin d'ignorer les erreurs quand la recherche ne trouve rien.

Nous prendrons les 1ères phrases de la page (sentences) ainsi que le lien URL lui-même.

Nous ajoutons également une comparaison in afin de vérifier la pertinence du résultat. En effet nous cherchons ici à partir de simples mots-clés dans le moteur de recherche de Wikipédia, et les résulats peuvent parfois être surprenants.

  1. import datetime
  2. import requests
  3. import xml.etree.ElementTree
  4. import wikipedia
  5.  
  6. mylayer = QgsProject.instance().mapLayersByName("peaks")[0]
  7. ...
  8. for feat in mylayer.getFeatures():
  9. ...
  10. peak_name = feat['NAME']
  11. ...
  12. layoutName = str(peak_name)
  13. ...
  14. # Récupérer le résumé Wikipédia
  15. try:
  16. wikipedia.set_lang("fr")
  17. myWikiContent = wikipedia.summary("massif des Écrins "+layoutName, sentences=2)
  18.  
  19. my_page = wikipedia.page(layoutName)
  20. myWikiLink = my_page.url
  21.  
  22. # Verifier la pertinence du resultat
  23. if layoutName.lower() in str(myWikiContent).lower():
  24. None
  25. else:
  26. myWikiContent = ""
  27. myWikiLink = ""
  28.  
  29. except Exception:
  30. pass
  31. ...
  • Note personnelle : la recherche et la comparaison peuvent être améliorées, elle semble ne pas marcher dans certains cas (sommet de l'Ourson par exemple...).

Condition

Puis nous mettons le texte dans une condition if else avant d'afficher le texte. Peut-être pas nécessaire mais évite d'encombrer la carte avec des blocs vides.

Le contenu du texte est maintenant une concaténation de nos nouvelles variables :

  1. ...
  2. #Texte
  3. if myWikiContent is None:
  4. None
  5. else:
  6. TextCustom = QgsLayoutItemLabel(layout)
  7. TextCustom.setText(myWikiContent+"\n\n"+myWikiLink)
  8. TextCustom.setFont(QFont("Verdana", 11))
  9. layout.addLayoutItem(TextCustom)
  10. TextCustom.attemptMove(QgsLayoutPoint(230, 100, QgsUnitTypes.LayoutMillimeters))
  11. TextCustom.attemptResize(QgsLayoutSize(60, 100, QgsUnitTypes.LayoutMillimeters))
  12. ...

Code complet pour générer les cartes à partir d'un projet QGIS vide (modifiez les chemins, fonctionne dans l'éditeur Python exclusivement)

  • Note personnelle : ajouter une image d'illustration dans les cartes depuis les données Wikidata.

 

Liens ou pièces jointes
Accéder à cette adresse URL (https://hg-map.fr/extern/data/shapes/france/chemin_de_fer.zip)chemin_de_fer.zip[ ]0 Ko
Télécharger ce fichier (data_BDTOPO_V3_Dep05_adresse.zip)data_BDTOPO_V3_Dep05_adresse.zip[ ]3889 Ko
Télécharger ce fichier (data_IRIS_2019.zip)data_IRIS_2019.zip[ ]45905 Ko
Télécharger ce fichier (decathlon_france.zip)decathlon_france.zip[308 magasins Décathlon français depuis OSM le 27 décembre 2020]11 Ko
Accéder à cette adresse URL (https://hg-map.fr/extern/data/shapes/france/eau.zip)eau.zip[ ]0 Ko
Télécharger ce fichier (glaciers.zip)glaciers.zip[ ]231 Ko
Télécharger ce fichier (iso_iris.zip)iso_iris.zip[Des zones isochrones à 15 minutes autour de 308 POIs.]12125 Ko
Télécharger ce fichier (MasterGeom2_ProgPython_DevoirMaison.pdf)Devoir-maison[ ]422 Ko
Télécharger ce fichier (peaks.zip)peaks.zip[ ]14 Ko
Accéder à cette adresse URL (https://hg-map.fr/extern/data/shapes/france/troncons_routes.zip)troncons_routes.zip[ ]0 Ko