|
Simulateur Ferroviaire
Reconstruction et visualisation d'un réseau ferroviaire à partir de données GeoJSON — Win32 / WebView2 / Leaflet
|
Le module PCC (Poste de Commandement Centralisé) produit et affiche un Tableau de Contrôle Optique (TCO) schématique du réseau ferroviaire.
Il opère en deux temps :
(x, y) à chaque nœud par parcours en largeur.HDC Win32.Principe fondamental : le layout PCC est purement topologique. Les coordonnées GPS ne sont autorisées qu'une seule fois — dans
PCCGraphBuilder::computeDeviationSides()— pour déterminer le côté géographique (±1) de la branche déviée de chaque aiguillage. Toute autre valeur de position découle de la topologie de voisinage.
PCCNode est la classe abstraite de base. Deux sous-classes concrètes :
| Sous-classe | Source | getNodeType() |
|---|---|---|
| PCCStraightNode | StraightBlock | PCCNodeType::STRAIGHT |
| PCCSwitchNode | SwitchBlock | PCCNodeType::SWITCH |
Chaque nœud porte :
m_sourceId — identifiant du bloc source ("s/0", "sw/3", …)m_source — pointeur non-propriétaire vers le ShuntingElement (durée de vie garantie par TopologyRepository)m_position — coordonnées logiques {x, y} calculées par PCCLayoutm_edges — liste de pointeurs non-propriétaires vers les arêtes adjacentesPCCSwitchNode ajoute :
m_rootEdge, m_normalEdge, m_deviationEdge — accès O(1) sans parcourir m_edges.m_deviationSide — côté géographique de la déviation (+1 nord / -1 sud), calculé par PCCGraphBuilder::computeDeviationSides() via produit vectoriel 2D sur coordonnées UTM.PCCEdge est une arête orientée (from → to) portant un rôle sémantique :
| PCCEdgeRole | Signification |
|---|---|
ROOT | Connexion switch → branche root |
NORMAL | Connexion switch → branche normale |
DEVIATION | Connexion switch → branche déviée |
STRAIGHT | Connexion straight → straight adjacent |
Les arêtes sont détenues par PCCGraph via unique_ptr. Les nœuds en reçoivent des pointeurs non-propriétaires via PCCNode::addEdge().
PCCGraph détient l'ensemble des nœuds (unique_ptr<PCCNode>) et des arêtes (unique_ptr<PCCEdge>). Il expose un index id → PCCNode* pour les lookups O(1) lors de la construction des arêtes.
PCCGraphBuilder construit le graphe en trois passes :
Un nœud PCC est créé pour chaque StraightBlock et SwitchBlock présent dans TopologyData. Tous les nœuds sont indexés dans PCCGraph avant la passe 2.
Arêtes de switch : pour chaque switch orienté, trois arêtes ROOT / NORMAL / DEVIATION sont créées depuis le nœud switch vers les nœuds des blocs branche. Les arêtes sont enregistrées dans PCCSwitchNode via setRootEdge() / setNormalEdge() / setDeviationEdge().
Arêtes STRAIGHT : pour chaque straight, getNeighbours() (pointeurs résolus par Phase 8) fournit les blocs adjacents. Une arête STRAIGHT est créée pour chaque connexion, avec dédoublonnage par clé canonique makeEdgeKey(idA, idB).
Correctif v2 :
buildEdges()utiliseStraightBlock::getNeighbours()(pointeurs résolus) et nongetNeighbourIds()(IDs vides pour les sous-blocs de subdivision produits parPhase6).
Pour chaque PCCSwitchNode avec un switch orienté et des tips CDC UTM disponibles, calcule le produit vectoriel 2D entre le vecteur jonction → tip root et le vecteur jonction → tip déviation :
side > 0 → déviation à gauche de root (vers le haut dans le TCO). side < 0 → déviation à droite de root (vers le bas dans le TCO).
PCCLayout attribue une position logique {x, y} à chaque nœud par parcours en largeur depuis les nœuds terminus (degré 1 dans le graphe PCC).
L'axe X représente la profondeur de progression le long du réseau. L'axe Y représente le décalage vertical des branches déviées.
findTermini() collecte tous les nœuds n'ayant qu'une seule arête. En l'absence de terminus (réseau en boucle), le premier nœud du graphe est utilisé comme point de départ.
Chaque terminus lance un BFS indépendant (runBFS()), avec un offsetX croissant pour éviter les collisions entre composantes connexes distinctes.
Pour chaque nœud courant (x, y) et chaque voisin non encore visité :
| Condition | Position voisin | arrivedViaDeviation |
|---|---|---|
| STRAIGHT / ROOT / NORMAL standard | (x+1, y) | false |
| DEVIATION → straight ou switch ordinaire | (x+1, y ± side) | false |
| DEVIATION → switch (double aiguille) | (x, y ± side) | true |
| ROOT forward, arrivée via déviation | (x+1, y) | false |
| NORMAL forward, arrivée via déviation | (x-1, y) | false |
Le drapeau arrivedViaDeviation modifie le tri des voisins à la prochaine itération : ROOT est traité en premier (continuation de la route déviée).
Quand deux nœuds obtiendraient la même position (x, y), le nœud entrant est décalé verticalement par incrément de ±1 jusqu'à trouver une cellule libre. Cette résolution n'intervient que dans des topologies exceptionnelles.
La structure TCORenderer::Projection est calculée une seule fois par computeProjection() et mise en cache dans PCCPanel. Elle contient :
originX, originY — décalage en pixels pour centrer le schémascaleX, scaleY — facteur d'échelle pixels/unité logiquestub, inactiveGap, halfGap — précalculs des constantes de dessin des aiguillages (optimisation Famille E)drawStraightBlock() trace un trait horizontal avec un gap central (BLOCK_GAP_PX) représentant la section de voie. La couleur dépend de ShuntingState :
| État | Couleur |
|---|---|
FREE | Gris clair RGB(220,220,220) |
OCCUPIED | Rouge RGB(220,50,50) |
INACTIVE | Gris foncé RGB(80,80,80) |
drawSwitchBlock() trace le symbole aiguille junctionX (forme Y) :
stub)(x+1, y ± deviationSide)La branche inactive est tracée en gris atténué (branchOff RGB(64,64,64)). Chaque branche utilise un PenScope RAII indépendant (Famille D) — aucun CreatePen redondant, aucune fuite GDI.
Le static_cast<PCCSwitchNode*> est garanti sûr par la vérification préalable de getNodeType() == SWITCH (Famille C — RTTI supprimé).
PCCPanel est une fenêtre Win32 enfant de MainWindow. Elle gère :
rebuild() est appelé après chaque parsing réussi. Il enchaîne :
PCCGraphBuilder::build() — construit le graphe PCCPCCLayout::compute() — attribue les positionsm_projDirty = true)InvalidateRect() pour forcer un WM_PAINTLa projection est coûteuse à recalculer. PCCPanel la met en cache dans m_cachedProj et ne la recalcule que si :
m_projDirty == true (après un rebuild), ouRECT a changé (resize fenêtre).TCORenderer::draw() reçoit la projection précalculée — il n'appelle plus computeProjection() en interne.
m_panX/Y)Le zoom/pan est appliqué via une world transform GDI (SetWorldTransform) avant l'appel à TCORenderer::draw(), avec fillBackground = false pour éviter de recouvrir la zone déjà peinte.
| Classe | Fichier | Rôle |
|---|---|---|
| PCCGraph | Modules/PCC/PCCGraph.h | Conteneur nœuds + arêtes |
| PCCNode | Modules/PCC/PCCNode.h | Nœud abstrait |
| PCCStraightNode | Modules/PCC/PCCStraightNode.h | Nœud straight concret |
| PCCSwitchNode | Modules/PCC/PCCSwitchNode.h | Nœud switch + deviationSide |
| PCCEdge | Modules/PCC/PCCEdge.h | Arête orientée avec rôle |
| PCCEdgeRole | Modules/PCC/PCCEdge.h | ROOT/NORMAL/DEVIATION/STRAIGHT |
| PCCNodeType | Modules/PCC/PCCNode.h | STRAIGHT/SWITCH |
| PCCGraphBuilder | Modules/PCC/PCCGraphBuilder.h | Construction graphe (3 passes) |
| PCCLayout | Modules/PCC/PCCLayout.h | Positionnement BFS |
| TCORenderer | Engine/HMI/PCCPanel/TCORenderer.h | Rendu GDI |
| TCORenderer::Projection | Engine/HMI/PCCPanel/TCORenderer.h | Cache de projection |
| PCCPanel | Engine/HMI/PCCPanel/PCCPanel.h | Fenêtre Win32 PCC |