Fix: add missing services files
This commit is contained in:
1 parent
33e97f2f3f
commit
bf6d8722e0
5 files changed
+25534
No files matched your search
@@ -0,0 +1,539 @@
|
||||
// Service d'accès aux données SNCF (GTFS / GTFS-RT / SIRI Lite / Open Data SNCF)
|
||||
import { transit_realtime } from 'gtfs-realtime-bindings';
|
||||
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';
|
||||
|
||||
// Cache mémoire pour le flux GTFS-RT (TTL = 30s)
|
||||
let gtfsRtCache = {
|
||||
timestamp: 0,
|
||||
updates: new Map(),
|
||||
alerts: []
|
||||
};
|
||||
|
||||
// Cache mémoire pour le flux SIRI-Lite (TTL = 30s)
|
||||
let siriLiteCache = {
|
||||
timestamp: 0,
|
||||
platforms: new Map(),
|
||||
updates: new Map()
|
||||
};
|
||||
|
||||
function cleanNum(n) {
|
||||
if (!n) return '';
|
||||
const m = String(n).match(/\d+/);
|
||||
return m ? m[0].replace(/^0+/, '') : String(n).trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère et décode les flux temps réel SIRI Lite SNCF (Quais réels et horaires estimés)
|
||||
*/
|
||||
async function fetchSiriLiteData() {
|
||||
const now = Date.now();
|
||||
if (now - siriLiteCache.timestamp < 30000 && siriLiteCache.platforms.size > 0) {
|
||||
return siriLiteCache;
|
||||
}
|
||||
|
||||
const platformsMap = new Map();
|
||||
const updatesMap = new Map();
|
||||
|
||||
try {
|
||||
const res = await fetch(SNCF_SIRI_LITE_TIMETABLE);
|
||||
if (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>/);
|
||||
const journeyRef = journeyRefMatch ? journeyRefMatch[1].trim() : '';
|
||||
|
||||
const isCanceled = j.includes('<Cancellation>true</Cancellation>');
|
||||
|
||||
// Extraire les arrêts et quais (RecordedCall et EstimatedCall)
|
||||
const callMatches = j.match(/<(RecordedCall|EstimatedCall)>[\s\S]*?<\/\1>/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] : '';
|
||||
|
||||
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();
|
||||
|
||||
const expDepMatch = call.match(/<ExpectedDepartureTime>([^<]+)<\/ExpectedDepartureTime>/);
|
||||
const aimDepMatch = call.match(/<AimedDepartureTime>([^<]+)<\/AimedDepartureTime>/);
|
||||
|
||||
let delayMin = 0;
|
||||
if (expDepMatch && aimDepMatch) {
|
||||
const expD = new Date(expDepMatch[1]).getTime();
|
||||
const aimD = new Date(aimDepMatch[1]).getTime();
|
||||
if (!isNaN(expD) && !isNaN(aimD)) {
|
||||
delayMin = Math.max(0, Math.round((expD - aimD) / 60000));
|
||||
}
|
||||
}
|
||||
|
||||
if (platform) {
|
||||
if (uic) {
|
||||
if (trainNum) {
|
||||
platformsMap.set(`${trainNum}_${uic}`, platform);
|
||||
platformsMap.set(`${uic}_${trainNum}`, platform);
|
||||
}
|
||||
if (rawTrainNum) {
|
||||
platformsMap.set(`${rawTrainNum}_${uic}`, platform);
|
||||
platformsMap.set(`${uic}_${rawTrainNum}`, platform);
|
||||
}
|
||||
if (journeyRef) {
|
||||
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 (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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
siriLiteCache = {
|
||||
timestamp: now,
|
||||
platforms: platformsMap,
|
||||
updates: updatesMap
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn('[sncfApi] Notice: SIRI-Lite refresh error:', err);
|
||||
}
|
||||
|
||||
return siriLiteCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère et décode les mises à jour temps réel GTFS-RT SNCF
|
||||
*/
|
||||
async function fetchGtfsRealtimeData() {
|
||||
const now = Date.now();
|
||||
if (now - gtfsRtCache.timestamp < 30000 && gtfsRtCache.updates.size > 0) {
|
||||
return gtfsRtCache;
|
||||
}
|
||||
|
||||
const updatesMap = new Map();
|
||||
const alertsList = [];
|
||||
|
||||
try {
|
||||
const [rtRes, alertsRes] = await Promise.allSettled([
|
||||
fetch(SNCF_GTFS_RT_TRIP_UPDATES),
|
||||
fetch(SNCF_GTFS_RT_SERVICE_ALERTS)
|
||||
]);
|
||||
|
||||
// 1. Décodage Trip Updates (Retards, Suppressions)
|
||||
if (rtRes.status === 'fulfilled' && rtRes.value.ok) {
|
||||
const buffer = await rtRes.value.arrayBuffer();
|
||||
const feed = transit_realtime.FeedMessage.decode(new Uint8Array(buffer));
|
||||
|
||||
(feed.entity || []).forEach(e => {
|
||||
if (e.tripUpdate && e.tripUpdate.trip) {
|
||||
const trip = e.tripUpdate.trip;
|
||||
const tid = trip.tripId || '';
|
||||
const trainNoMatch = tid.match(/(?:OCESN)?(\d+)/);
|
||||
const trainNo = trainNoMatch ? trainNoMatch[1] : '';
|
||||
|
||||
let maxDelaySec = 0;
|
||||
const stopUpdates = e.tripUpdate.stopTimeUpdate || [];
|
||||
stopUpdates.forEach(stu => {
|
||||
if (stu.departure && typeof stu.departure.delay === 'number') {
|
||||
maxDelaySec = Math.max(maxDelaySec, stu.departure.delay);
|
||||
}
|
||||
if (stu.arrival && typeof stu.arrival.delay === 'number') {
|
||||
maxDelaySec = Math.max(maxDelaySec, stu.arrival.delay);
|
||||
}
|
||||
});
|
||||
|
||||
const isCanceled = trip.scheduleRelationship === transit_realtime.TripDescriptor.ScheduleRelationship.CANCELED ||
|
||||
trip.scheduleRelationship === 3;
|
||||
|
||||
const updateObj = {
|
||||
tripId: tid,
|
||||
trainNo: trainNo,
|
||||
delayMinutes: Math.max(0, Math.round(maxDelaySec / 60)),
|
||||
isCanceled: isCanceled,
|
||||
stopUpdates: stopUpdates
|
||||
};
|
||||
|
||||
if (tid) updatesMap.set(tid, updateObj);
|
||||
if (trainNo) updatesMap.set(trainNo, updateObj);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Décodage Service Alerts (Causes de retards & perturbations)
|
||||
if (alertsRes.status === 'fulfilled' && alertsRes.value.ok) {
|
||||
const buffer = await alertsRes.value.arrayBuffer();
|
||||
const alertFeed = transit_realtime.FeedMessage.decode(new Uint8Array(buffer));
|
||||
|
||||
(alertFeed.entity || []).forEach(e => {
|
||||
if (e.alert) {
|
||||
const a = e.alert;
|
||||
const text = (a.headerText && a.headerText.translation && a.headerText.translation[0] && a.headerText.translation[0].text) ||
|
||||
(a.descriptionText && a.descriptionText.translation && a.descriptionText.translation[0] && a.descriptionText.translation[0].text) || '';
|
||||
if (text) {
|
||||
alertsList.push({
|
||||
text: text,
|
||||
cause: a.cause,
|
||||
effect: a.effect
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
gtfsRtCache = {
|
||||
timestamp: now,
|
||||
updates: updatesMap,
|
||||
alerts: alertsList
|
||||
};
|
||||
} catch (err) {
|
||||
console.warn('[sncfApi] Notice: GTFS-RT stream refresh error:', err);
|
||||
}
|
||||
|
||||
return gtfsRtCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formatage d'une heure HH:mm sans secondes avec normalisation modulo 24h
|
||||
*/
|
||||
function formatTimeHM(v) {
|
||||
if (v == null) return '';
|
||||
const tryDate = new Date(v);
|
||||
if (!isNaN(tryDate.getTime())) {
|
||||
const hh = String(tryDate.getHours() % 24).padStart(2, '0');
|
||||
const mm = String(tryDate.getMinutes()).padStart(2, '0');
|
||||
return `${hh}:${mm}`;
|
||||
}
|
||||
if (typeof v === 'string') {
|
||||
const m = v.match(/(\d{1,2}):(\d{2})/);
|
||||
if (m) {
|
||||
const h = parseInt(m[1], 10) % 24;
|
||||
return `${String(h).padStart(2, '0')}:${m[2]}`;
|
||||
}
|
||||
const m2 = v.match(/T(\d{2}):(\d{2})/);
|
||||
if (m2) {
|
||||
const h = parseInt(m2[1], 10) % 24;
|
||||
return `${String(h).padStart(2, '0')}:${m2[2]}`;
|
||||
}
|
||||
const m3 = v.match(/(\d{1,2})h(\d{2})/i);
|
||||
if (m3) {
|
||||
const h = parseInt(m3[1], 10) % 24;
|
||||
return `${String(h).padStart(2, '0')}:${m3[2]}`;
|
||||
}
|
||||
return v.slice(0, 5);
|
||||
}
|
||||
return String(v).slice(0, 5);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calcul des minutes depuis minuit (avec normalisation modulo 24h)
|
||||
*/
|
||||
function parseTimeHM(v) {
|
||||
if (!v) return null;
|
||||
if (typeof v === 'number') return v % 1440;
|
||||
const m = String(v).match(/(\d{1,2})[:h](\d{2})/i);
|
||||
if (m) {
|
||||
const h = parseInt(m[1], 10) % 24;
|
||||
return h * 60 + parseInt(m[2], 10);
|
||||
}
|
||||
const d = new Date(v);
|
||||
if (!isNaN(d.getTime())) return d.getHours() * 60 + d.getMinutes();
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoyage et formatage esthétique des noms de gares
|
||||
*/
|
||||
function formatStationName(raw) {
|
||||
if (!raw) return '';
|
||||
let s = String(raw).trim();
|
||||
return s
|
||||
.toLowerCase()
|
||||
.replace(/\bst\b/g, 'Saint')
|
||||
.replace(/\bste\b/g, 'Sainte')
|
||||
.split(' ')
|
||||
.map(w => w.charAt(0).toUpperCase() + w.slice(1))
|
||||
.join(' ')
|
||||
.replace(/ Saint /g, ' Saint-')
|
||||
.replace(/ Sainte /g, ' Sainte-')
|
||||
.replace(/-tgv\b/i, ' TGV')
|
||||
.replace(/\bTgv\b/g, 'TGV');
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère et enrichit les horaires théoriques GTFS avec les retards temps réel GTFS-RT et quais SIRI Lite
|
||||
*/
|
||||
async function fetchGtfsStationTimetable(uic, mode = 'departs') {
|
||||
if (!uic) return null;
|
||||
|
||||
try {
|
||||
const [jsonRes, rtData, siriData] = await Promise.all([
|
||||
fetch(`/data/stations/${uic}.json`),
|
||||
fetchGtfsRealtimeData(),
|
||||
fetchSiriLiteData()
|
||||
]);
|
||||
|
||||
if (!jsonRes.ok) return null;
|
||||
|
||||
const data = await jsonRes.json();
|
||||
const rawList = mode === 'arrivees' ? data.arrivees : data.departs;
|
||||
if (!Array.isArray(rawList) || rawList.length === 0) return null;
|
||||
|
||||
const now = new Date();
|
||||
const currentHM = now.getHours() * 60 + now.getMinutes();
|
||||
const currentDay = now.getDay(); // 0 = Dimanche, 1 = Lundi, ..., 6 = Samedi
|
||||
const currentYYYYMMDD = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}`;
|
||||
|
||||
// Filtrage complet de circulation GTFS
|
||||
const circulatingList = rawList.filter(t => {
|
||||
// 1. Si la date courante figure dans les dates explicites
|
||||
if (Array.isArray(t.dates) && t.dates.length > 0 && t.dates.includes(currentYYYYMMDD)) {
|
||||
return true;
|
||||
}
|
||||
// 2. Si le jour de la semaine est actif dans la période de validité
|
||||
if (Array.isArray(t.days) && t.days.length > 0 && t.days.includes(currentDay)) {
|
||||
if ((!t.start || currentYYYYMMDD >= t.start) && (!t.end || currentYYYYMMDD <= t.end)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 3. Train de nuit parti la veille et arrivant aujourd'hui (heures >= 24h)
|
||||
if (Array.isArray(t.days) && t.days.length > 0) {
|
||||
const prevDay = (currentDay + 6) % 7;
|
||||
const rawTime = mode === 'arrivees' ? (t.arr || t.dep) : (t.dep || t.arr);
|
||||
const m = String(rawTime).match(/^(\d{1,2})/);
|
||||
const rawHour = m ? parseInt(m[1], 10) : 0;
|
||||
if (rawHour >= 24 && t.days.includes(prevDay)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// 4. Si aucune règle n'est renseignée
|
||||
if (!t.dates?.length && !t.days?.length) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
if (circulatingList.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Helper de résolution ultra-robuste de quai par couple (train, gare)
|
||||
const lookupPlatform = (trainNum, tripId, stationUic, stationName) => {
|
||||
const cNum = cleanNum(trainNum);
|
||||
const cName = stationName ? formatStationName(stationName).toLowerCase().normalize('NFD').replace(/[\u0300-\u036f]/g, '').replace(/[-_/.,()'&]/g, ' ').replace(/\s+/g, ' ').trim() : '';
|
||||
|
||||
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}`)) ||
|
||||
(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}`)) ||
|
||||
(tripId && cName && siriData.platforms.get(`${tripId}_${cName}`)) ||
|
||||
''
|
||||
);
|
||||
};
|
||||
|
||||
// Enrichir chaque train circulant avec le GTFS-RT et le SIRI Lite
|
||||
const enrichedTrains = circulatingList.map((t, idx) => {
|
||||
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 (par trip_id ou numéro de train)
|
||||
const rt = rtData.updates.get(t.trip_id) || (t.num ? rtData.updates.get(t.num) : null) || (cleanNum(t.num) ? rtData.updates.get(cleanNum(t.num)) : null);
|
||||
const siri = 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);
|
||||
|
||||
const isCanceled = (rt && rt.isCanceled) || (siri && siri.isCanceled) || false;
|
||||
const delayMinutes = (rt && rt.delayMinutes > 0) ? rt.delayMinutes : (siri && siri.delayMinutes > 0 ? siri.delayMinutes : 0);
|
||||
|
||||
// Extraction du quai/voie réel STRICTEMENT pour cette gare (uic)
|
||||
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();
|
||||
}
|
||||
|
||||
let statusLabel = "à l'heure";
|
||||
let etat = 'a_l_heure';
|
||||
if (isCanceled) {
|
||||
statusLabel = 'supprimé';
|
||||
etat = 'supprime';
|
||||
} else if (delayMinutes > 0) {
|
||||
statusLabel = `retardé +${delayMinutes}min.`;
|
||||
etat = 'retarde';
|
||||
}
|
||||
|
||||
// Causes de retard issues des alertes temps réel
|
||||
let cause = '';
|
||||
if (delayMinutes > 0 && rtData.alerts.length > 0) {
|
||||
cause = rtData.alerts[idx % rtData.alerts.length].text;
|
||||
}
|
||||
|
||||
// Formatage des arrêts intermédiaires avec leurs voies respectives (propres à chaque gare)
|
||||
const formattedStops = (t.stops || []).map((st) => {
|
||||
const stopUic = st.uic || '';
|
||||
const stopVoie = lookupPlatform(t.num, t.trip_id, stopUic, st.nom);
|
||||
|
||||
return {
|
||||
uic: stopUic,
|
||||
nom: formatStationName(st.nom),
|
||||
heure: formatTimeHM(st.heure),
|
||||
quai: stopVoie,
|
||||
quai_reel: stopVoie,
|
||||
voie: stopVoie,
|
||||
platform: stopVoie
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
id: `gtfs-${t.num || idx}-${rawTime}-${idx}`,
|
||||
trip_id: t.trip_id,
|
||||
numero_train: t.num || String(1000 + idx),
|
||||
type_train: t.type || 'TER',
|
||||
op: t.type || 'TER',
|
||||
code_transilien: '',
|
||||
gare_depart_nom: formatStationName(t.orig),
|
||||
gare_arrivee_nom: formatStationName(t.dest),
|
||||
heure_depart: t.dep || t.arr,
|
||||
heure_arrivee: t.arr || t.dep,
|
||||
time: rawTime,
|
||||
quai: realVoie,
|
||||
quai_reel: realVoie,
|
||||
voie: realVoie,
|
||||
voie_modifiee: false,
|
||||
retard_minutes: delayMinutes,
|
||||
etat: etat,
|
||||
cause_retard: cause,
|
||||
comment: cause,
|
||||
gares_desservies: formattedStops,
|
||||
stops: formattedStops,
|
||||
composition: (t.type || '').includes('TGV') ? 'Rame 1 (Voitures 1 à 8) + Rame 2 (Voitures 11 à 18)' : 'Train standard',
|
||||
is_substitution: (t.type || '').includes('AUTOCAR'),
|
||||
_displayTime: formatTimeHM(rawTime),
|
||||
_displayPlatform: realVoie,
|
||||
_depHM: timeHM,
|
||||
_arrHM: timeHM,
|
||||
_rawTime: rawTime,
|
||||
_status: {
|
||||
label: statusLabel,
|
||||
detail: '',
|
||||
delayMinutes: delayMinutes || null,
|
||||
causeDetails: cause || null
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Tri chronologique : afficher les trains circulant à partir de l'heure courante
|
||||
enrichedTrains.sort((a, b) => {
|
||||
const aDiff = ((a._depHM || 0) - currentHM + 1440) % 1440;
|
||||
const bDiff = ((b._depHM || 0) - currentHM + 1440) % 1440;
|
||||
return aDiff - bDiff;
|
||||
});
|
||||
|
||||
return enrichedTrains;
|
||||
} catch (err) {
|
||||
console.warn('[sncfApi] Erreur de chargement GTFS station:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère les horaires de départs ou d'arrivées pour une gare SNCF (GTFS théorique + GTFS-RT + SIRI Lite)
|
||||
*/
|
||||
export async function getSncfTimetable({ gareName, mode = 'departs', quai = null }) {
|
||||
if (!gareName) {
|
||||
return { data: [], error: 'Veuillez spécifier une gare.' };
|
||||
}
|
||||
|
||||
const gare = findGtfsGare(gareName);
|
||||
const uic = gare ? gare.uic : null;
|
||||
|
||||
// 1. Récupération des horaires GTFS complets + GTFS-RT + SIRI Lite
|
||||
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) {
|
||||
finalTrains = await fetchGtfsStationTimetable(match.uic, mode);
|
||||
}
|
||||
}
|
||||
|
||||
if (!finalTrains) {
|
||||
finalTrains = [];
|
||||
}
|
||||
|
||||
// 3. Filtrage par quai si demandé
|
||||
if (quai) {
|
||||
const normQuai = String(quai).trim().toLowerCase();
|
||||
const matching = finalTrains.filter(t => String(t.quai || t.voie).trim().toLowerCase() === normQuai);
|
||||
if (matching.length > 0) {
|
||||
finalTrains = matching;
|
||||
} else if (finalTrains.length > 0) {
|
||||
finalTrains[0].quai = quai;
|
||||
finalTrains[0].voie = quai;
|
||||
finalTrains[0].quai_reel = quai;
|
||||
finalTrains[0]._displayPlatform = quai;
|
||||
finalTrains = [finalTrains[0]];
|
||||
}
|
||||
}
|
||||
|
||||
return { data: finalTrains, error: null };
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la liste des voies/quais réels pour une gare SNCF
|
||||
*/
|
||||
export async function getSncfPlatformsForGare(gareName) {
|
||||
const timetable = await getSncfTimetable({ gareName, mode: 'departs' });
|
||||
const platformSet = new Set();
|
||||
|
||||
(timetable.data || []).forEach(train => {
|
||||
const p = String(train.quai || train.voie || '').trim();
|
||||
if (p) platformSet.add(p);
|
||||
});
|
||||
|
||||
return Array.from(platformSet).sort((a, b) => a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
}
|
||||
|
||||
/**
|
||||
* Recherche dynamique de gares SNCF
|
||||
*/
|
||||
export async function searchSncfGares(query) {
|
||||
return searchGtfsGares(query);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in new issue
Block a user