Chargement de fichiers GPX
Le format GPX est couramment utilisé pour stocker des données GPS, générées par une montre connectée lors d'un parcours de course par exemple. Dans ce chapitre, nous allons étudier la structure d'un tel fichier afin d'en extraire différentes informations comme la distance totale parcourue, la vitesse moyenne, jusqu'à la représentation du parcours sur une carte.
Le format XML
Le format GPX utlise la syntaxe
XML
qui permet de structurer
des informations à l'aide d'éléments délimités par des
balises (tags en anglais) ouvrante et fermante. Ci-dessous, un
extrait du fichier XML character.xml
.
<?xml version="1.0" encoding="UTF-8"?>
<bigbangtheory>
<character id="46567">
<lastname>Cooper</lastname>
<firstname>Sheldon</firstname>
<gender>M</gender>
</character>
<character id="12874">
<lastname>?</lastname>
<firstname>Penny</firstname>
<gender>F</gender>
</character>
<character id="12355">
<firstname>Leonard</firstname>
<lastname>Hofstadter</lastname>
<gender>M</gender>
</character>
<character id="978776">
<lastname>Wolowitz</lastname>
<firstname>Howard</firstname>
<gender>M</gender>
</character>
<character id="34345">
<gender>M</gender>
<firstname>Raj</firstname>
<lastname>Koothrappali</lastname>
</character>
</bigbangtheory>
Ce fichier recense les personnages principaux de la série américaine
Big Bang Theory.
Vous remarquerez que les informations
sont organisées sous la forme d'une arborescence traduite par l'imbrication des
éléments : l'élément racine bigbangtheory
contient plusieurs
éléments enfants character
. Chaque élément enfant peut lui-même
contenir des enfants (lastname
, firstname
et gender
dans ce cas précis).
Ainsi, le contenu d'un élément délimité par une balise ouvrante <tagname>
et une balise fermante </tagname>
peut être :
- Une liste d'éléments enfants
- Un texte (par exemple
F
pour l'élémentgender
)
Un attribut peut aussi être associé à un élément : il apparaît dans la balise ouvrante
sous la forme d'un couple (clé,valeur). Dans notre exemple, chaque élément character
dispose d'un attribut id
.
Le module beautifulsoup4
La structure d'un fichier XML étant plus complexe que celle d'un fichier CSV, nous allons nous
appuyer sur le module
beautifulsoup4
pour parcourir le contenu du fichier afin d'en extraire
certaines informations. Comme tous les modules externes, il doit être installé avant d'être utilisé.
La commande sous Windows est la suivante :
pip.exe install beautifulsoup4 lxml
Et sous macOS ou Linux
pip3 install beautifulsoup4 lxml
Un fois le module importé dans le script, on charge le contenu du fichier XML en mémoire
à l'aide de la fonction open()
et on construit l'arborescence des données
avec la fonction BeautifulSoup()
.
from bs4 import BeautifulSoup
# Chargement du fichier XML
content = open('character.xml')
# Construction de l'arborescence
soup = BeautifulSoup(content, 'lxml')
Extraction d'informations
la méthode find_all()
permet de rechercher un élément dans
l'arborescence des données XML afin d'extraire une information particulière.
Par exemple, le code ci-dessous permet d'afficher les prénoms de tous les personnages.
from bs4 import BeautifulSoup
# Chargement du fichier XML
content = open('character.xml')
# Construction de l'arborescence
soup = BeautifulSoup(content, 'lxml')
for tag in soup.find_all('firstname'):
print(tag.string)
La recherche est effectuée à partir de la racine de l'arborescence afin
d'extraire tous les éléments firstname
, enfant d'un élément character
lui-même
enfant de l'élément bigbangtheory
.
L'accès à la valeur d'un attribut associé à un élément se fait via l'opérateur [ ]
.
Le script suivant affiche les identifiants de tous les personnages.
from bs4 import BeautifulSoup
# Chargement du fichier XML
content = open('character.xml')
# Construction de l'arborescence
soup = BeautifulSoup(content, 'lxml')
for tag in soup.find_all('character'):
print(tag['id'])
Enfin, lorsque l'on a besoin d'accéder à plusieurs valeurs au sein d'un élément,
il est déconseillé d'appeler plusieurs fois la méthode find_all()
qui effectue un parcours complet de la structure XML. Il est plutôt
recommandé d'utiliser la propriété children
qui permet d'accéder
à liste des enfants d'un élément. Le script ci-dessous affiche l'identifiant
ainsi que le nom et le prénom de chaque personnage :
from bs4 import BeautifulSoup
content = open('character.xml')
soup = BeautifulSoup(content, 'lxml')
print('\n\n+ Liste des personnages principaux :')
for tag in soup.find_all('character'):
id = tag['id']
lastname = ''
firstname = ''
for child in tag.children:
if child.name == 'lastname':
lastname = child.string
if child.name == 'firstname':
firstname = child.string
print('(', id, ')', lastname, firstname)
Exercice
Modifiez le script précédent afin de calculer la proportion de personnages féminins et masculins dans Big Bang Theory.
+ Liste des personnages principaux :
( 46567 ) Cooper Sheldon
( 12874 ) ? Penny
( 12355 ) Hofstadter Leonard
( 978776 ) Wolowitz Howard
( 34345 ) Koothrappali Raj
( 75543 ) Bloom Stuart
( 65238 ) Fowler Amy Farrah
( 45984 ) Rostenkowski Bernadette
( 17360 ) Jeffries Arthur
( 91727 ) Kripke Barry
( 12757 ) Winkle Leslie
( 761289 ) Sweeney Emily
( 13247 ) Winkle Leslie
( 97852 ) Koothrappali Priya
( 57689 ) Wheaton Wil
( 91557 ) Gibbs Dave
( 836411 ) Barnett Stephanie
+ Proportion d'hommes et de femmes :
Femmes : 47%
Hommes : 53%
from bs4 import BeautifulSoup
content = open('character.xml')
soup = BeautifulSoup(content, 'lxml')
female_count = 0
male_count = 0
print('\n\n+ Liste des personnages principaux :')
for tag in soup.find_all('character'):
id = tag['id']
lastname = ''
firstname = ''
for child in tag.children:
if child.name == 'lastname':
lastname = child.string
if child.name == 'firstname':
firstname = child.string
if child.name == 'gender':
if child.string == 'F':
female_count = female_count + 1
else:
male_count = male_count + 1
print('(', id, ')', lastname, firstname)
character_count = female_count + male_count
print('\n\n+ Proportion d'hommes et de femmes :')
print('Femmes :', round(female_count*100/character_count), '%')
print('Hommes :', round(male_count*100/character_count), '%\n\n')
Le format GPX
Maintenant que nous nous sommes familiarisés avec le format XML et les méthodes du module
beautifulsoup4
,
il est temps de disséquer un fichier GPX contenant des relevés GPS. Je vous propose de commencer avec
un extrait du fichier RATJ2012-21km-herve.schely.gpx
.
<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1"
creator="runtastic - makes sports funtastic, http://www.runtastic.com"
xsi:schemaLocation="http://www.topografix.com/GPX/1/1
http://www.topografix.com/GPX/1/1/gpx.xsd
http://www.garmin.com/xmlschemas/GpxExtensions/v3
http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd
http://www.garmin.com/xmlschemas/TrackPointExtension/v1
http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd"
xmlns="http://www.topografix.com/GPX/1/1"
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<metadata>
<copyright author="www.runtastic.com">
<year>2012</year>
<license>http://www.runtastic.com</license>
</copyright>
<link href="http://www.runtastic.com">
<text>runtastic</text>
</link>
<time>2012-10-21T07:01:38.000Z</time>
</metadata>
<trk>
<link href="http://www.runtastic.com/sport-sessions/29614704">
<text>Cliquez sur ce lien pour voir cette activité sur runtastic.com</text>
</link>
<trkseg>
<trkpt lon="4.0281324386596697" lat="49.2516021728515980">
<ele>82.0</ele>
<time>2012-10-21T07:01:39.000Z</time>
</trkpt>
<trkpt lon="4.0278654098510698" lat="49.2515068054199006">
<ele>82.0</ele>
<time>2012-10-21T07:01:52.000Z</time>
</trkpt>
<trkpt lon="4.0278654098510698" lat="49.2515068054199006">
<ele>82.0</ele>
<time>2012-10-21T07:02:12.000Z</time>
</trkpt>
</trkseg>
</trk>
</gpx>
Exercice
Après avoir étudié minutieusement la structure du fichier d'exemple, écrivez un script gpxplorer.py
qui remplit trois listes avec les données de longitude, latitude et altitude contenues dans l'arborescence.
from bs4 import BeautifulSoup
# Liste des latitudes exprimées en degrés
latitude = []
# Liste des longitudes exprimées en degrés
longitude = []
# Liste des altitudes exprimées en mètres
elevation = []
# Chargement du fichier GPX
content = open('RATJ2012-21km-herve.schely.gpx')
# Construction de l'arborescnce
soup = BeautifulSoup(content, 'lxml')
for point in soup.find_all('trkpt'):
latitude.append(float(point['lat']))
longitude.append(float(point['lon']))
for param in point.children:
if param.name == 'ele':
elevation.append(float(param.string))
Dans le chapitre Visualisation de trajectoires, nous avons appris
à créer des graphiques à l'aide du module matplotlib
.
Exercice
Modifiez le scriptgpxplorer.py
afin de générer un graphique à partir des relevés d'altitude.
from bs4 import BeautifulSoup
import matplotlib.pyplot as plt
# Liste des latitudes exprimées en degrés
latitude = []
# Liste des longitudes exprimées en degrés
longitude = []
# Liste des altitudes exprimées en mètres
elevation = []
# Chargement du fichier GPX
content = open('RATJ2012-21km-herve.schely.gpx')
# Construction de l'arborescnce
soup = BeautifulSoup(content, 'lxml')
for point in soup.find_all('trkpt'):
latitude.append(float(point['lat']))
longitude.append(float(point['lon']))
for param in point.children:
if param.name == 'ele':
elevation.append(float(param.string))
plt.plot(range(len(elevation)), elevation)
plt.ylim(0, 200)
plt.title('Relevé d'altitudes')
plt.ylabel('hauteur (m)')
plt.show()
Pour évaluer des distances à partir de couples (latitude, longitude), nous allons avoir besoin de quelques rudiments de trigonométrie, c'est justement l'objet du prochain chapitre.