297 lines
8.9 KiB
Markdown
297 lines
8.9 KiB
Markdown
# DOCUMENTATION
|
|
|
|
## Reseau routier .net.xml de SUMO
|
|
[ref](https://sumo.dlr.de/docs/Networks/SUMO_Road_Networks.html)
|
|
|
|

|
|
|
|
### 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):
|
|
```
|
|
|
|
<a id="mainInit"></a>
|
|
##### `__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():
|
|
```
|
|
|
|
<a id="mainLoopInit"></a>
|
|
##### \_\_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()
|
|
```
|
|
|
|
## Compilation
|
|
### Pour faire un executable
|
|
nuitka --onefile --enable-plugin=pyside6 main.py
|