RELEASE V5.6.0 : Voici les modifications apportées :
-> REFONTE TOTALE DE TOUS LES AFFICHEURS CLASSIQUES + QUAIS + OPTION TGV FONCTIONNALITÉS EN COURS DE DÉVELOPPEMENT : -> ADAPTABILITÉ EN FONCTION DES SITUATIONS
This commit is contained in:
1 parent
9c6ac798f1
commit
d8f8ceaa9f
18 files changed
+2324
-533
No files matched your search
+546
-85
@@ -5,23 +5,81 @@ import { findGtfsGare, searchGtfsGares } from './gtfsStations';
|
||||
const SNCF_GTFS_RT_TRIP_UPDATES = 'https://proxy.transport.data.gouv.fr/resource/sncf-gtfs-rt-trip-updates';
|
||||
const SNCF_GTFS_RT_SERVICE_ALERTS = 'https://proxy.transport.data.gouv.fr/resource/sncf-gtfs-rt-service-alerts';
|
||||
const SNCF_SIRI_LITE_TIMETABLE = 'https://proxy.transport.data.gouv.fr/resource/sncf-siri-lite-estimated-timetable';
|
||||
const SNCF_SIRI_LITE_SITUATION_EXCHANGE = 'https://proxy.transport.data.gouv.fr/resource/sncf-siri-lite-situation-exchange';
|
||||
const NAVITIA_BASE_URL = 'https://api.sncf.com/v1';
|
||||
const NAVITIA_TOKEN = (typeof process !== 'undefined' && process.env && process.env.REACT_APP_NAVITIA_TOKEN) || 'bf06df97-2ff1-4560-b74c-f127cb70764d';
|
||||
|
||||
// Cache mémoire pour le flux GTFS-RT (TTL = 30s)
|
||||
// TTL de rafraîchissement des flux temps réel SNCF : 2 minutes (120 secondes)
|
||||
const REALTIME_CACHE_TTL = 120000;
|
||||
|
||||
// Cache mémoire pour le flux GTFS-RT (TTL = 2 min)
|
||||
let gtfsRtCache = {
|
||||
timestamp: 0,
|
||||
updates: new Map(),
|
||||
alerts: []
|
||||
};
|
||||
|
||||
// Cache mémoire pour le flux SIRI-Lite (TTL = 30s)
|
||||
// Cache mémoire pour le flux SIRI-Lite Estimated Timetable (TTL = 2 min)
|
||||
let siriLiteCache = {
|
||||
timestamp: 0,
|
||||
platforms: new Map(),
|
||||
updates: new Map()
|
||||
};
|
||||
|
||||
// Cache mémoire pour le flux SIRI-Lite Situation Exchange (TTL = 2 min)
|
||||
let situationExchangeCache = {
|
||||
timestamp: 0,
|
||||
platforms: new Map(),
|
||||
updates: new Map(),
|
||||
alerts: []
|
||||
};
|
||||
|
||||
// Cache mémoire pour l'API OpenData SNCF (TTL = 2 min)
|
||||
const openDataPlatformsCache = new Map();
|
||||
|
||||
const MAJOR_HUBS = [
|
||||
'paris', 'lyon', 'dijon', 'marseille', 'lille', 'strasbourg', 'bordeaux',
|
||||
'toulouse', 'nantes', 'rennes', 'nice', 'montpellier', 'nimes', 'metz',
|
||||
'nancy', 'mulhouse', 'tours', 'le mans', 'rouen', 'clermont ferrand',
|
||||
'grenoble', 'chambery', 'avignon', 'valence', 'besancon viotte',
|
||||
'saint etienne', 'toulon', 'cannes', 'antibes', 'angers'
|
||||
];
|
||||
|
||||
const MAJOR_HUB_UICS = [
|
||||
'87713040', '87686006', '87271007', '87391003', '87113001', '87384008',
|
||||
'87547000', '87686667', '87722025', '87723197', '87751008', '87286005',
|
||||
'87286013', '87212027', '87581009', '87611004', '87481002', '87471003',
|
||||
'87756056', '87773002', '87775007', '87192039', '87141002', '87182063',
|
||||
'87571000', '87444000', '87411017', '87734004', '87747006', '87741009',
|
||||
'87765008', '87318964', '87763029', '87718007', '87726000', '87755009',
|
||||
'87757625', '87757674', '87484006'
|
||||
];
|
||||
|
||||
export function isMajorHub(stationName, uic = '') {
|
||||
if (uic && MAJOR_HUB_UICS.includes(String(uic).replace(/\D/g, ''))) return true;
|
||||
if (!stationName) return false;
|
||||
const clean = String(stationName).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
return MAJOR_HUBS.some(hub => clean === hub || clean.startsWith(hub + ' ') || clean.endsWith(' ' + hub));
|
||||
}
|
||||
|
||||
export function isCarNumber(num) {
|
||||
const d = String(num || '').replace(/\D/g, '');
|
||||
return (d.length === 5 && d.startsWith('3')) || (d.length === 6 && d.startsWith('3'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Résout la voie pour une circulation :
|
||||
* - Aucune voie nominale/fictive inventée : seuls les quais temps réel SNCF sont affichés.
|
||||
* - Les services par autocar affichent 'AUTOCAR' (pictogramme).
|
||||
*/
|
||||
export function resolveNominalTrack(stationName, trainNum, uic = '') {
|
||||
const rawNum = String(trainNum || '').replace(/\D/g, '');
|
||||
if (isCarNumber(rawNum)) return 'AUTOCAR';
|
||||
return '';
|
||||
}
|
||||
|
||||
// Helper fetch avec timeout pour ne jamais bloquer l'affichage en cas de réseau lent ou de blocage
|
||||
async function fetchWithTimeout(url, options = {}, timeoutMs = 7000) {
|
||||
async function fetchWithTimeout(url, options = {}, timeoutMs = 25000) {
|
||||
const controller = new AbortController();
|
||||
const id = setTimeout(() => controller.abort(), timeoutMs);
|
||||
try {
|
||||
@@ -40,12 +98,53 @@ function cleanNum(n) {
|
||||
return m ? m[0].replace(/^0+/, '') : String(n).trim();
|
||||
}
|
||||
|
||||
function extractAllTrainNumbers(journeyXml) {
|
||||
const nums = new Set();
|
||||
|
||||
// 1. TrainNumberRef
|
||||
const tnr = journeyXml.match(/<TrainNumberRef>([^<]+)<\/TrainNumberRef>/);
|
||||
if (tnr) {
|
||||
const c = cleanNum(tnr[1]);
|
||||
if (c) nums.add(c);
|
||||
}
|
||||
|
||||
// 2. VehicleJourneyName
|
||||
const vjn = journeyXml.match(/<VehicleJourneyName>([^<]+)<\/VehicleJourneyName>/);
|
||||
if (vjn) {
|
||||
const c = cleanNum(vjn[1]);
|
||||
if (c) nums.add(c);
|
||||
}
|
||||
|
||||
// 3. PublishedLineName if numeric
|
||||
const pln = journeyXml.match(/<PublishedLineName>([^<]+)<\/PublishedLineName>/);
|
||||
if (pln) {
|
||||
const m = pln[1].match(/^\d+$/);
|
||||
if (m) nums.add(cleanNum(m[0]));
|
||||
}
|
||||
|
||||
// 4. DatedVehicleJourneyRef / VehicleJourneyRef / FramedVehicleJourneyRef
|
||||
const refs = journeyXml.match(/<(?:DatedVehicleJourneyRef|VehicleJourneyRef|FramedVehicleJourneyRef)>([\s\S]*?)<\/(?:DatedVehicleJourneyRef|VehicleJourneyRef|FramedVehicleJourneyRef)>/g) || [];
|
||||
for (const r of refs) {
|
||||
const vjMatch = r.match(/FR:VehicleJourney::(\d{3,7})/i) || r.match(/VehicleJourney::(\d{3,7})/i) || r.match(/::(\d{3,7})/);
|
||||
if (vjMatch) {
|
||||
nums.add(cleanNum(vjMatch[1]));
|
||||
}
|
||||
const lrMatch = r.match(/LineRef::(\d{3,7})/i);
|
||||
if (lrMatch) {
|
||||
nums.add(cleanNum(lrMatch[1]));
|
||||
}
|
||||
}
|
||||
|
||||
return Array.from(nums);
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère et décode les flux temps réel SIRI Lite SNCF (Quais réels et horaires estimés)
|
||||
* Récupère et décode le flux SIRI Lite SNCF officiel en temps réel (circulations dans les 60 prochaines minutes)
|
||||
* Source : https://proxy.transport.data.gouv.fr/resource/sncf-siri-lite-estimated-timetable
|
||||
*/
|
||||
async function fetchSiriLiteData() {
|
||||
const now = Date.now();
|
||||
if (now - siriLiteCache.timestamp < 30000 && siriLiteCache.platforms.size > 0) {
|
||||
if (now - siriLiteCache.timestamp < REALTIME_CACHE_TTL && siriLiteCache.platforms.size > 0) {
|
||||
return siriLiteCache;
|
||||
}
|
||||
|
||||
@@ -53,79 +152,112 @@ async function fetchSiriLiteData() {
|
||||
const updatesMap = new Map();
|
||||
|
||||
try {
|
||||
const res = await fetchWithTimeout(SNCF_SIRI_LITE_TIMETABLE, {}, 8000);
|
||||
const res = await fetchWithTimeout(SNCF_SIRI_LITE_TIMETABLE, {
|
||||
headers: {
|
||||
'Accept': 'application/xml, text/xml, */*'
|
||||
}
|
||||
}, 25000);
|
||||
|
||||
if (res && res.ok) {
|
||||
const xml = await res.text();
|
||||
const journeys = xml.split('<EstimatedVehicleJourney>');
|
||||
|
||||
for (let i = 1; i < journeys.length; i++) {
|
||||
const j = journeys[i];
|
||||
const trainNumMatch = j.match(/<TrainNumberRef>([^<]+)<\/TrainNumberRef>/);
|
||||
const rawTrainNum = trainNumMatch ? trainNumMatch[1].trim() : '';
|
||||
const trainNum = cleanNum(rawTrainNum);
|
||||
|
||||
const journeyRefMatch = j.match(/<DatedVehicleJourneyRef>([^<]+)<\/DatedVehicleJourneyRef>/) || j.match(/<VehicleJourneyRef>([^<]+)<\/VehicleJourneyRef>/);
|
||||
// 1. Extraction exhaustive des identifiants et numéros de train
|
||||
const trainNums = extractAllTrainNumbers(j);
|
||||
|
||||
const journeyRefMatch = j.match(/<DatedVehicleJourneyRef>([^<]+)<\/DatedVehicleJourneyRef>/) || j.match(/<VehicleJourneyRef>([^<]+)<\/VehicleJourneyRef>/) || j.match(/<FramedVehicleJourneyRef>[\s\S]*?<DatedVehicleJourneyRef>([^<]+)<\/DatedVehicleJourneyRef>/);
|
||||
const journeyRef = journeyRefMatch ? journeyRefMatch[1].trim() : '';
|
||||
|
||||
const isCanceled = j.includes('<Cancellation>true</Cancellation>');
|
||||
const isCanceledJourney = j.includes('<Cancellation>true</Cancellation>') || j.includes('<JourneyStatus>cancelled</JourneyStatus>');
|
||||
|
||||
// Extraire les arrêts et quais (RecordedCall et EstimatedCall)
|
||||
const callMatches = j.match(/<(RecordedCall|EstimatedCall)>[\s\S]*?<\/\1>/g) || [];
|
||||
// 2. Extraire tous les arrêts desservis (RecordedCall et EstimatedCall)
|
||||
const callMatches = j.match(/<(?:RecordedCall|EstimatedCall)>[\s\S]*?<\/(?:RecordedCall|EstimatedCall)>/g) || [];
|
||||
for (const call of callMatches) {
|
||||
const stopRefMatch = call.match(/<StopPointRef>[^:]*::(\d+)<\/StopPointRef>/) || call.match(/<StopPointRef>([^<]+)<\/StopPointRef>/);
|
||||
const uicMatch = stopRefMatch ? (stopRefMatch[1].match(/\d{7,8}/) || [stopRefMatch[1]]) : null;
|
||||
const uic = uicMatch ? uicMatch[0] : '';
|
||||
// Extraction du code UIC de l'arrêt
|
||||
const stopRefMatch = call.match(/<StopPointRef>([^<]+)<\/StopPointRef>/);
|
||||
let uic = '';
|
||||
let uic7 = '';
|
||||
if (stopRefMatch) {
|
||||
const rawRef = stopRefMatch[1];
|
||||
const uicDigits = rawRef.match(/\d{7,8}/);
|
||||
uic = uicDigits ? uicDigits[0] : rawRef.replace(/\D/g, '');
|
||||
if (uic.length === 8 && uic.startsWith('87')) {
|
||||
uic7 = uic.slice(2);
|
||||
}
|
||||
}
|
||||
|
||||
const stopNameMatch = call.match(/<StopPointName>([^<]+)<\/StopPointName>/);
|
||||
const stopNameClean = stopNameMatch ? formatStationName(stopNameMatch[1]).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
|
||||
|
||||
const depPlatMatch = call.match(/<DeparturePlatformName>([^<]+)<\/DeparturePlatformName>/);
|
||||
const arrPlatMatch = call.match(/<ArrivalPlatformName>([^<]+)<\/ArrivalPlatformName>/);
|
||||
const platform = (depPlatMatch ? depPlatMatch[1] : (arrPlatMatch ? arrPlatMatch[1] : '')).trim();
|
||||
// Extraction des quais (Départ, Arrivée, Prévu ou Réel)
|
||||
const depPlatMatch = call.match(/<(?:DeparturePlatformName|PlannedDeparturePlatformName)>([^<]+)<\/(?:DeparturePlatformName|PlannedDeparturePlatformName)>/);
|
||||
const arrPlatMatch = call.match(/<(?:ArrivalPlatformName|PlannedArrivalPlatformName)>([^<]+)<\/(?:ArrivalPlatformName|PlannedArrivalPlatformName)>/);
|
||||
const quayMatch = call.match(/<QuayRef>([^<]+)<\/QuayRef>/);
|
||||
|
||||
let rawPlatform = (depPlatMatch ? depPlatMatch[1] : (arrPlatMatch ? arrPlatMatch[1] : (quayMatch ? quayMatch[1] : ''))).trim();
|
||||
const platform = rawPlatform.replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
|
||||
|
||||
// Extraction des heures et calcul du retard
|
||||
const expDepMatch = call.match(/<ExpectedDepartureTime>([^<]+)<\/ExpectedDepartureTime>/);
|
||||
const aimDepMatch = call.match(/<AimedDepartureTime>([^<]+)<\/AimedDepartureTime>/);
|
||||
const expArrMatch = call.match(/<ExpectedArrivalTime>([^<]+)<\/ExpectedArrivalTime>/);
|
||||
const aimArrMatch = call.match(/<AimedArrivalTime>([^<]+)<\/AimedArrivalTime>/);
|
||||
|
||||
let delayMin = 0;
|
||||
if (expDepMatch && aimDepMatch) {
|
||||
const expD = new Date(expDepMatch[1]).getTime();
|
||||
const aimD = new Date(aimDepMatch[1]).getTime();
|
||||
const expTimeStr = (expDepMatch && expDepMatch[1]) || (expArrMatch && expArrMatch[1]);
|
||||
const aimTimeStr = (aimDepMatch && aimDepMatch[1]) || (aimArrMatch && aimArrMatch[1]);
|
||||
|
||||
if (expTimeStr && aimTimeStr) {
|
||||
const expD = new Date(expTimeStr).getTime();
|
||||
const aimD = new Date(aimTimeStr).getTime();
|
||||
if (!isNaN(expD) && !isNaN(aimD)) {
|
||||
delayMin = Math.max(0, Math.round((expD - aimD) / 60000));
|
||||
}
|
||||
}
|
||||
|
||||
const isStopCancelled = isCanceledJourney || call.includes('<DepartureStatus>cancelled</DepartureStatus>') || call.includes('<ArrivalStatus>cancelled</ArrivalStatus>') || call.includes('<Cancellation>true</Cancellation>');
|
||||
|
||||
if (platform) {
|
||||
if (uic) {
|
||||
if (trainNum) {
|
||||
platformsMap.set(`${trainNum}_${uic}`, platform);
|
||||
platformsMap.set(`${uic}_${trainNum}`, platform);
|
||||
for (const tNum of trainNums) {
|
||||
if (uic) {
|
||||
platformsMap.set(`${tNum}_${uic}`, platform);
|
||||
platformsMap.set(`${uic}_${tNum}`, platform);
|
||||
}
|
||||
if (rawTrainNum) {
|
||||
platformsMap.set(`${rawTrainNum}_${uic}`, platform);
|
||||
platformsMap.set(`${uic}_${rawTrainNum}`, platform);
|
||||
if (uic7) {
|
||||
platformsMap.set(`${tNum}_${uic7}`, platform);
|
||||
platformsMap.set(`${uic7}_${tNum}`, platform);
|
||||
}
|
||||
if (journeyRef) {
|
||||
if (stopNameClean) {
|
||||
platformsMap.set(`${tNum}_${stopNameClean}`, platform);
|
||||
}
|
||||
}
|
||||
if (journeyRef) {
|
||||
if (uic) {
|
||||
platformsMap.set(`${journeyRef}_${uic}`, platform);
|
||||
platformsMap.set(`${uic}_${journeyRef}`, platform);
|
||||
}
|
||||
}
|
||||
if (stopNameClean) {
|
||||
if (trainNum) platformsMap.set(`${trainNum}_${stopNameClean}`, platform);
|
||||
if (rawTrainNum) platformsMap.set(`${rawTrainNum}_${stopNameClean}`, platform);
|
||||
if (journeyRef) platformsMap.set(`${journeyRef}_${stopNameClean}`, platform);
|
||||
if (stopNameClean) {
|
||||
platformsMap.set(`${journeyRef}_${stopNameClean}`, platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (trainNum || rawTrainNum || journeyRef) {
|
||||
const info = {
|
||||
platform: platform,
|
||||
delayMinutes: delayMin,
|
||||
isCanceled: isCanceled
|
||||
};
|
||||
if (trainNum) updatesMap.set(trainNum, info);
|
||||
if (rawTrainNum) updatesMap.set(rawTrainNum, info);
|
||||
if (journeyRef) updatesMap.set(journeyRef, info);
|
||||
const info = {
|
||||
platform: platform,
|
||||
delayMinutes: delayMin,
|
||||
isCanceled: isStopCancelled
|
||||
};
|
||||
|
||||
for (const tNum of trainNums) {
|
||||
if (uic) updatesMap.set(`${tNum}_${uic}`, info);
|
||||
updatesMap.set(tNum, info);
|
||||
}
|
||||
if (journeyRef) {
|
||||
if (uic) updatesMap.set(`${journeyRef}_${uic}`, info);
|
||||
updatesMap.set(journeyRef, info);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,12 +269,298 @@ async function fetchSiriLiteData() {
|
||||
updates: updatesMap
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn('[sncfApi] Notice: SIRI-Lite refresh error/timeout:', err);
|
||||
console.warn('[sncfApi] Notice: SIRI-Lite Estimated Timetable refresh error/timeout:', err);
|
||||
}
|
||||
|
||||
return siriLiteCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère et décode le flux SIRI Lite Situation Exchange SNCF (Circulations à venir, perturbations, changements de voie, suppressions)
|
||||
* Source : https://proxy.transport.data.gouv.fr/resource/sncf-siri-lite-situation-exchange
|
||||
*/
|
||||
async function fetchSituationExchangeData() {
|
||||
const now = Date.now();
|
||||
if (now - situationExchangeCache.timestamp < REALTIME_CACHE_TTL && situationExchangeCache.platforms.size > 0) {
|
||||
return situationExchangeCache;
|
||||
}
|
||||
|
||||
const platformsMap = new Map();
|
||||
const updatesMap = new Map();
|
||||
const alertsList = [];
|
||||
|
||||
try {
|
||||
const res = await fetchWithTimeout(SNCF_SIRI_LITE_SITUATION_EXCHANGE, {
|
||||
headers: {
|
||||
'Accept': 'application/xml, text/xml, */*'
|
||||
}
|
||||
}, 12000);
|
||||
|
||||
if (res && res.ok) {
|
||||
const xml = await res.text();
|
||||
const situations = xml.split('<PtSituationElement>');
|
||||
|
||||
for (let i = 1; i < situations.length; i++) {
|
||||
const s = situations[i];
|
||||
|
||||
// 1. Extraction exhaustive du train et de la mission
|
||||
const trainNums = extractAllTrainNumbers(s);
|
||||
const trainNumMatch = s.match(/<TrainNumberRef>([^<]+)<\/TrainNumberRef>/) || s.match(/<VehicleJourneyRef>([^<]+)<\/VehicleJourneyRef>/) || s.match(/<PublishedLineName>([^<]+)<\/PublishedLineName>/);
|
||||
const rawTrainNum = trainNumMatch ? trainNumMatch[1].trim() : '';
|
||||
const primaryTrainNum = cleanNum(rawTrainNum);
|
||||
if (primaryTrainNum && !trainNums.includes(primaryTrainNum)) {
|
||||
trainNums.push(primaryTrainNum);
|
||||
}
|
||||
|
||||
// 2. Extraction de l'arrêt impacté
|
||||
const stopRefMatch = s.match(/<StopPointRef>([^<]+)<\/StopPointRef>/);
|
||||
let uic = '';
|
||||
let uic7 = '';
|
||||
if (stopRefMatch) {
|
||||
const rawRef = stopRefMatch[1];
|
||||
const uicDigits = rawRef.match(/\d{7,8}/);
|
||||
uic = uicDigits ? uicDigits[0] : rawRef.replace(/\D/g, '');
|
||||
if (uic.length === 8 && uic.startsWith('87')) {
|
||||
uic7 = uic.slice(2);
|
||||
}
|
||||
}
|
||||
|
||||
const stopNameMatch = s.match(/<StopPointName>([^<]+)<\/StopPointName>/);
|
||||
const stopNameClean = stopNameMatch ? formatStationName(stopNameMatch[1]).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
|
||||
|
||||
// 3. Extraction des messages en français
|
||||
const summaryFrMatch = s.match(/<Summary[^>]*xml:lang="FR"[^>]*>([^<]+)<\/Summary>/i) || s.match(/<Summary[^>]*>([^<]+)<\/Summary>/i);
|
||||
const summary = summaryFrMatch ? summaryFrMatch[1].trim() : '';
|
||||
|
||||
const descFrMatch = s.match(/<Description[^>]*xml:lang="FR"[^>]*>([^<]+)<\/Description>/i) || s.match(/<Description[^>]*>([^<]+)<\/Description>/i);
|
||||
const desc = descFrMatch ? descFrMatch[1].trim() : '';
|
||||
|
||||
const messageText = desc || summary || '';
|
||||
|
||||
// 4. Détection de changement de quai / voie
|
||||
const platTagMatch = s.match(/<(?:DeparturePlatformName|PlannedDeparturePlatformName|ArrivalPlatformName|PlannedArrivalPlatformName|QuayRef)>([^<]+)<\//i);
|
||||
let platform = platTagMatch ? platTagMatch[1].replace(/^(?:voie|quai|track|platform)\s*/i, '').trim() : '';
|
||||
|
||||
if (!platform && messageText) {
|
||||
const voieTextMatch = messageText.match(/\b(?:voie|quai)\s+([A-Za-z0-9]+)\b/i);
|
||||
if (voieTextMatch) {
|
||||
platform = voieTextMatch[1].trim();
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Détection de suppression
|
||||
const isCanceled = /supprim|annul|gelöscht|cancelled|interrompu/i.test(messageText) || s.includes('<Cancellation>true</Cancellation>');
|
||||
|
||||
if (platform) {
|
||||
for (const tNum of trainNums) {
|
||||
if (uic) {
|
||||
platformsMap.set(`${tNum}_${uic}`, platform);
|
||||
platformsMap.set(`${uic}_${tNum}`, platform);
|
||||
}
|
||||
if (uic7) {
|
||||
platformsMap.set(`${tNum}_${uic7}`, platform);
|
||||
platformsMap.set(`${uic7}_${tNum}`, platform);
|
||||
}
|
||||
if (stopNameClean) {
|
||||
platformsMap.set(`${tNum}_${stopNameClean}`, platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const info = {
|
||||
platform: platform,
|
||||
delayMinutes: 0,
|
||||
isCanceled: isCanceled,
|
||||
cause: summary || desc || ''
|
||||
};
|
||||
|
||||
for (const tNum of trainNums) {
|
||||
if (uic) updatesMap.set(`${tNum}_${uic}`, info);
|
||||
updatesMap.set(tNum, info);
|
||||
}
|
||||
|
||||
if (messageText) {
|
||||
alertsList.push({
|
||||
header: summary,
|
||||
text: desc || summary
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
situationExchangeCache = {
|
||||
timestamp: now,
|
||||
platforms: platformsMap,
|
||||
updates: updatesMap,
|
||||
alerts: alertsList
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn('[sncfApi] Notice: SIRI-Lite Situation Exchange error/timeout:', err);
|
||||
}
|
||||
|
||||
return situationExchangeCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les quais en temps réel depuis l'API OpenData SNCF (Navitia + OpenDataSoft)
|
||||
*/
|
||||
async function fetchOpenDataSncfPlatforms(uic, mode = 'departs') {
|
||||
if (!uic) return new Map();
|
||||
const cacheKey = `${uic}_${mode}`;
|
||||
const now = Date.now();
|
||||
if (openDataPlatformsCache.has(cacheKey)) {
|
||||
const cached = openDataPlatformsCache.get(cacheKey);
|
||||
if (now - cached.timestamp < 30000) {
|
||||
return cached.platforms;
|
||||
}
|
||||
}
|
||||
|
||||
const platformsMap = new Map();
|
||||
|
||||
try {
|
||||
const authHeader = NAVITIA_TOKEN.startsWith('Basic') ? NAVITIA_TOKEN : 'Basic ' + btoa(NAVITIA_TOKEN + ':');
|
||||
const uicClean = String(uic).replace(/\D/g, '');
|
||||
const isArrival = mode === 'arrivees';
|
||||
const navitiaPath = isArrival ? 'arrivals' : 'departures';
|
||||
const odsDataset = isArrival ? 'prochaines-arrivees-des-trains-en-gare-temps-reel' : 'prochains-departs-des-trains-en-gare-temps-reel';
|
||||
|
||||
const navitiaEndpoints = [
|
||||
`https://api.sncf.com/v1/coverage/sncf/stop_areas/stop_area:OCE:SA:${uicClean}/${navitiaPath}?count=100`,
|
||||
`https://api.sncf.com/v1/coverage/sncf/stop_areas/stop_area:SNCF:${uicClean}/${navitiaPath}?count=100`,
|
||||
`https://api.sncf.com/v1/coverage/sncf/stop_points/stop_point:OCE:SP:${uicClean}/${navitiaPath}?count=100`,
|
||||
`https://api.navitia.io/v1/coverage/sncf/stop_areas/stop_area:OCE:SA:${uicClean}/${navitiaPath}?count=100`,
|
||||
`https://api.navitia.io/v1/coverage/sncf/stop_areas/stop_area:${uicClean}/${navitiaPath}?count=100`
|
||||
];
|
||||
|
||||
const [navitiaRes, odsV2Res, odsV1Res] = await Promise.allSettled([
|
||||
fetchWithTimeout(navitiaEndpoints[0], { headers: { 'Authorization': authHeader } }, 5000)
|
||||
.then(async res => {
|
||||
if (res && res.ok) return res;
|
||||
return fetchWithTimeout(navitiaEndpoints[1], { headers: { 'Authorization': authHeader } }, 5000);
|
||||
})
|
||||
.then(async res => {
|
||||
if (res && res.ok) return res;
|
||||
return fetchWithTimeout(navitiaEndpoints[3], { headers: { 'Authorization': NAVITIA_TOKEN } }, 5000);
|
||||
}),
|
||||
fetchWithTimeout(`https://ressources.data.sncf.com/api/explore/v2.1/catalog/datasets/${odsDataset}/records?limit=100&where=search(code_uic%2C%22${uicClean}%22)`, {}, 5000),
|
||||
fetchWithTimeout(`https://data.sncf.com/api/records/1.0/search/?dataset=${odsDataset}&q=${uicClean}&rows=100`, {}, 5000)
|
||||
]);
|
||||
|
||||
// 1. Décodage Navitia (API OpenData SNCF temps réel)
|
||||
if (navitiaRes.status === 'fulfilled' && navitiaRes.value && navitiaRes.value.ok) {
|
||||
const data = await navitiaRes.value.json();
|
||||
const list = (isArrival ? data.arrivals : data.departures) || [];
|
||||
for (const item of list) {
|
||||
const info = item.display_informations || {};
|
||||
const trainNum = cleanNum(info.headsign || info.trip_short_name || info.commercial_mode || info.code || '');
|
||||
const rawNum = String(info.headsign || info.trip_short_name || info.code || '').replace(/\D/g, '');
|
||||
|
||||
const stopPoint = item.stop_point || (item.stop_date_time && item.stop_date_time.stop_point) || {};
|
||||
let platform = (
|
||||
stopPoint.platform_code ||
|
||||
info.platform ||
|
||||
''
|
||||
).toString().trim();
|
||||
|
||||
if (!platform && stopPoint.label) {
|
||||
const m = stopPoint.label.match(/(?:voie|quai|track|platform)\s*([a-z0-9]+)/i);
|
||||
if (m) platform = m[1];
|
||||
}
|
||||
|
||||
platform = platform.replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
|
||||
|
||||
const rawDt = (item.stop_date_time && (item.stop_date_time.departure_date_time || item.stop_date_time.arrival_date_time || item.stop_date_time.base_departure_date_time || item.stop_date_time.base_arrival_date_time)) || '';
|
||||
let timeHM = '';
|
||||
if (rawDt.includes('T')) {
|
||||
const tPart = rawDt.split('T')[1];
|
||||
if (tPart && tPart.length >= 4) {
|
||||
timeHM = `${tPart.slice(0, 2)}:${tPart.slice(2, 4)}`;
|
||||
}
|
||||
}
|
||||
|
||||
const destClean = info.direction ? formatStationName(info.direction).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
|
||||
|
||||
if (platform) {
|
||||
if (trainNum && uicClean) {
|
||||
platformsMap.set(`${trainNum}_${uicClean}`, platform);
|
||||
platformsMap.set(`${uicClean}_${trainNum}`, platform);
|
||||
}
|
||||
if (rawNum && uicClean) {
|
||||
platformsMap.set(`${rawNum}_${uicClean}`, platform);
|
||||
platformsMap.set(`${uicClean}_${rawNum}`, platform);
|
||||
}
|
||||
if (trainNum && destClean) {
|
||||
platformsMap.set(`${trainNum}_${destClean}`, platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Décodage OpenDataSoft SNCF v2.1
|
||||
if (odsV2Res.status === 'fulfilled' && odsV2Res.value && odsV2Res.value.ok) {
|
||||
const data = await odsV2Res.value.json();
|
||||
const records = data.results || data.records || [];
|
||||
for (const r of records) {
|
||||
const fields = r.fields || r;
|
||||
const trainNum = cleanNum(fields.num || fields.numero || fields.train || fields.num_train || '');
|
||||
const rawNum = String(fields.num || fields.numero || fields.train || fields.num_train || '').replace(/\D/g, '');
|
||||
const platform = String(fields.voie || fields.quai || fields.platform || fields.track || fields.voie_quai || '').replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
|
||||
const destClean = fields.destination || fields.orig ? formatStationName(fields.destination || fields.orig).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
|
||||
|
||||
if (platform) {
|
||||
if (trainNum && uicClean) {
|
||||
platformsMap.set(`${trainNum}_${uicClean}`, platform);
|
||||
platformsMap.set(`${uicClean}_${trainNum}`, platform);
|
||||
}
|
||||
if (rawNum && uicClean) {
|
||||
platformsMap.set(`${rawNum}_${uicClean}`, platform);
|
||||
platformsMap.set(`${uicClean}_${rawNum}`, platform);
|
||||
}
|
||||
if (trainNum && destClean) {
|
||||
platformsMap.set(`${trainNum}_${destClean}`, platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Décodage OpenDataSoft SNCF v1.0
|
||||
if (odsV1Res.status === 'fulfilled' && odsV1Res.value && odsV1Res.value.ok) {
|
||||
const data = await odsV1Res.value.json();
|
||||
const records = data.records || [];
|
||||
for (const r of records) {
|
||||
const fields = r.fields || {};
|
||||
const trainNum = cleanNum(fields.num || fields.numero || fields.train || fields.num_train || '');
|
||||
const rawNum = String(fields.num || fields.numero || fields.train || fields.num_train || '').replace(/\D/g, '');
|
||||
const platform = String(fields.voie || fields.quai || fields.platform || fields.track || fields.voie_quai || '').replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
|
||||
const destClean = fields.destination || fields.orig ? formatStationName(fields.destination || fields.orig).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
|
||||
|
||||
if (platform) {
|
||||
if (trainNum && uicClean) {
|
||||
platformsMap.set(`${trainNum}_${uicClean}`, platform);
|
||||
platformsMap.set(`${uicClean}_${trainNum}`, platform);
|
||||
}
|
||||
if (rawNum && uicClean) {
|
||||
platformsMap.set(`${rawNum}_${uicClean}`, platform);
|
||||
platformsMap.set(`${uicClean}_${rawNum}`, platform);
|
||||
}
|
||||
if (trainNum && destClean) {
|
||||
platformsMap.set(`${trainNum}_${destClean}`, platform);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn('[sncfApi] Notice: OpenData SNCF platform fetch error:', err);
|
||||
}
|
||||
|
||||
openDataPlatformsCache.set(cacheKey, {
|
||||
timestamp: now,
|
||||
platforms: platformsMap
|
||||
});
|
||||
|
||||
return platformsMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère et décode les mises à jour temps réel GTFS-RT SNCF
|
||||
*/
|
||||
@@ -311,8 +729,8 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
||||
const publicUrl = (typeof process !== 'undefined' && process.env && process.env.PUBLIC_URL) ? process.env.PUBLIC_URL : '';
|
||||
const stationUrl = `${publicUrl}/data/stations/${uic}.json`;
|
||||
|
||||
// Chargement parallèle tolérant aux pannes réseau / CORS
|
||||
const [jsonRes, rtData, siriData] = await Promise.all([
|
||||
// Chargement parallèle tolérant aux pannes réseau / CORS (OpenData SNCF, SIRI-Lite Estimated Timetable, SIRI-Lite Situation Exchange, GTFS-RT)
|
||||
const [jsonRes, rtData, siriData, sxData, openDataPlatforms] = await Promise.all([
|
||||
fetch(stationUrl).catch(err => {
|
||||
console.warn(`[sncfApi] Notice: fetch station ${uic} error:`, err);
|
||||
return null;
|
||||
@@ -322,8 +740,16 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
||||
return { updates: new Map(), alerts: [] };
|
||||
}),
|
||||
fetchSiriLiteData().catch(err => {
|
||||
console.warn('[sncfApi] Notice: SIRI-Lite error:', err);
|
||||
console.warn('[sncfApi] Notice: SIRI-Lite Timetable error:', err);
|
||||
return { platforms: new Map(), updates: new Map() };
|
||||
}),
|
||||
fetchSituationExchangeData().catch(err => {
|
||||
console.warn('[sncfApi] Notice: SIRI-Lite Situation Exchange error:', err);
|
||||
return { platforms: new Map(), updates: new Map(), alerts: [] };
|
||||
}),
|
||||
fetchOpenDataSncfPlatforms(uic, mode).catch(err => {
|
||||
console.warn('[sncfApi] Notice: OpenData SNCF platform error:', err);
|
||||
return new Map();
|
||||
})
|
||||
]);
|
||||
|
||||
@@ -333,6 +759,8 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
||||
}
|
||||
|
||||
const data = await jsonRes.json();
|
||||
const gareRef = findGtfsGare(uic);
|
||||
const targetStationNom = (gareRef && gareRef.nom) || (data && data.nom) || '';
|
||||
const rawList = mode === 'arrivees' ? data.arrivees : data.departs;
|
||||
if (!Array.isArray(rawList) || rawList.length === 0) return null;
|
||||
|
||||
@@ -362,21 +790,18 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
||||
if (Array.isArray(customDaysRaw)) {
|
||||
customDays = customDaysRaw;
|
||||
} else if (typeof customDaysRaw === 'string') {
|
||||
try { customDays = JSON.parse(customDaysRaw); } catch (e) {}
|
||||
}
|
||||
if (Array.isArray(customDays) && customDays.length > 0) {
|
||||
const match = customDays.find(item => item.date === targetDateHyphen || item.date === targetYYYYMMDD);
|
||||
if (match) return Boolean(match.circule);
|
||||
try { customDays = JSON.parse(customDaysRaw); } catch (_) { customDays = []; }
|
||||
}
|
||||
if (customDays.includes(targetDateHyphen)) return true;
|
||||
}
|
||||
|
||||
// 3. Colonnes circule_lundi, circule_mardi...
|
||||
const dayNames = ['dimanche', 'lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi'];
|
||||
const colName = `circule_${dayNames[targetDay]}`;
|
||||
if (typeof t[colName] !== 'undefined' && t[colName] !== null) {
|
||||
const db = t.date_debut ? String(t.date_debut).slice(0, 10) : null;
|
||||
// 3. Base Supabase : circulation standard par jour de semaine
|
||||
const daysMap = ['circule_dimanche', 'circule_lundi', 'circule_mardi', 'circule_mercredi', 'circule_jeudi', 'circule_vendredi', 'circule_samedi'];
|
||||
const colName = daysMap[targetDay];
|
||||
if (typeof t[colName] !== 'undefined') {
|
||||
const dd = t.date_debut ? String(t.date_debut).slice(0, 10) : null;
|
||||
const df = t.date_fin ? String(t.date_fin).slice(0, 10) : null;
|
||||
if (db && targetDateHyphen < db) return false;
|
||||
if (dd && targetDateHyphen < dd) return false;
|
||||
if (df && targetDateHyphen > df) return false;
|
||||
return Boolean(t[colName]);
|
||||
}
|
||||
@@ -399,21 +824,37 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
||||
return true;
|
||||
};
|
||||
|
||||
// Helper de résolution de quai
|
||||
const lookupPlatform = (trainNum, tripId, stationUic, stationName) => {
|
||||
// Helper de résolution de quai en temps réel (Estimated Timetable + Situation Exchange + OpenData)
|
||||
const lookupPlatform = (trainNum, tripId, stationUic, stationName, rawTimeStr = '', destName = '') => {
|
||||
const cNum = cleanNum(trainNum);
|
||||
const rawNum = String(trainNum || '').replace(/\D/g, '');
|
||||
const cName = stationName ? formatStationName(stationName).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
|
||||
const cDest = destName ? formatStationName(destName).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
|
||||
const uicClean = String(stationUic || '').replace(/\D/g, '');
|
||||
const uic7 = (uicClean.length === 8 && uicClean.startsWith('87')) ? uicClean.slice(2) : '';
|
||||
|
||||
return (
|
||||
(cNum && stationUic && siriData.platforms.get(`${cNum}_${stationUic}`)) ||
|
||||
(trainNum && stationUic && siriData.platforms.get(`${trainNum}_${stationUic}`)) ||
|
||||
(stationUic && cNum && siriData.platforms.get(`${stationUic}_${cNum}`)) ||
|
||||
(stationUic && trainNum && siriData.platforms.get(`${stationUic}_${trainNum}`)) ||
|
||||
// 1. SIRI-Lite Estimated Timetable (60 prochaines min - Priorité 1)
|
||||
(cNum && uicClean && siriData.platforms.get(`${cNum}_${uicClean}`)) ||
|
||||
(rawNum && uicClean && siriData.platforms.get(`${rawNum}_${uicClean}`)) ||
|
||||
(uicClean && cNum && siriData.platforms.get(`${uicClean}_${cNum}`)) ||
|
||||
(cNum && uic7 && siriData.platforms.get(`${cNum}_${uic7}`)) ||
|
||||
(uic7 && cNum && siriData.platforms.get(`${uic7}_${cNum}`)) ||
|
||||
(cNum && cName && siriData.platforms.get(`${cNum}_${cName}`)) ||
|
||||
(trainNum && cName && siriData.platforms.get(`${trainNum}_${cName}`)) ||
|
||||
(tripId && stationUic && siriData.platforms.get(`${tripId}_${stationUic}`)) ||
|
||||
(stationUic && tripId && siriData.platforms.get(`${stationUic}_${tripId}`)) ||
|
||||
(rawNum && cName && siriData.platforms.get(`${rawNum}_${cName}`)) ||
|
||||
(tripId && uicClean && siriData.platforms.get(`${tripId}_${uicClean}`)) ||
|
||||
(tripId && cName && siriData.platforms.get(`${tripId}_${cName}`)) ||
|
||||
// 2. SIRI-Lite Situation Exchange (Circulations à venir / Changements de quai)
|
||||
(cNum && uicClean && sxData.platforms.get(`${cNum}_${uicClean}`)) ||
|
||||
(rawNum && uicClean && sxData.platforms.get(`${rawNum}_${uicClean}`)) ||
|
||||
(uicClean && cNum && sxData.platforms.get(`${uicClean}_${cNum}`)) ||
|
||||
(cNum && cName && sxData.platforms.get(`${cNum}_${cName}`)) ||
|
||||
(rawNum && cName && sxData.platforms.get(`${rawNum}_${cName}`)) ||
|
||||
// 3. OpenData SNCF direct
|
||||
(cNum && uicClean && openDataPlatforms.get(`${cNum}_${uicClean}`)) ||
|
||||
(rawNum && uicClean && openDataPlatforms.get(`${rawNum}_${uicClean}`)) ||
|
||||
(cNum && cName && openDataPlatforms.get(`${cNum}_${cName}`)) ||
|
||||
(cNum && cDest && openDataPlatforms.get(`${cNum}_${cDest}`)) ||
|
||||
''
|
||||
);
|
||||
};
|
||||
@@ -423,17 +864,30 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
||||
const rawTime = mode === 'arrivees' ? (t.arr || t.dep) : (t.dep || t.arr);
|
||||
const timeHM = parseTimeHM(rawTime) || 0;
|
||||
|
||||
// Recherche de mise à jour GTFS-RT ou SIRI-Lite (valide pour aujourd'hui)
|
||||
// Recherche de mise à jour GTFS-RT, SIRI-Lite Timetable ou SIRI-Lite Situation Exchange
|
||||
const rt = !isTomorrow ? (rtData.updates.get(t.trip_id) || (t.num ? rtData.updates.get(t.num) : null) || (cleanNum(t.num) ? rtData.updates.get(cleanNum(t.num)) : null)) : null;
|
||||
const siri = !isTomorrow ? (siriData.updates.get(t.num) || (cleanNum(t.num) ? siriData.updates.get(cleanNum(t.num)) : null) || (t.trip_id ? siriData.updates.get(t.trip_id) : null)) : null;
|
||||
const sx = !isTomorrow ? (sxData.updates.get(t.num) || (cleanNum(t.num) ? sxData.updates.get(cleanNum(t.num)) : null) || (t.trip_id ? sxData.updates.get(t.trip_id) : null)) : null;
|
||||
|
||||
const isCanceled = (rt && rt.isCanceled) || (siri && siri.isCanceled) || false;
|
||||
const isCanceled = (rt && rt.isCanceled) || (siri && siri.isCanceled) || (sx && sx.isCanceled) || false;
|
||||
const delayMinutes = (rt && rt.delayMinutes > 0) ? rt.delayMinutes : (siri && siri.delayMinutes > 0 ? siri.delayMinutes : 0);
|
||||
|
||||
const currentStationName = mode === 'arrivees' ? t.dest : t.orig;
|
||||
let realVoie = lookupPlatform(t.num, t.trip_id, uic, currentStationName);
|
||||
if (!realVoie && t.quai && typeof t.quai === 'string') {
|
||||
realVoie = t.quai.trim();
|
||||
const currentStationName = targetStationNom || (data && data.nom) || (mode === 'arrivees' ? t.dest : t.orig);
|
||||
const destinationStation = mode === 'arrivees' ? t.orig : t.dest;
|
||||
const isAutocar = (
|
||||
(t.type && (t.type.toUpperCase().includes('AUTOCAR') || t.type.toUpperCase().includes('CAR') || t.type.toUpperCase() === 'BUS')) ||
|
||||
(t.type_train && (t.type_train.toUpperCase().includes('AUTOCAR') || t.type_train.toUpperCase().includes('CAR') || t.type_train.toUpperCase() === 'BUS')) ||
|
||||
(t.op && (t.op.toUpperCase().includes('AUTOCAR') || t.op.toUpperCase().includes('CAR') || t.op.toUpperCase() === 'BUS')) ||
|
||||
Boolean(t.is_substitution) ||
|
||||
isCarNumber(t.num || t.numero_train || t.numero)
|
||||
);
|
||||
|
||||
let realVoie = lookupPlatform(t.num, t.trip_id, uic, currentStationName, rawTime, destinationStation);
|
||||
if (!realVoie && (t.quai || t.quai_reel || t.platform || t.voie)) {
|
||||
realVoie = String(t.quai || t.quai_reel || t.platform || t.voie).replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
|
||||
}
|
||||
if (!realVoie) {
|
||||
realVoie = isAutocar ? 'AUTOCAR' : resolveNominalTrack(currentStationName, t.num, uic);
|
||||
}
|
||||
|
||||
let statusLabel = "à l'heure";
|
||||
@@ -446,14 +900,24 @@ async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
||||
etat = 'retarde';
|
||||
}
|
||||
|
||||
let cause = '';
|
||||
if (!isTomorrow && delayMinutes > 0 && rtData.alerts.length > 0) {
|
||||
cause = rtData.alerts[idx % rtData.alerts.length].text;
|
||||
let cause = (sx && sx.cause) || '';
|
||||
if (!isTomorrow && delayMinutes > 0 && !cause) {
|
||||
if (rtData.alerts.length > 0) {
|
||||
cause = rtData.alerts[idx % rtData.alerts.length].text;
|
||||
} else if (sxData.alerts.length > 0) {
|
||||
cause = sxData.alerts[idx % sxData.alerts.length].text;
|
||||
}
|
||||
}
|
||||
|
||||
const formattedStops = (t.stops || []).map((st) => {
|
||||
const stopUic = st.uic || '';
|
||||
const stopVoie = lookupPlatform(t.num, t.trip_id, stopUic, st.nom);
|
||||
let stopVoie = lookupPlatform(t.num, t.trip_id, stopUic, st.nom, st.heure, destinationStation);
|
||||
if (!stopVoie && (st.quai || st.quai_reel || st.voie || st.platform)) {
|
||||
stopVoie = String(st.quai || st.quai_reel || st.voie || st.platform).replace(/^(?:voie|quai|track|platform)\s*/i, '').trim();
|
||||
}
|
||||
if (!stopVoie) {
|
||||
stopVoie = isAutocar ? 'AUTOCAR' : resolveNominalTrack(st.nom, t.num);
|
||||
}
|
||||
|
||||
return {
|
||||
uic: stopUic,
|
||||
@@ -580,13 +1044,10 @@ export async function getSncfTimetable({ gareName, mode = 'departs', quai = null
|
||||
let finalTrains = null;
|
||||
if (uic) {
|
||||
finalTrains = await fetchGtfsStationTimetable(uic, mode);
|
||||
}
|
||||
|
||||
// 2. Si non trouvé ou gare sans fichier, chercher avec le nom
|
||||
if (!finalTrains || finalTrains.length === 0) {
|
||||
const rawGare = gare ? gare.nom : gareName;
|
||||
const match = findGtfsGare(rawGare);
|
||||
if (match && match.uic && match.uic !== uic) {
|
||||
} else {
|
||||
// Si UIC non trouvé directement, chercher par nom
|
||||
const match = findGtfsGare(gareName);
|
||||
if (match && match.uic) {
|
||||
finalTrains = await fetchGtfsStationTimetable(match.uic, mode);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user