Vous devrez utiliser l'interface TPLab pour envoyer vos TP aux encadrants de TP.
La note ne valide pas seulement le résultat de votre programme, mais également son style :
Vérifiez ces points avant de demander à votre intervenant de valider votre code.
La procédure suivante initialise une petite fenêtre de dessin :
from tkinter import * def test_dessin(taille=400): """procédure pour tester l'interface de dessin paramètre : taille, de type entier : taille en pixel de la zone de dessin""" # initialisation de la fenêtre graphique root = Tk() root.title("test") root.resizable(width=False, height=False) root.bind('q', lambda _: root.destroy()) dessin = Canvas(root, width=taille, height=taille, background='white') dessin.pack() dessin.bind('q', lambda _: root.destroy()) ... mainloop()
La variable dessin
est un objet particulier avec de nombreuses méthode de dessin associées :
dessin.create_line
, qui prend en argument des coordonnées (en pixels) de points. Par exemple
dessin.create_line([0, 0, 100, 100, 100, 200])
dessin.create_polygon
, qui prend aussi des coordonnées (en pixels) de points. Par exemple
dessin.create_polygon([0, 0, 100, 100, 100, 200])
Attention : l'origine de la fenêtre est le coin en haut à gauche, et l'axe vertical est orienté vers le bas.
Les méthodes create_line
et create_polygon
ont en commun des arguments optionnels :
fill=
pour spécifier la couleur. (La couleur est une chaîne de caractères contenant le nom de la couleur en anglais : "red"
' "black"
, etc. ou bien une chaîne donnant la décomposition RGB en hexadecimal sur 6 caractères : "#00ff80"
.)
width=
pour spécifier l'épaisseur des traits (en pixels).
Complétez la fonction de test pour dessiner un carré plein rouge avec ses diagonales en noir au milieu de la fenêtre de dessin.
Nous avons vu comment ouvrir un fichier sur le disque dur : on utilise la fonction open
de Python. Il est également possible d'ouvrir (en lecture seulement) un fichier sur le réseau : on utilise la fonction urlopen
:
from urllib.request import urlopen f = urlopen("http://lama.univ-savoie.fr/~hyvernat/Enseignement/1213/info113/td1.txt")
Le résultat d'un urlopen(url)
est très similaire au résultat d'un open(fichier)
: on peut accéder au contenu grace aux méthodes read
ou readline
.
Les fichiers réseaux sont ouverts en mode binaire. Le résultat d'un read
ou d'un readline
est donc un tableau d'octets au lieu d'une chaîne. Pour convertir un tableau d'octets en chaîne, on peut utiliser :
bs = f.readline() # bs est un tableau d'octets s = bs.decode("UTF-8", "ignore") # s est une chaîne de caractères en ASCII
Attention : les octets qu'on ne peut pas décoder en caractère UTF-8 seront ignorés. si le fichier n'est pas un document texte en UTF-8, la variable s
contiendra seulement les caractère non accentués du document.
Pour simplifier la gestion des documents téléchargés, nous utiliserons la fonction suivante :
import os import hashlib from urllib.request import urlopen def get_url(url, local=True, binaire=False): """charge une url et crée une version dans un cache local. Lors des requêtes suivantes, la version en cache est chargée si local=True. Pour des fichiers binaires (images), utiliser "binaire=True".""" if not os.path.exists("url_cache"): os.makedirs("url_cache/") nom_fichier = "url_cache/"+hashlib.md5(url.encode("UTF-8")).hexdigest() + ".tmp" if os.path.exists(nom_fichier) and local: if binaire: text = open(nom_fichier, "rb").read() else: text = open(nom_fichier, "r", encoding="UTF-8").read() else: text = urlopen(url).read() if len(text) > 0: if binaire: fichier = open(nom_fichier, "wb") else: fichier = open(nom_fichier, "w", encoding="UTF-8") if not binaire: text = text.decode("UTF-8", "ignore") + '\n' fichier.write(text) fichier.close() return(text)
Cette fonction télécharge le fichier à l'url demandé et le convertit en grosse chaîne de caractères. Chaque document téléchargé est sauvegardé dans un répertoire temporaire ("url_cache
") et lorsqu'on redemande un document déjà téléchargé, la fonction le récupère sur le disque.
Dans ce TP, nous allons utiliser l'API ("Application Programming Interface") OpenStreetMap qui permet à un utilisateur d'interroger le serveur et récupérer des information utilisables par un programme :
L'API OpenStreetMap permet de télécharger les données d'une carte rectangulaire à partir de ses coordonnées :
Par exemple, les coordonnées de la Tour Eiffel sont :
On peut donc obtenir les informations avec 2.2946±0.002 comme longitudes et 48.8584±0.002 comme latitudes.
Lorsque l'on a les coordonnées, il suffit de récupérer l'url http://api.openstreetmap.org/api/0.6/map?bbox=lon_min,lat_min,lon_max,lat_max
.
Par exemple : http://api.openstreetmap.org/api/0.6/map?bbox=2.2926,48.8564,2.2966,48.8604
Écrivez une fonction qui permet de construire une url pour un morceau de carte :
def url_openstreetmap(longitude, latitude, delta): """construit l'url de téléchargement des données d'une carte sur OpenStreetMap. paramètres : - longitude, de type flottant : longitude du point central de la carte - latitude, de type flottant : latitude du point central de la carte - delta, de type flottant : écartement (en degrés) par rapport au centre de la carte retour de type chaîne de caractères : url du morceau de carte correspondant """
Par exemple, l'url ci dessus a été obtenue avec url_openstreetmap(2.2946, 48.8584, 0.002)
.
Il est théoriquement impossible de plaquer un morceau de sphère sur un carré sans déformation. Pour corriger un peu cette déformation, on peut considérer des delta
différents pour la longitude et la latitude : on peut par exemple calculer la latitude minimale et maximale avec
latitude-delta/cos(longitude*pi/180))
et latitude+delta/cos(longitude*pi/180))
au lieu de latitude-delta
et latitude+delta
.
Les fichiers carte OpenStreetMap sont au format XML et sont constités de noeuds (node
) et de chemin (way
). Chaque chemin est lui même composé de noeuds. Par exemple, le fichier correspondant à l'url donnée ci dessus contient
<node id="21378260" lat="48.8566913" lon="2.2935429"/> <node id="24909428" lat="48.8600801" lon="2.2952792"/> <node id="33388329" lat="48.8576384" lon="2.2947551"/>
Chacune de ces lignes identifie un noeud avec ces coordonnées (lat
et lon
) et un identifiant (id
).
Le chemin
<way id="69034494"> <nd ref="828545285"/> <nd ref="828545086"/> <nd ref="828543774"/> <nd ref="828543773"/> <nd ref="828545285"/> <tag k="building" v="yes"/> <tag k="source" v="cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010"/> </way>
contient 5 noeuds, identifiés par leur ... identifiant.
La bibliothèque xml
permet de lire et parcourir les documents XML en python. On peut en particulier transformer un fichier ou une chaîne de caractères en document XML. (À condition que le fichier ou la chaîne contienne un description XML valide.)
>>> import xml.etree.ElementTree as XML
...
>>> carte = XML.fromstring(s)
L'avantage est que la variable carte
devient un objet de type "document XML", avec des méthodes spécifiques. Par exemple, on peut retrouver la liste des noeuds (les éléments node
) avec
>>> liste_noeuds = carte.findall("node")
On peut ensuite accéder aux attributs (identifiant, longitude, latitude) de chaque noeud avec attrib
:
>>> premier_noeud = liste_noeuds[0] >>> print("Le premier noeud a l'identifiant", premier_noeud.attrib["id"]) Le premier noeud a l'identifiant 368288
premier_noeud.attrib
est donc simplement un dictionnaire contenant tous les attributs.
Écrivez une fonction pour récupérer les coordonnées de chaque noeud en flottant :
def dictionnaire_noeuds(carte): """créé un dictionnaire de noeuds à partir des données brutes d'une carte Paramètre : carte, de type document XML : carte retour de type dictionnaire : associe à chaque identifiant de noeud, ses coordonnées (càd une paire de flottants : longitude et latitude)"""
Pour tester :
>>> carte = XML.fromstring(get_url(url_openstreetmap(2.2946, 48.8584, 0.002))) >>> dico_noeuds = dictionnaire_noeuds(carte) >>> print(len(dico)) 2345 >>> i = "368288" >>> print("coordonnées du noeud", i, dico_noeuds[i]) coordonnées du noeud 368288 (2.2954514, 48.8579822)
Les éléments de carte OpenStreetMap sont contenu dans des "chemin" : des élément appelés way dans les données XML. Par exemple :
<way id="69034494"> <nd ref="828545285"/> <nd ref="828545086"/> <nd ref="828543774"/> <nd ref="828543773"/> <nd ref="828545285"/> <tag k="building" v="yes"/> <tag k="source" v="cadastre-dgi-fr source : Direction Générale des Impôts - Cadastre. Mise à jour : 2010"/> </way>
représente un bâtiment (<tag k="building">
) donné par cinq noeud (nodes <nd .../>
). Seuls les identifiants des noeuds sont donnés. On peut donc retrouver leurs coordonnées dans le dictionnaire renvoyé par la fonction dictionnaire_noeuds
.
Comme pour les noeuds, on peut récupérer la liste des chemins avec
>>> liste_chemins = carte.findall("way")
Chaque chemin est également un morceau de document XML, et on peut donc récupérer les éléments nd
avec findall
:
>>> chemin = liste_chemins[0]
>>> ln = chemin.findall("nd")
Écrivez une fonction
def liste_noeuds(chemin): """récupère la liste des identifiants de noeuds dans un chemin Paramètre : chemin, de type XML retour de type liste de chaînes de caractères (liste des identifiants de noeuds)"""
Pour ceci, la première étape est de récupérer la liste des éléments nd
du chemin avec chemin.findall("nd")
. Il faudra ensuite parcourir cette liste pour récupérer les identifiants (attribut "ref"
).
Chaque chemin a un type. Il y a par exemple
highway
,
railway
,
waterway
,
building
,
amenity
,
landuse
,
shop
, sport
, tourism
, ...
Le type d'un chemin est stocké dans dans l'attribut "k" d'un élément tag
. Malheureusement, un chemin peut avoir de nombreux éléments tag
. On peut tester si un chemin est d'un certain type en :
tag
(avec ``.findall("tag"))
k
(avec ``.attrib["k"]) avec le type en question.
Écrivez la fonction
def chemin_de_type(chemin, type): """teste si un chemin OpenStreeMap a un certain type Paramètre : chemin, de type XML : chemin de la carte type, de type chaîne de caractères : type du chemin que l'on cherche retour de type booléen"""
Étant donnés les coordonnées longitude / latitude d'un point, on peut calculer (une approximation) de ses coordonnées en pixel dans carré de taille de la manière suivante
x = (longitude - longitude_min) * taille/(2*delta_longitude) y = taille - (latitude - latitude_min) * taille/(2*delta_latitude)
où
taille
est la taille (en pixel) de la zone de dessin
delta_longitude
et delta_latitude
sont les différence (en degré) entre le milieu de la zone et le bord de la zone
Écrivez la procédure suivante :
def dessine_routes(longitude, latitude, delta = 0.002): """dessine les routes dans la zone donnée paramètres : - longitude, de type flottant : longitude du point central de la carte - latitude, de type flottant : latitude du point central de la carte - delta, de type flottant : écartement (en degrés) par rapport au centre de la carte"""
Pour les coordonnées de la tour Eiffel, vous devriez obtenir quelque chose comme
Pour cela, vous devrez :
url_openstreetmap
)
get_url
)
XML.fromstring
)
dictionnaire_noeuds
)
.findall("way")
)
chemin_de_type
)
nd
du chemin (avec la fonction liste_noeuds
)
nd
dans une liste
create_line
Bien sûr, il faudra pour pouvoir dessiner initialiser une fenêtre graphique comme dans la partie préliminaires.
Si vous avez utilisé la formule latitude_max = latitude+delta/cos(longitude*pi/180))
dans votre fonction url_openstreetmap
, il faudra la réutiliser pour transformer les coordonnées longitude / latitude en pixels.
En partant de votre procédure dessine_route
, écrivez une procédure dessine_route_batiment
qui dessine les routes et les bâtiments (type building
).
Attention : pour les bâtiments, il faut utiliser create_polygon
au lieu de create_line
.
Votre carte pourrait ressembler à
Dessinez les cartes correspondant à
ou
(Attention, le dernier exemple est long...)
Transformez votre procédure précédente en procédure dessine_carte
qui dessine d'autres éléments...
Pour donner un peu de vie aux cartes que vous dessinez, vous pouvez utiliser :
fill=
)
dash=[4,2]
pour la procédure create_line
smooth=1
)
outline=
)
Bonus
Complétez votre programmes avec, par exemple :
tag
avec
k
égal à name
,
v
contenant le nom
-