Simulateur Ferroviaire
Reconstruction et visualisation d'un réseau ferroviaire à partir de données GeoJSON — Win32 / WebView2 / Leaflet
Chargement...
Recherche...
Aucune correspondance
Éléments — Modèle de domaine ferroviaire

Vue d'ensemble

Les éléments interactifs constituent le modèle de domaine ferroviaire. Ils représentent les entités physiques de l'infrastructure (tronçons, aiguillages) avec leurs géométries, leur topologie et leur état opérationnel.

GeoParser pipeline TopologyRepository HMI / Rendu
Phase6_BlockExtractor ────────► StraightBlock ────────► Leaflet / TCO
Phase8_SwitchOrientator ──────► SwitchBlock ────────► PCCGraph (via PCCGraphBuilder)
Phase7_DoubleSwitchDetector ──► (absorbLink)
Phase9_RepositoryTransfer ────► (resolve pointers)

Règle de cycle de vie :

  • Les éléments sont construits avec des données brutes (IDs de voisins sous forme de chaînes).
  • Les pointeurs résolus (prev/next, root/normal/deviation) sont null jusqu'à Phase9_RepositoryTransfer::resolve() — aucun code amont ne doit y accéder.
  • TopologyRepository est le seul propriétaire des instances (unique_ptr) ; tous les pointeurs résolus sont non-propriétaires.
Classe Responsabilité unique
Element Interface abstraite commune — identifiant, type, logger partagé
ShuntingElement Étend avec un état opérationnel et les helpers isFree / isOccupied / isInactive
StraightBlock Répresentation d'un bloc de circuit de voie
SwitchBlock Répresentation d'un bloc d'aiguillage

Hiérarchie

Element getId(), getType(), m_id, m_logger (static)
└── ShuntingElement getState(), isFree(), isOccupied(), isInactive(), m_state
├── StraightBlock
└── SwitchBlock

Copie interdite / déplacement autorisé sur toute la hiérarchie. La copie est supprimée dans Element (risque de slicing). Le déplacement est explicitement déclaré dans ShuntingElement car la présence du destructeur virtuel supprimerait sa génération implicite en C++11/14/17 — ce qui rendrait StraightBlock et SwitchBlock non-déplaçables et incompatibles avec make_unique et le pipeline de construction.

</blockquote>

Element

Classe de base abstraite. Définit l'interface commune à tous les éléments ferroviaires.

Element
├── getId() → std::string (pure virtual)
├── getType() → ElementType (pure virtual)
├── m_id : std::string (protected)
└── m_logger : Logger (protected, static)

Logger statique partagé

Element::m_logger est une unique instance statique partagée par toutes les classes dérivées (StraightBlock et SwitchBlock confondus). Cela produit un seul fichier Logs/Elements.log regroupant tous les événements d'infrastructure.

Le mutex interne du Logger garantit la thread-safety des écritures concurrentes.

// Utilisation dans une classe dérivée
LOG_INFO(m_logger, m_id + " orienté NORMAL→s/1 DEVIATION→s/2");
LOG_DEBUG(m_logger, m_id + " prev=" + m_neighbours.prev->getId());
#define LOG_INFO(logger, message)
Definition Logger.h:67
#define LOG_DEBUG(logger, message)
Definition Logger.h:70

ShuntingElement

Couche intermédiaire abstraite. Étend Element avec un état opérationnel (m_state, FREE par défaut) et trois helpers de commodité non-virtuels basés sur getState().

ShuntingElement
├── getState() → ShuntingState (pure virtual)
├── isFree() → bool basé sur getState()
├── isOccupied()→ bool basé sur getState()
├── isInactive()→ bool basé sur getState()
└── m_state : ShuntingState (protected, FREE par défaut)

Les helpers isFree() / isOccupied() / isInactive() délèguent à getState() afin que toute sous-classe surchargeant getState() bénéficie automatiquement de la logique correcte.


StraightBlock

Modèle d'un tronçon de voie droite. Identifiant au format "s/0", "s/1", … ou "s/0_c1", "s/0_c2", … après découpe par le pipeline.

Géométrie duale WGS84 / UTM

Chaque bloc maintient deux représentations de sa polyligne, pour deux usages distincts :

Champ Système Usage
m_pointsWGS84 WGS-84 (lat, lon) Rendu LeafletTopologyRenderer
m_pointsUTM UTM (x = est, y = nord, mètres) Calculs métriques du pipeline

Les deux polylignes ont la même taille et le même indexage.

Longueur géodésique : m_lengthMeters est calculée par somme Haversine sur m_pointsWGS84 et mise à jour automatiquement par setPointsWGS84(). getLengthUTM() calcule la longueur euclidienne depuis m_pointsUTM à la demande.

Topologie — IDs de voisins

m_neighbourIds contient les IDs (chaînes) des blocs adjacents, qu'il s'agisse de StraightBlock ou de SwitchBlock. La liste est maintenue triée lexicographiquementaddNeighbourId() insère en position correcte et rejette les doublons.

Méthode Rôle
addNeighbourId(id) Insertion triée sans doublon
replaceNeighbourId(oldId, newId) Substitution lors de l'absorption double switch

Pointeurs résolus — <tt>StraightNeighbours</tt>

struct StraightNeighbours {
ShuntingElement* prev; // extrémité A — nullptr si terminus
ShuntingElement* next; // extrémité B — nullptr si terminus
};

Renseignés par Phase9_RepositoryTransfer::resolve() via setNeighbourPrev() / setNeighbourNext() ou setNeighbourPointers(). Ils restent nullptr jusqu'à cette phase — aucun code antérieur ne doit les lire.

Méthode Rôle
getNeighbours() Retourne la struct complète
setNeighbourPrev(elem) Assigne l'extrémité A
setNeighbourNext(elem) Assigne l'extrémité B
setNeighbourPointers(n) Assigne les deux en une opération

SwitchBlock

Modèle d'un aiguillage ferroviaire à 3 branches. Identifiant au format "sw/0", "sw/1", …

Un SwitchBlock passe par trois états successifs au fil du pipeline :

État 1 — Non orienté branchIds = ["s/1","s/2","s/3"] rootId=∅
↓ Phase8_SwitchOrientator::orient()
État 2 — Orienté root="s/1" normal="s/2" deviation="s/3"
↓ Phase7_DoubleSwitchDetector::absorbLink() (si double aiguille)
État 3 — Double root="s/1" normal="sw/4" deviation="s/3" ← sw/4 remplace s/2

Géométrie — jonction et tips CDC

Le point de jonction (m_junctionWGS84, m_junctionUTM) est le nœud physique où convergent les trois branches.

Les tips CDC (m_tipOnRoot, m_tipOnNormal, m_tipOnDeviation) sont les extrémités de chaque branche issues du fichier CDC. Ils sont de type std::optional car absents avant l'orientation (Phase8).

La longueur totale de traversée est calculée par computeTotalLength() :

totalLength = root_leg + max(normal_leg, deviation_leg)

où chaque leg est la distance Haversine entre la jonction et le tip correspondant.

Requête Description
getJunctionWGS84() Coordonnée WGS-84 du nœud de jonction
getJunctionUTM() Coordonnée UTM du nœud de jonction
getTipOnRoot() Tip CDC branche root — std::optional
getTipOnNormal() Tip CDC branche normale — std::optional
getTipOnDeviation() Tip CDC branche déviée — std::optional
getTotalLengthMeters() Longueur de traversée — std::optional

Orientation — rôles des branches

Avant Phase7, un SwitchBlock ne connaît que la liste brute m_branchIds. orient() assigne les rôles sémantiques root / normal / deviation en vérifiant que chaque ID est bien présent dans m_branchIds (lève std::invalid_argument sinon).

isOriented() retourne true dès que m_rootBranchId est renseigné.

swapNormalDeviation() permute symétriquement les rôles, les tips CDC, les polylignes absorbées et les marqueurs de double switch — utilisé par Phase8_SwitchOrientator lors d'une correction de sens.

Méthode Rôle
orient(rootId, normalId, deviationId) Assigne les trois rôles
swapNormalDeviation() Permutation normale ↔ deviation
isOriented() True si les rôles sont assignés

Double aiguille — absorption du segment de liaison

Quand deux aiguillages sont reliés par un court StraightBlock de liaison, Phase7_DoubleSwitchDetector absorbe ce segment dans l'un des deux aiguillages.

absorbLink() effectue en une seule opération :

  1. Remplace linkId par partnerId dans m_branchIds
  2. Met à jour m_normalBranchId ou m_deviationBranchId
  3. Met à jour le tip CDC correspondant (extrémité distale du segment absorbé)
  4. Mémorise la polyligne absorbée (WGS84 + UTM) pour le rendu
  5. Renseigne m_doubleOnNormal ou m_doubleOnDeviation avec l'ID du partenaire
Avant absorption Après absorption
sw/0 ──s/link── sw/1 sw/0 (normal→sw/1, absorbedNormal=[s/link coords])

isDouble() retourne true si au moins un côté a absorbé un segment. getPartnerOnNormal() / getPartnerOnDeviation() retournent le SwitchBlock* partenaire via cast statique (valide uniquement si isDouble() est true).

Requête Description
isDouble() True si un segment de liaison a été absorbé
getDoubleOnNormal() ID du partenaire côté normal — std::optional
getDoubleOnDeviation() ID du partenaire côté deviation — std::optional
getAbsorbedNormalCoordinates() Polyligne WGS84 absorbée côté normal
getAbsorbedDeviationCoordinates() Polyligne WGS84 absorbée côté deviation
getAbsorbedNormalCoordsUTM() Polyligne UTM absorbée côté normal
getAbsorbedDeviationCoordsUTM() Polyligne UTM absorbée côté deviation
getPartnerOnNormal() SwitchBlock* partenaire côté normal, nullptr sinon
getPartnerOnDeviation() SwitchBlock* partenaire côté deviation, nullptr sinon

Pointeurs résolus — <tt>SwitchBranches</tt>

struct SwitchBranches {
ShuntingElement* root; // tronc entrant — nullptr si non résolu
ShuntingElement* normal; // sortie directe — nullptr si non résolu
ShuntingElement* deviation; // sortie déviée — nullptr si non résolu
};

Renseignés par Phase9_RepositoryTransfer::resolve().

Mutation Rôle
setRootPointer(elem) Assigne la branche root
setNormalPointer(elem) Assigne la branche normale
setDeviationPointer(elem) Assigne la branche déviée
setBranchPointers(branches) Assigne les trois en une opération
replaceBranchPointer(old, new) Substitution après absorption (Phase8)

État opérationnel — branche active

m_activeBranch représente la position physique de l'aiguillage (NORMAL par défaut). Il est modifié en runtime par l'opérateur via l'IHM.

Lors d'une modification, la valeur est propagée automatiquement aux aiguillages partenaires d'un double switch via getPartnerOnNormal() / getPartnerOnDeviation(), sauf si propagate = false est passé explicitement (pour éviter la récursion infinie lors de la réception d'une propagation).

Opérateur clique sur sw/0 sw/0.toggleActiveBranch()
→ m_activeBranch = DEVIATION → sw/0.setActiveBranch(DEVIATION, true)
→ propagation vers sw/1 (partenaire) → sw/1.setActiveBranch(DEVIATION, false)
Méthode Rôle
getActiveBranch() Branche active courante
isDeviationActive() Raccourci booléen
setActiveBranch(branch, propagate=true) Assigne + propage optionnellement
toggleActiveBranch(propagate=true) Alterne + propage + retourne la nouvelle valeur

Pointeurs résolus — récapitulatif

Les deux structs de pointeurs suivent la même convention :

  • Propriété : TopologyRepository — ne jamais delete ces pointeurs.
  • Validité : garantie uniquement après Phase9_RepositoryTransfer::resolve().
  • Valeur nulle : nullptr indique un terminus ou une branche non résolue.

StraightBlock::StraightNeighbours

Champ Type Description
prev ShuntingElement* Bloc adjacent à l'extrémité A — nullptr si terminus
next ShuntingElement* Bloc adjacent à l'extrémité B — nullptr si terminus

SwitchBlock::SwitchBranches

Champ Type Description
root ShuntingElement* Tronc entrant — nullptr si non résolu
normal ShuntingElement* Sortie directe — nullptr si non résolu
deviation ShuntingElement* Sortie déviée — nullptr si non résolu

Énumérations

Enum Valeurs Usage
ElementType SWITCH, STRAIGHT Typage sans RTTI — dispatching dans PCCGraphBuilder et GeoParser
ShuntingState FREE, OCCUPIED, INACTIVE État opérationnel de l'infrastructure
ActiveBranch NORMAL, DEVIATION Position physique de l'aiguillage — modifiée en runtime