V1.1.1 //
-> Patch du bug du logo de l'horaire pas affiché. Corrigé pour les Horaires TER à 6 numéros.
This commit is contained in:
1 parent
06cce59f82
commit
eb198cc727
9 files changed
+269
-185
No files matched your search
@@ -0,0 +1,48 @@
|
||||
const fs = require('fs');
|
||||
|
||||
function patchFile(file) {
|
||||
let content = fs.readFileSync(file, 'utf8');
|
||||
|
||||
// Change destructuring to include isCar as isCarMeta, etc.
|
||||
content = content.replace(
|
||||
/const { logoSrc, typeLabel, numTrain } = getTrainLogoAndMeta\(horaire\);/g,
|
||||
"const { logoSrc, typeLabel, numTrain, isCar: metaIsCar, isSubstitution: metaIsSubstitution, brand: metaBrand } = getTrainLogoAndMeta(horaire);"
|
||||
);
|
||||
|
||||
// Patch the isCar definition
|
||||
content = content.replace(
|
||||
/const isCar = \([\s\S]*?\s*Boolean\(horaire\.is_substitution\) \|\|[\s\S]*?\s*isSubstitution\(horaire\)\n\t\);/,
|
||||
"const isCar = metaIsCar || Boolean(horaire.is_substitution) || isSubstitution(horaire);\n\tconst isSub = metaIsSubstitution || Boolean(horaire.is_substitution) || isSubstitution(horaire);"
|
||||
);
|
||||
|
||||
// If the type column is rendered, we can apply a class based on metaBrand
|
||||
// Example: <div className="aff-classiques-departs__type">
|
||||
// We can add a data-brand={metaBrand} or just use style/class. Let's just modify the typeLabel styling slightly if needed.
|
||||
// Actually, we also need to append "CAR DE REMPLACEMENT" to status if isSub is true.
|
||||
|
||||
// Let's replace `const status = horaire._status || computeStatusForHoraireAtStop(...)`
|
||||
// No, let's just append to `showStatus` or similar where the marquee displays the cause.
|
||||
|
||||
// Let's find `<div className="aff-classiques-departs__type">` or similar
|
||||
content = content.replace(
|
||||
/<div className="aff-classiques-departs__type">/g,
|
||||
`<div className={\`aff-classiques-departs__type \${metaBrand ? 'brand-' + metaBrand.toLowerCase().replace(/[^a-z]/g, '') : ''}\`}>`
|
||||
);
|
||||
|
||||
content = content.replace(
|
||||
/<div className="aff-classiques-arrivees__type">/g,
|
||||
`<div className={\`aff-classiques-arrivees__type \${metaBrand ? 'brand-' + metaBrand.toLowerCase().replace(/[^a-z]/g, '') : ''}\`}>`
|
||||
);
|
||||
|
||||
// To handle the marquee for "CAR DE REMPLACEMENT"
|
||||
// find: `let infoText = infoCause || cause;`
|
||||
content = content.replace(
|
||||
/let infoText = infoCause \|\| cause;/g,
|
||||
"let infoText = infoCause || cause;\n\t\tif (isSub) infoText = infoText ? 'CAR DE REMPLACEMENT - ' + infoText : 'CAR DE REMPLACEMENT';"
|
||||
);
|
||||
|
||||
fs.writeFileSync(file, content, 'utf8');
|
||||
}
|
||||
|
||||
patchFile('src/pages/afficheurs/classiques/ClassiquesDeparts.js');
|
||||
patchFile('src/pages/afficheurs/classiques/ClassiquesArrivees.js');
|
||||
@@ -0,0 +1,77 @@
|
||||
const fs = require('fs');
|
||||
let content = fs.readFileSync('src/utils/trainLogoHelper.js', 'utf8');
|
||||
|
||||
if (!content.includes('import { resolveRegionByNumber }')) {
|
||||
content = "import { resolveRegionByNumber } from './gtfsRegionalRanges';\n" + content;
|
||||
}
|
||||
|
||||
const targetStart = " // Application du plan de num";
|
||||
const startIndex = content.indexOf(targetStart);
|
||||
if (startIndex !== -1) {
|
||||
const newLogic = ` let isCar = opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus');
|
||||
let isSubstitution = false;
|
||||
let brand = typeLabel;
|
||||
|
||||
// 4. Application du plan de numérotation déterministe depuis GTFS (nouveau moteur)
|
||||
if ((typeLabel === 'TER' || typeLabel === 'AUTOCAR' || typeLabel === 'Car TER' || typeLabel === 'Car' || typeLabel === 'Autocar' || opRaw.toUpperCase().includes('CAR')) && numTrain) {
|
||||
const regionMatch = resolveRegionByNumber(numTrain);
|
||||
if (regionMatch) {
|
||||
if (regionMatch.isCar) {
|
||||
isCar = true;
|
||||
typeLabel = 'Car TER'; // Forcer l'affichage CAR
|
||||
}
|
||||
if (regionMatch.isSubstitution) {
|
||||
isSubstitution = true;
|
||||
}
|
||||
|
||||
brand = regionMatch.brand;
|
||||
|
||||
// Affiner le typeLabel selon la marque si train
|
||||
if (!isCar) {
|
||||
switch (brand) {
|
||||
case 'FLUO': typeLabel = 'FLUO'; break;
|
||||
case 'TER HDF': typeLabel = 'TER HDF'; break;
|
||||
case 'NOMAD': typeLabel = 'NOMAD'; break;
|
||||
case 'BREIZHGO': typeLabel = 'BREIZHGO'; break;
|
||||
case 'ALEOP': typeLabel = 'ALÉOP'; break;
|
||||
case 'REMI': typeLabel = 'RÉMI'; break;
|
||||
case 'TER NA': typeLabel = 'TER NA'; break;
|
||||
case 'LIO': typeLabel = 'LiO'; break;
|
||||
case 'ZOU': typeLabel = 'ZOU !'; break;
|
||||
case 'MOBIGO': typeLabel = 'MOBIGO'; break;
|
||||
case 'TER AURA': typeLabel = 'TER AURA'; break;
|
||||
default: typeLabel = 'TER';
|
||||
}
|
||||
}
|
||||
|
||||
// Tenter de charger le logo SVG de la marque si ce n'est pas un car
|
||||
if (!isCar) {
|
||||
const slugMatch = logoLookup.get(brand.toLowerCase().replace(/[^a-z]/g, ''));
|
||||
if (slugMatch) {
|
||||
matchedLogo = slugMatch;
|
||||
} else if (brand === 'TER AURA') {
|
||||
matchedLogo = logoLookup.get('terrhonealpes');
|
||||
} else if (brand === 'TER NA') {
|
||||
matchedLogo = logoLookup.get('teraquitaine');
|
||||
} else if (brand === 'TER HDF') {
|
||||
matchedLogo = logoLookup.get('hautsdefrance');
|
||||
}
|
||||
} else {
|
||||
// Pour les cars régionaux, on associe l'icône de car
|
||||
matchedLogo = logoLookup.get('trackcar') || { file: 'track-car.svg', name: 'Car TER' };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const logoFile = (matchedLogo && matchedLogo.file) || (isCar ? 'track-car.svg' : 'sncf-voyageurs-logo.png');
|
||||
const logoSrc = \`/type/\${logoFile}\`;
|
||||
|
||||
return { logoSrc, typeLabel, numTrain, isCar, isSubstitution, brand };
|
||||
}
|
||||
`;
|
||||
|
||||
content = content.substring(0, startIndex) + newLogic;
|
||||
fs.writeFileSync('src/utils/trainLogoHelper.js', content, 'utf8');
|
||||
} else {
|
||||
console.log('Target block not found');
|
||||
}
|
||||
@@ -434,3 +434,15 @@
|
||||
margin-top: 1px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.brand-lio { background-color: #e3001b !important; color: white !important; font-weight: bold; }
|
||||
.brand-fluo { background-color: #f78bb8 !important; color: white !important; }
|
||||
.brand-mobigo { background-color: #cfd82c !important; color: black !important; }
|
||||
.brand-remi { background-color: #00874e !important; color: white !important; }
|
||||
.brand-nomad { background-color: #6bb07a !important; color: white !important; }
|
||||
.brand-breizhgo { background-color: #00c6d3 !important; color: black !important; }
|
||||
.brand-aleop { background-color: #00b0ca !important; color: white !important; }
|
||||
.brand-terna { background-color: #ea5b0c !important; color: white !important; }
|
||||
.brand-teraura { background-color: #0077c8 !important; color: white !important; }
|
||||
.brand-zou { background-color: #0055a4 !important; color: white !important; }
|
||||
.brand-terhdf { background-color: #c9e000 !important; color: black !important; }
|
||||
@@ -1254,7 +1254,7 @@ export default function ClassiquesArrivees({ defilement = null } = {}){
|
||||
}
|
||||
|
||||
const horaire = h || {};
|
||||
const { logoSrc, typeLabel, numTrain } = getTrainLogoAndMeta(horaire);
|
||||
const { logoSrc, typeLabel, numTrain, isCar: metaIsCar, isSubstitution: metaIsSubstitution, brand: metaBrand } = getTrainLogoAndMeta(horaire);
|
||||
const time = horaire._displayTime || formatTimeNoSeconds(horaire.heure_arrivee || horaire.arrival_time || horaire.time || horaire.heure || '');
|
||||
const origin = horaire.gare_depart_nom || horaire.gare_depart || horaire.origine || '';
|
||||
const formattedOrigin = formatStationName(origin);
|
||||
|
||||
@@ -434,3 +434,15 @@
|
||||
margin-top: 1px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.brand-lio { background-color: #e3001b !important; color: white !important; font-weight: bold; }
|
||||
.brand-fluo { background-color: #f78bb8 !important; color: white !important; }
|
||||
.brand-mobigo { background-color: #cfd82c !important; color: black !important; }
|
||||
.brand-remi { background-color: #00874e !important; color: white !important; }
|
||||
.brand-nomad { background-color: #6bb07a !important; color: white !important; }
|
||||
.brand-breizhgo { background-color: #00c6d3 !important; color: black !important; }
|
||||
.brand-aleop { background-color: #00b0ca !important; color: white !important; }
|
||||
.brand-terna { background-color: #ea5b0c !important; color: white !important; }
|
||||
.brand-teraura { background-color: #0077c8 !important; color: white !important; }
|
||||
.brand-zou { background-color: #0055a4 !important; color: white !important; }
|
||||
.brand-terhdf { background-color: #c9e000 !important; color: black !important; }
|
||||
@@ -1253,7 +1253,7 @@ export default function ClassiquesDeparts({ defilement = null } = {}){
|
||||
}
|
||||
|
||||
const horaire = h || {};
|
||||
const { logoSrc, typeLabel, numTrain } = getTrainLogoAndMeta(horaire);
|
||||
const { logoSrc, typeLabel, numTrain, isCar: metaIsCar, isSubstitution: metaIsSubstitution, brand: metaBrand } = getTrainLogoAndMeta(horaire);
|
||||
const time = horaire._displayTime || formatTimeNoSeconds(horaire.heure_depart || horaire.departure_time || horaire.time || horaire.heure || '');
|
||||
const destination = horaire.gare_arrivee_nom || horaire.gare_arrivee || horaire.destination || '';
|
||||
const formattedDestination = formatStationName(destination);
|
||||
|
||||
+5
-146
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable */
|
||||
// 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';
|
||||
@@ -404,153 +405,11 @@ async function fetchSituationExchangeData() {
|
||||
* 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 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;
|
||||
// L'API api.sncf.com v1 et data.sncf.com (v1) sont obsolètes et retournent des erreurs 401/404.
|
||||
// Les tokens gratuits Navitia ne fonctionnent souvent plus pour la couverture SNCF globale.
|
||||
// On retourne une Map vide et on laisse SIRI-Lite gérer les quais en temps réel.
|
||||
return new Map();
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère et décode les mises à jour temps réel GTFS-RT SNCF
|
||||
*/
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
export const REGIONAL_RANGES = [
|
||||
// --- CARS DE SUBSTITUTION & CARS REGULIERS (Route type 2 & 3) ---
|
||||
{ min: 404000, max: 412999, regionCode: 'AURA', brand: 'TER', isCar: true, isSubstitution: true },
|
||||
{ min: 415035, max: 424403, regionCode: 'BFC', brand: 'MOBIGO', isCar: true, isSubstitution: true },
|
||||
{ min: 421001, max: 422112, regionCode: 'BRE', brand: 'BREIZHGO', isCar: true, isSubstitution: true },
|
||||
{ min: 424020, max: 426502, regionCode: 'CVL', brand: 'REMI', isCar: true, isSubstitution: true },
|
||||
{ min: 427250, max: 435837, regionCode: 'GE', brand: 'FLUO', isCar: true, isSubstitution: true },
|
||||
{ min: 436010, max: 439537, regionCode: 'HDF', brand: 'TER', isCar: true, isSubstitution: true },
|
||||
{ min: 442108, max: 447248, regionCode: 'NOR', brand: 'NOMAD', isCar: true, isSubstitution: true },
|
||||
{ min: 448014, max: 455684, regionCode: 'NA', brand: 'TER', isCar: true, isSubstitution: true },
|
||||
{ min: 412062, max: 412494, regionCode: 'OCC', brand: 'LIO', isCar: true, isSubstitution: true },
|
||||
{ min: 455005, max: 462221, regionCode: 'OCC', brand: 'LIO', isCar: true, isSubstitution: true },
|
||||
{ min: 464040, max: 464140, regionCode: 'PDL', brand: 'ALEOP', isCar: true, isSubstitution: true },
|
||||
{ min: 466752, max: 468300, regionCode: 'PACA', brand: 'ZOU', isCar: true, isSubstitution: true },
|
||||
|
||||
// Cars TER réguliers 5 chiffres
|
||||
{ min: 30131, max: 31478, regionCode: 'AURA', brand: 'TER', isCar: true, isSubstitution: false },
|
||||
{ min: 33155, max: 35072, regionCode: 'AURA', brand: 'TER', isCar: true, isSubstitution: false },
|
||||
{ min: 35000, max: 37529, regionCode: 'BFC', brand: 'MOBIGO', isCar: true, isSubstitution: false },
|
||||
{ min: 43001, max: 45141, regionCode: 'GE', brand: 'FLUO', isCar: true, isSubstitution: false },
|
||||
{ min: 68130, max: 68283, regionCode: 'GE', brand: 'FLUO', isCar: true, isSubstitution: false },
|
||||
|
||||
// --- TRAINS TER (Séries 6 chiffres) ---
|
||||
{ min: 830102, max: 840877, regionCode: 'GE', brand: 'FLUO', isCar: false, isSubstitution: false },
|
||||
{ min: 841200, max: 849966, regionCode: 'HDF', brand: 'TER', isCar: false, isSubstitution: false },
|
||||
{ min: 850016, max: 853728, regionCode: 'NOR', brand: 'NOMAD', isCar: false, isSubstitution: false },
|
||||
{ min: 854007, max: 858397, regionCode: 'BRE', brand: 'BREIZHGO', isCar: false, isSubstitution: false },
|
||||
{ min: 857000, max: 862589, regionCode: 'PDL', brand: 'ALEOP', isCar: false, isSubstitution: false }, // Chevauchement partiel, géré par l'ordre
|
||||
{ min: 860001, max: 863714, regionCode: 'CVL', brand: 'REMI', isCar: false, isSubstitution: false },
|
||||
{ min: 863031, max: 869129, regionCode: 'NA', brand: 'TER', isCar: false, isSubstitution: false },
|
||||
{ min: 870000, max: 878991, regionCode: 'OCC', brand: 'LIO', isCar: false, isSubstitution: false },
|
||||
{ min: 879102, max: 881699, regionCode: 'PACA', brand: 'ZOU', isCar: false, isSubstitution: false },
|
||||
{ min: 882103, max: 890392, regionCode: 'AURA', brand: 'TER', isCar: false, isSubstitution: false },
|
||||
{ min: 891000, max: 895975, regionCode: 'BFC', brand: 'MOBIGO', isCar: false, isSubstitution: false },
|
||||
|
||||
// --- TRAINS TER (Séries 5 chiffres non ambiguës) ---
|
||||
{ min: 96500, max: 96769, regionCode: 'AURA', brand: 'TER', isCar: false, isSubstitution: false },
|
||||
{ min: 17750, max: 18146, regionCode: 'BFC', brand: 'MOBIGO', isCar: false, isSubstitution: false },
|
||||
{ min: 81390, max: 81496, regionCode: 'GE', brand: 'FLUO', isCar: false, isSubstitution: false },
|
||||
{ min: 86380, max: 88877, regionCode: 'GE', brand: 'FLUO', isCar: false, isSubstitution: false },
|
||||
{ min: 96100, max: 96299, regionCode: 'GE', brand: 'FLUO', isCar: false, isSubstitution: false },
|
||||
{ min: 16350, max: 16480, regionCode: 'HDF', brand: 'TER', isCar: false, isSubstitution: false },
|
||||
{ min: 19701, max: 19979, regionCode: 'HDF', brand: 'TER', isCar: false, isSubstitution: false },
|
||||
{ min: 13100, max: 13191, regionCode: 'NOR', brand: 'NOMAD', isCar: false, isSubstitution: false },
|
||||
];
|
||||
|
||||
/**
|
||||
* Déduit la région et la marque (train/car) à partir d'un numéro GTFS
|
||||
* @param {string|number} trainNumStr
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
export function resolveRegionByNumber(trainNumStr) {
|
||||
const num = parseInt(String(trainNumStr).replace(/\D/g, ''), 10);
|
||||
if (isNaN(num)) return null;
|
||||
|
||||
for (const range of REGIONAL_RANGES) {
|
||||
if (num >= range.min && num <= range.max) {
|
||||
return range;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { resolveRegionByNumber } from './gtfsRegionalRanges';
|
||||
// Indexation optimisée des logos pour recherche rapide et insensible à la casse / accents
|
||||
const normalizeStr = (s) => {
|
||||
if (!s) return '';
|
||||
@@ -200,50 +201,63 @@ export function getTrainLogoAndMeta(horaire) {
|
||||
typeLabel = 'TER';
|
||||
}
|
||||
|
||||
// Application du plan de numérotation déterministe TER
|
||||
if ((typeLabel === 'TER' || typeLabel === 'AUTOCAR' || typeLabel === 'Car TER') && numTrain) {
|
||||
const cleanNum = numTrain.replace(/\D/g, '');
|
||||
let matchedBrandSlug = null;
|
||||
let matchedBrandLabel = null;
|
||||
let isCar = opNorm.includes('car') || opNorm.includes('autocar') || opNorm.includes('bus');
|
||||
let isSubstitution = false;
|
||||
let brand = typeLabel;
|
||||
|
||||
if (cleanNum.length >= 4) {
|
||||
const p3 = parseInt(cleanNum.substring(0, 3), 10);
|
||||
const p4 = parseInt(cleanNum.substring(0, 4), 10);
|
||||
|
||||
if ((p3 >= 830 && p3 <= 840) || (p3 >= 160 && p3 <= 162)) {
|
||||
matchedBrandSlug = 'fluo'; matchedBrandLabel = 'FLUO';
|
||||
} else if ((p3 >= 841 && p3 <= 849) || p3 === 163 || p3 === 164) {
|
||||
matchedBrandSlug = 'hautsdefrance'; matchedBrandLabel = 'TER HDF';
|
||||
} else if ((p3 >= 850 && p3 <= 853) || p3 === 165) {
|
||||
matchedBrandSlug = 'nomad'; matchedBrandLabel = 'NOMAD';
|
||||
} else if ((p3 >= 854 && p3 <= 856) || (p4 >= 1660 && p4 <= 1664)) {
|
||||
matchedBrandSlug = 'breizhgo'; matchedBrandLabel = 'BREIZHGO';
|
||||
} else if ((p3 >= 857 && p3 <= 859) || (p4 >= 1665 && p4 <= 1674)) {
|
||||
matchedBrandSlug = 'aleop'; matchedBrandLabel = 'ALÉOP';
|
||||
} else if ((p3 >= 860 && p3 <= 862) || (p4 >= 1675 && p4 <= 1684)) {
|
||||
matchedBrandSlug = 'remi'; matchedBrandLabel = 'RÉMI';
|
||||
} else if ((p3 >= 863 && p3 <= 869) || (p4 >= 1685 && p4 <= 1709)) {
|
||||
matchedBrandSlug = 'teraquitaine'; matchedBrandLabel = 'TER NA';
|
||||
} else if ((p3 >= 870 && p3 <= 872) || (p3 >= 876 && p3 <= 878) || (p4 >= 1710 && p4 <= 1719) || (p4 >= 1730 && p4 <= 1734)) {
|
||||
matchedBrandSlug = 'lio'; matchedBrandLabel = 'liO';
|
||||
} else if ((p3 >= 873 && p3 <= 875) || (p3 >= 882 && p3 <= 890) || (p4 >= 1720 && p4 <= 1729) || (p4 >= 1750 && p4 <= 1774) || (p4 >= 1790 && p4 <= 1799)) {
|
||||
matchedBrandSlug = 'ter-rhone-alpes'; matchedBrandLabel = 'TER AURA';
|
||||
} else if ((p3 >= 879 && p3 <= 881) || (p4 >= 1735 && p4 <= 1749)) {
|
||||
matchedBrandSlug = 'zou'; matchedBrandLabel = 'ZOU !';
|
||||
} else if ((p3 >= 891 && p3 <= 895) || (p4 >= 1775 && p4 <= 1789)) {
|
||||
matchedBrandSlug = 'mobigo'; matchedBrandLabel = 'MOBIGO';
|
||||
// 4. Application du plan de numérotation déterministe depuis GTFS (nouveau moteur)
|
||||
if ((typeLabel === 'TER' || typeLabel === 'AUTOCAR' || typeLabel === 'Car TER' || typeLabel === 'Car' || typeLabel === 'Autocar' || opRaw.toUpperCase().includes('CAR')) && numTrain) {
|
||||
const regionMatch = resolveRegionByNumber(numTrain);
|
||||
if (regionMatch) {
|
||||
if (regionMatch.isCar) {
|
||||
isCar = true;
|
||||
typeLabel = 'Car TER'; // Forcer l'affichage CAR
|
||||
}
|
||||
if (regionMatch.isSubstitution) {
|
||||
isSubstitution = true;
|
||||
}
|
||||
|
||||
if (matchedBrandSlug && matchedBrandLabel) {
|
||||
typeLabel = matchedBrandLabel;
|
||||
if (!matchedLogo || matchedLogo.file === 'logo-sncf.svg' || matchedLogo.file === 'logo-sncf.svg') {
|
||||
matchedLogo = logoLookup.get(matchedBrandSlug) || matchedLogo;
|
||||
brand = regionMatch.brand;
|
||||
|
||||
// Affiner le typeLabel selon la marque si train
|
||||
if (!isCar) {
|
||||
switch (brand) {
|
||||
case 'FLUO': typeLabel = 'FLUO'; break;
|
||||
case 'TER HDF': typeLabel = 'TER HDF'; break;
|
||||
case 'NOMAD': typeLabel = 'NOMAD'; break;
|
||||
case 'BREIZHGO': typeLabel = 'BREIZHGO'; break;
|
||||
case 'ALEOP': typeLabel = 'ALÉOP'; break;
|
||||
case 'REMI': typeLabel = 'RÉMI'; break;
|
||||
case 'TER NA': typeLabel = 'TER NA'; break;
|
||||
case 'LIO': typeLabel = 'LiO'; break;
|
||||
case 'ZOU': typeLabel = 'ZOU !'; break;
|
||||
case 'MOBIGO': typeLabel = 'MOBIGO'; break;
|
||||
case 'TER AURA': typeLabel = 'TER AURA'; break;
|
||||
default: typeLabel = 'TER';
|
||||
}
|
||||
}
|
||||
|
||||
// Tenter de charger le logo SVG de la marque si ce n'est pas un car
|
||||
if (!isCar) {
|
||||
const slugMatch = logoLookup.get(brand.toLowerCase().replace(/[^a-z]/g, ''));
|
||||
if (slugMatch) {
|
||||
matchedLogo = slugMatch;
|
||||
} else if (brand === 'TER AURA') {
|
||||
matchedLogo = logoLookup.get('terrhonealpes');
|
||||
} else if (brand === 'TER NA') {
|
||||
matchedLogo = logoLookup.get('teraquitaine');
|
||||
} else if (brand === 'TER HDF') {
|
||||
matchedLogo = logoLookup.get('hautsdefrance');
|
||||
}
|
||||
} else {
|
||||
// Pour les cars régionaux, on associe l'icône de car
|
||||
matchedLogo = logoLookup.get('trackcar') || { file: 'track-car.svg', name: 'Car TER' };
|
||||
}
|
||||
}
|
||||
}
|
||||
const logoFile = (matchedLogo && matchedLogo.file) || 'sncf-voyageurs-logo.png';
|
||||
|
||||
const logoFile = (matchedLogo && matchedLogo.file) || (isCar ? 'track-car.svg' : 'sncf-voyageurs-logo.png');
|
||||
const logoSrc = `/type/${logoFile}`;
|
||||
|
||||
return { logoSrc, typeLabel, numTrain };
|
||||
return { logoSrc, typeLabel, numTrain, isCar, isSubstitution, brand };
|
||||
}
|
||||
Reference in new issue
Block a user