onvaou

Guide des Correspondances GTFS

🎯 Réponse à votre question

Oui, la base de données est maintenant capable de gérer les correspondances entre gares !

Le script createGTFSDatabase.js crée :

  1. Vue direct_connections : Connexions au sein d’un même trajet
  2. Vue transfer_opportunities : Toutes les correspondances possibles entre trajets
  3. Index optimisés : Pour des recherches ultra-rapides de correspondances
  4. Table optionnelle : Correspondances pré-calculées (si activée)

📊 Architecture de la base de données

Tables principales

Vues créées pour les correspondances

1. Vue direct_connections

Toutes les connexions possibles au sein d’un même trajet.

-- Exemple: Paris → Lyon (sans changer de train)
SELECT * FROM direct_connections
WHERE from_stop_name LIKE '%Paris%'
  AND to_stop_name LIKE '%Lyon%'
LIMIT 5;

2. Vue transfer_opportunities ⭐ NOUVEAU

Toutes les correspondances possibles dans chaque gare.

Filtre automatique :

-- Exemple: Toutes les correspondances à Lyon Part-Dieu
SELECT * FROM transfer_opportunities
WHERE transfer_stop_name LIKE '%Lyon Part%'
LIMIT 10;

🚀 Utilisation dans votre application

Service de base : gtfsDatabaseService.ts

Le service de base inclut une méthode simple pour trouver des correspondances :

import { gtfsDb } from './src/services/gtfsDatabaseService';

// Recherche avec correspondances (méthode basique)
const journeys = await gtfsDb.findJourney(
  'StopID:...',  // Départ
  'StopID:...',  // Arrivée
  '08:00:00',    // Heure de départ
  new Date(),    // Date
  2              // Max 2 correspondances
);

// journeys = [
//   [connection1, connection2],  // Trajet avec 1 correspondance
//   [connection3],                // Trajet direct
// ]

Service amélioré : gtfsDatabaseServiceEnhanced.ts ⭐ RECOMMANDÉ

Le service amélioré utilise des requêtes SQL optimisées pour des performances maximales :

import { gtfsDbEnhanced } from './src/services/gtfsDatabaseServiceEnhanced';

await gtfsDbEnhanced.initialize();

// Recherche COMPLÈTE avec toutes les options
const journeys = await gtfsDbEnhanced.findAllJourneys(
  'StopID:DUA8711300:LOC',  // Paris Gare de Lyon
  'StopID:DUA8727100:LOC',  // Lyon Part Dieu
  '08:00:00',
  2  // Max 2 correspondances
);

// Résultat trié par durée totale
journeys.forEach(journey => {
  console.log(`Durée: ${journey.totalDuration} min`);
  console.log(`Correspondances: ${journey.legs.length - 1}`);
  if (journey.transferStation) {
    console.log(`Via: ${journey.transferStation}`);
    console.log(`Temps d'attente: ${journey.transferTime} min`);
  }

  journey.legs.forEach((leg, i) => {
    console.log(`  ${i+1}. ${leg.from_stop_name}${leg.to_stop_name}`);
    console.log(`     ${leg.departure_time}${leg.arrival_time}`);
    console.log(`     ${leg.route_short_name}`);
  });
});

🔍 Exemples de requêtes SQL

1. Trouver tous les trajets avec 1 correspondance

SELECT
  leg1.from_stop_name as depart,
  leg1.departure_time,
  leg1.to_stop_name as correspondance,
  leg1.arrival_time as arrivee_correspondance,
  leg2.departure_time as depart_correspondance,
  leg2.to_stop_name as arrivee,
  leg2.arrival_time,
  -- Temps de correspondance en minutes
  (CAST(substr(leg2.departure_time, 1, 2) AS INTEGER) * 60 +
   CAST(substr(leg2.departure_time, 4, 2) AS INTEGER)) -
  (CAST(substr(leg1.arrival_time, 1, 2) AS INTEGER) * 60 +
   CAST(substr(leg1.arrival_time, 4, 2) AS INTEGER)) as temps_correspondance_min,
  -- Durée totale
  (CAST(substr(leg2.arrival_time, 1, 2) AS INTEGER) * 60 +
   CAST(substr(leg2.arrival_time, 4, 2) AS INTEGER)) -
  (CAST(substr(leg1.departure_time, 1, 2) AS INTEGER) * 60 +
   CAST(substr(leg1.departure_time, 4, 2) AS INTEGER)) as duree_totale_min
FROM direct_connections leg1
JOIN direct_connections leg2
  ON leg1.to_stop_id = leg2.from_stop_id
  AND leg1.trip_id != leg2.trip_id
  AND leg2.departure_time > leg1.arrival_time
WHERE leg1.from_stop_name LIKE '%Paris%'
  AND leg2.to_stop_name LIKE '%Marseille%'
  AND leg1.departure_time >= '08:00:00'
  AND temps_correspondance_min >= 5
  AND temps_correspondance_min <= 60
ORDER BY duree_totale_min
LIMIT 10;

2. Trouver les gares avec le plus de correspondances

SELECT
  transfer_stop_name,
  COUNT(DISTINCT departure_trip_id) as nb_trains_au_depart,
  AVG(transfer_time_minutes) as temps_attente_moyen,
  MIN(transfer_time_minutes) as temps_attente_min
FROM transfer_opportunities
GROUP BY transfer_stop_name
ORDER BY nb_trains_au_depart DESC
LIMIT 20;

3. Vérifier les correspondances disponibles pour un trajet spécifique

SELECT
  transfer_stop_name,
  arrival_time,
  COUNT(*) as nb_connexions_disponibles,
  MIN(departure_time) as prochain_depart,
  MIN(transfer_time_minutes) as temps_attente_min
FROM transfer_opportunities
WHERE arrival_trip_id = 'TRIP_ID_HERE'
GROUP BY transfer_stop_name, arrival_time
ORDER BY transfer_time_minutes;

⚙️ Optimisations et performances

Index créés automatiquement

Le script crée automatiquement ces index pour accélérer les recherches :

-- Index pour les correspondances
CREATE INDEX idx_stop_times_stop_arrival ON stop_times(stop_id, arrival_time);
CREATE INDEX idx_stop_times_stop_departure ON stop_times(stop_id, departure_time);
CREATE INDEX idx_stop_times_compound ON stop_times(stop_id, trip_id, departure_time, arrival_time);

Table de correspondances pré-calculées (optionnel)

Pour des performances maximales, vous pouvez activer la table precalculated_transfers :

// Dans createGTFSDatabase.js, ligne 700
createTransferTable(db, true);  // ⚠️ Peut être long et volumineux

Cette table pré-calcule toutes les correspondances possibles. C’est très rapide à interroger mais :

Recommandation : Commencez sans cette table. Les vues SQL sont déjà très rapides. Activez-la uniquement si vous avez des problèmes de performance.


📱 Exemple d’interface utilisateur

Affichage d’un trajet avec correspondance

const JourneyDisplay = ({ journey }: { journey: JourneyWithTransfer }) => {
  return (
    <View style={styles.journey}>
      {/* Résumé */}
      <Text style={styles.duration}>
        Durée totale: {Math.floor(journey.totalDuration / 60)}h
        {journey.totalDuration % 60}min
      </Text>

      {journey.legs.length > 1 && (
        <Text style={styles.transfers}>
          {journey.legs.length - 1} correspondance(s)
        </Text>
      )}

      {/* Chaque tronçon */}
      {journey.legs.map((leg, index) => (
        <View key={index}>
          <View style={styles.leg}>
            <Text style={styles.station}>{leg.from_stop_name}</Text>
            <Text style={styles.time}>{leg.departure_time}</Text>
            <Text style={styles.route}>{leg.route_short_name}</Text>

            <Text style={styles.arrow}></Text>

            <Text style={styles.station}>{leg.to_stop_name}</Text>
            <Text style={styles.time}>{leg.arrival_time}</Text>
          </View>

          {/* Correspondance */}
          {index < journey.legs.length - 1 && (
            <View style={styles.transfer}>
              <Icon name="swap-horizontal" />
              <Text>Correspondance ({journey.transferTime} min d'attente)</Text>
            </View>
          )}
        </View>
      ))}
    </View>
  );
};

🎯 Cas d’usage avancés

1. Recherche intelligente

L’algorithme privilégie automatiquement :

  1. Trajets directs (0 correspondance)
  2. 1 correspondance avec temps d’attente court
  3. 2 correspondances uniquement si nécessaire
// La fonction findAllJourneys gère tout ça automatiquement !
const journeys = await gtfsDbEnhanced.findAllJourneys(
  fromStopId,
  toStopId,
  departureTime,
  2  // maxTransfers
);

2. Filtrage par date

// Obtenir les trajets valides pour une date spécifique
const allJourneys = await gtfsDbEnhanced.findAllJourneys(/*...*/);
const validJourneys = await gtfsDbEnhanced.filterJourneysByDate(
  allJourneys,
  new Date('2025-12-25')  // Jour de Noël
);

3. Suggestions de gares de correspondance

// Trouver les meilleures gares pour une correspondance
const query = `
  SELECT
    leg1.to_stop_name as gare_correspondance,
    COUNT(*) as nb_options,
    AVG(
      (CAST(substr(leg2.departure_time, 1, 2) AS INTEGER) * 60 +
       CAST(substr(leg2.departure_time, 4, 2) AS INTEGER)) -
      (CAST(substr(leg1.arrival_time, 1, 2) AS INTEGER) * 60 +
       CAST(substr(leg1.arrival_time, 4, 2) AS INTEGER))
    ) as temps_attente_moyen
  FROM direct_connections leg1
  JOIN direct_connections leg2
    ON leg1.to_stop_id = leg2.from_stop_id
  WHERE leg1.from_stop_id = ?
    AND leg2.to_stop_id = ?
  GROUP BY leg1.to_stop_name
  ORDER BY nb_options DESC, temps_attente_moyen ASC
  LIMIT 5;
`;

🔧 Configuration

Paramètres de correspondance

Vous pouvez ajuster les limites dans la vue transfer_opportunities :

-- Dans createGTFSDatabase.js, fonction createViews()

WHERE transfer_time_minutes >= 5    -- Minimum: 5 minutes
  AND transfer_time_minutes <= 120; -- Maximum: 2 heures

Modifiez ces valeurs selon vos besoins :


📊 Comparaison des méthodes

Méthode Vitesse Correspondances Complexité
Fichiers JSON 🐌 Lent ❌ Complexe 😰 Élevée
Vue direct_connections ⚡ Rapide ⚠️ Même trajet uniquement 😊 Simple
Vue transfer_opportunities ⚡ Rapide ✅ Oui 😊 Simple
Service Enhanced 🚀 Ultra-rapide ✅ Oui (SQL optimisé) 😎 Très simple
Table pré-calculée 💨 Instantané ✅ Oui 😌 Moyen

✅ Conclusion

Oui, la base de données gère complètement les correspondances !

Ce qui est créé automatiquement :

Comment utiliser :

  1. Méthode simple : gtfsDb.findJourney() - boucles TypeScript
  2. Méthode optimale : gtfsDbEnhanced.findAllJourneys() - SQL pur ⭐

Performances :

Tout est prêt pour gérer les correspondances de manière optimale ! 🚀