# DOCUMENTATION ## Reseau routier .net.xml de SUMO [ref](https://sumo.dlr.de/docs/Networks/SUMO_Road_Networks.html) ![Différents éléments composant le reseau routier](net_el.jpg "Elements d'un réseau") ### Edge Relie deux nodes, il possède un ID unique le décrivant et les deux ID des nodes composant ses extrémitées. Il ne possède pas directement d'information concernant le tracé de la route, ces données sont fournis par les [lanes](#Lane) Un edge est considéré comme "interne" si il est inclus dans une jonction ### Lane Comme le nom l'indique, decris une voie de la route. Possède un ID et un index numéroté de droite à gauche, une vitesse maximale autorisé, une longueur. Le tracé de la route est defini par la propriètée "shape" qui est composée d'une liste de position x,y ### Junction/Node Une jonction entre plusieurs routes. Possède un ID, une position (x,y), une liste des voies qui s'y intersecte, une liste des voies internes qui la compose, un shape qui l'englobe ## Implémentation ### main.py Le fichier principal #### Imports imports "classiques" : ```python import os, sys import time ``` imports pour sumo, honnêtement je suis pas sur qu'on en ai besoin si la librairie est installée avec pip mais bon c'était le code recommandé [ici](https://sumo.dlr.de/docs/Tools/Sumolib.html) : ```python if 'SUMO_HOME' in os.environ: tools = os.path.join(os.environ['SUMO_HOME'], 'tools') sys.path.append(tools) else: print("please declare environment variable 'SUMO_HOME'") ``` imports pour Qt : ```python from PySide6.QtCore import Qt, QTimer from PySide6.QtWidgets import QApplication, QMainWindow from PySide6.QtGui import QSurfaceFormat, QAction ``` import du fichier contenant la description de interface, generée par uic : ```python from window import Ui_MainWindow ``` le fichier contenant la boucle principale de maj : ```python from mainLoop import mainLoop ``` #### Classe MainWindow On définit la classe MainWindow qui hérite de QMainWindow ```python class MainWindow(QMainWindow): ``` ##### `__init__` La fonction `__init__` est la fonction qui est appellée quand on crée l'objet (le constructeur) ```python def __init__(self): ``` On fait appel à la fonction `__init__` de la superclass, donc on execute le code d'initialisation de la classe QMainWindow ```python super(MainWindow, self).__init__() ``` On genere l'interface à partir du fichier générée par QT Designer ```python self.ui = Ui_MainWindow() self.ui.setupUi(self) ``` On initialise la boucle de mise à jour ```python self.mainLoop = mainLoop(self) ``` On donne à la boucle principale le widget qui nous sert à afficher les infos (l'interface avec position, vitesse,... à droite de l'écran). Après il est transmis à chaque voiture qui décident de ce quelles vont afficher ```python self.mainLoop.addInfosDisplay(self.ui.infos) ``` On génere le menu (l'interface en haut de l'écran). Avec le menu general, qui as un sous-menu (File), qui as un sous-menu (Open), qui as deux sous-actions (ouvrir le réseau et ouvrir les vehicules). Pour chaque action on les relient (connect) à un fonction qui sera executée quand on cliquera sur le bouton. ```python fileMenu = self.menuBar().addMenu("&File") openMenu = fileMenu.addMenu("&Open") openNet = QAction("&Open Network",self) openNet.setStatusTip("Open Network file (.net.xml)") openNet.triggered.connect(self.mainLoop.openNetwork) openMenu.addAction(openNet) openVeh = QAction("&Open Vehicles",self) openVeh.setStatusTip("Open Vehicle description (.rou.xml)") openVeh.triggered.connect(self.mainLoop.openVehicles) openMenu.addAction(openVeh) ``` On crée un timer qui va executer toutes les 1/60s (60fps) la fonction update de notre boucle principale ```python timer = QTimer(self) timer.timeout.connect(self.mainLoop.update) timer.start(1.0/60) ``` ##### keyPressEvent Cette fonction est appelée à chaque appui sur le clavier, ici si la touche est Esc ou Q on arrète le programme ```python def keyPressEvent(self, e): if e.key() == Qt.Key_Escape or e.key() == Qt.Key_Q: self.close() ``` #### main `__name__` est defini à `"__main__"` seulement si on est dans le fichier principal (i.e pas dans un module). Donc cette portion de code est celle executée quand on lance main.py ```python if __name__ == "__main__": ``` On crée une QApplication, comme ça Qt se demerde pour gérer l'event loop, le context,... ```python app = QApplication() ``` On définie les propriètés par defaut de la surface, j'avais mis ça pour activer l'AA mais ça à l'air foiré ([cf](https://doc.qt.io/qtforpython/PySide6/QtOpenGLWidgets/QOpenGLWidget.html#PySide6.QtOpenGLWidgets.PySide6.QtOpenGLWidgets.QOpenGLWidget.resized)) ```python format = QSurfaceFormat() format.setDepthBufferSize(24) format.setVersion(3, 2) format.setSamples(4) format.setProfile(QSurfaceFormat.CoreProfile) QSurfaceFormat.setDefaultFormat(format) ``` On crée notre objet de fenetre principale défini [juste au dessus](#Classe-MainWindow) ```python window = MainWindow() window.show() ``` Finalement on lance l'event loop, et on quitte dès qu'elle retourne ```python sys.exit(app.exec()) ``` ### mainLoop.py Le fichier contenant notre boucle de maj principale, appelé par un QTimer défini dans [main.py](#Classe-MainWindow) #### imports Nos deux classes définies dans [Map.py](#Map.py) et [CarController.py](#CarController.py) chargées de gérer respectivement le reseau (chargement, affichage,...) et les voitures (chargement, calcul de trajet, affichage, deplacements, ...) ```python from Map import Map from CarController import CarController ``` import de QFileDialog pour l'ui d'ouverture des fichiers de reseau et de vehicules ```python from PySide6.QtWidgets import QFileDialog ``` #### classe mainLoop Notre classe (On remarquera qu'elle ne suit pas la même convention pour le nom que MainWindow) ```python class mainLoop(): ``` ##### \_\_init\_\_ La fonction d'initialisation Ici parent fait reference à notre MainWindow parce que QFileDialog veut absolument un QWidget. painter est la surface d'affichage principale, définie dans le fichier [mainPainter.py](#mainPainter.py), on s'en sert pour dessiner. On crée nos objet pour le réseau (Map) et pour les voitures (CarController), pour l'instant ils sont vides, on les remplis après quand l'utilisateur nous donne l'emplacement des fichiers ([openNetwork](#openNetwork) et [openVehicles](openVehicles)) ```python def __init__(self, parent): self.parent = parent self.painter = parent.ui.mainSurf self.map = Map() self.controller = CarController(self.map) self.painter.addMap(self.map) self.painter.addCarController(self.controller) ``` ##### update La fonction dans laquelle on fait les maj, elle est appelée periodiquement par le Timer defini dans le [`__init__`](#mainInit) de la fenêtre principale On fait d'abord la maj des voitures (deplacement + conduite). Puis on fait appel à update sur notre painter qui va prévoir un [paintEvent](#paintEvent) dans lequel on fait appel à [Map.draw](#mapDraw) et [CarController.draw](#CCDraw) qui va redessiner les elements de l'affichage ```python def update(self): self.controller.update() self.painter.update() ``` ##### openNetwork Fonction appelé quand l'utilisateur click sur le bouton open-\>network file ```python def openNetwork(self): ``` On crée un dialogue d'ouverture de fichier ```python fileName = QFileDialog.getOpenFileName(self.parent,"Open Network", "./", "Network File (*.net.xml)") ``` Si le resultat est vide (i.e l'utilisateur à annulé) on abandonne ```python if(fileName[0] == ''): return ``` Sinon on crée le reseau à partir u chemin récupéré ```python self.map.fromPath(fileName[0]) ``` On génére la matrice de transformation entre coordonnées du réseau-\>coordonées de l'écran ```python self.painter.generateTransform() ``` Maintenant qu'on as le réseau on peut generer les itineraires pour les voitures (si on as pas encore chargé les voitures cette fonction n'a pas d'effet) ```python self.controller.prepareRoute() ``` ##### openVehicles Très similaire à la fonction [openNetwork](#openNetwork) mais ce coup ci pour charger les vehicules ```python def openVehicles(self): fileName = QFileDialog.getOpenFileName(self.parent,"Open Vehicle trip description", "./", "Route File (*.rou.xml)") if(fileName[0] == ''): return self.controller.fromPath(fileName[0]) ``` Si le reseau est déjà chargé on peut directement calculer les trajet ici ```python if(self.map.isLoaded()): self.controller.prepareRoute() ```