219 lines
7.1 KiB
JavaScript
219 lines
7.1 KiB
JavaScript
/**
|
|
* Algorithmes de calcul de plus court chemin pour les voyages
|
|
*/
|
|
|
|
/**
|
|
* Classe pour le calcul de plus court chemin (algorithme de Dijkstra)
|
|
*/
|
|
export class PathFinder {
|
|
/**
|
|
* Construit un graphe à partir des données de voyage
|
|
* @param {Array} travelData - Données de voyage
|
|
* @param {String|Array} modes - Mode(s) de transport ('road', 'river', 'sea', ['road', 'river', 'sea'], 'water')
|
|
* @returns {Object} Graphe avec adjacence et poids
|
|
*/
|
|
static buildGraph(travelData, modes = 'road') {
|
|
const graph = {};
|
|
|
|
// Normaliser modes en tableau
|
|
let modeList = [];
|
|
if (modes === 'water') {
|
|
modeList = ['river', 'sea'];
|
|
} else if (modes === 'mixed') {
|
|
modeList = ['road', 'river', 'sea'];
|
|
} else if (Array.isArray(modes)) {
|
|
modeList = modes;
|
|
} else {
|
|
modeList = [modes];
|
|
}
|
|
|
|
for (const route of travelData) {
|
|
const from = route.from;
|
|
const to = route.to;
|
|
|
|
// Initialiser les nœuds
|
|
if (!graph[from]) {
|
|
graph[from] = [];
|
|
}
|
|
if (!graph[to]) {
|
|
graph[to] = [];
|
|
}
|
|
|
|
// Pour chaque mode de transport disponible
|
|
for (const mode of modeList) {
|
|
const distanceKey = `${mode}_distance`;
|
|
const daysKey = `${mode}_days`;
|
|
const distance = route[distanceKey];
|
|
const days = route[daysKey];
|
|
|
|
// Ignorer les routes sans ce mode de transport
|
|
if (!distance || distance === "" || !days || days === "") {
|
|
continue;
|
|
}
|
|
|
|
// Ajouter les arêtes (bidirectionnelles)
|
|
graph[from].push({
|
|
destination: to,
|
|
distance: parseFloat(distance),
|
|
days: parseFloat(days),
|
|
danger: route[`${mode}_danger`] || "",
|
|
mode: mode
|
|
});
|
|
|
|
graph[to].push({
|
|
destination: from,
|
|
distance: parseFloat(distance),
|
|
days: parseFloat(days),
|
|
danger: route[`${mode}_danger`] || "",
|
|
mode: mode
|
|
});
|
|
}
|
|
}
|
|
|
|
return graph;
|
|
}
|
|
|
|
/**
|
|
* Algorithme de Dijkstra pour trouver le plus court chemin
|
|
* @param {Object} graph - Graphe d'adjacence
|
|
* @param {String} start - Ville de départ
|
|
* @param {String} end - Ville d'arrivée
|
|
* @param {String} metric - Métrique à minimiser ('distance' ou 'days')
|
|
* @returns {Object|null} Chemin trouvé avec détails ou null si pas de chemin
|
|
*/
|
|
static dijkstra(graph, start, end, metric = 'days') {
|
|
// Normaliser les noms de villes
|
|
const normalizedStart = this.findCityInGraph(graph, start);
|
|
const normalizedEnd = this.findCityInGraph(graph, end);
|
|
|
|
if (!normalizedStart || !normalizedEnd) {
|
|
console.warn(`PathFinder: Ville non trouvée - start: ${start}, end: ${end}`);
|
|
return null;
|
|
}
|
|
|
|
// Initialisation
|
|
const distances = {};
|
|
const previous = {};
|
|
const visited = new Set();
|
|
const queue = [];
|
|
|
|
// Initialiser toutes les distances à l'infini
|
|
for (const node in graph) {
|
|
distances[node] = Infinity;
|
|
previous[node] = null;
|
|
}
|
|
|
|
distances[normalizedStart] = 0;
|
|
queue.push({ node: normalizedStart, distance: 0 });
|
|
|
|
while (queue.length > 0) {
|
|
// Trouver le nœud avec la plus petite distance
|
|
queue.sort((a, b) => a.distance - b.distance);
|
|
const { node: current } = queue.shift();
|
|
|
|
if (visited.has(current)) continue;
|
|
visited.add(current);
|
|
|
|
// Si on a atteint la destination
|
|
if (current === normalizedEnd) {
|
|
return this.reconstructPath(previous, normalizedStart, normalizedEnd, graph, metric);
|
|
}
|
|
|
|
// Explorer les voisins
|
|
const neighbors = graph[current] || [];
|
|
for (const neighbor of neighbors) {
|
|
if (visited.has(neighbor.destination)) continue;
|
|
|
|
const weight = neighbor[metric]; // 'distance' ou 'days'
|
|
const newDistance = distances[current] + weight;
|
|
|
|
if (newDistance < distances[neighbor.destination]) {
|
|
distances[neighbor.destination] = newDistance;
|
|
previous[neighbor.destination] = {
|
|
from: current,
|
|
edge: neighbor
|
|
};
|
|
queue.push({ node: neighbor.destination, distance: newDistance });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Pas de chemin trouvé
|
|
console.warn(`PathFinder: Aucun chemin trouvé entre ${start} et ${end}`);
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Trouve une ville dans le graphe (insensible à la casse)
|
|
* @param {Object} graph - Graphe
|
|
* @param {String} cityName - Nom de la ville
|
|
* @returns {String|null} Nom normalisé de la ville ou null
|
|
*/
|
|
static findCityInGraph(graph, cityName) {
|
|
const lowerName = cityName.toLowerCase();
|
|
for (const city in graph) {
|
|
if (city.toLowerCase() === lowerName) {
|
|
return city;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Reconstruit le chemin à partir des prédécesseurs
|
|
* @param {Object} previous - Map des prédécesseurs
|
|
* @param {String} start - Ville de départ
|
|
* @param {String} end - Ville d'arrivée
|
|
* @param {Object} graph - Graphe d'adjacence
|
|
* @param {String} metric - Métrique utilisée
|
|
* @returns {Object} Détails du chemin
|
|
*/
|
|
static reconstructPath(previous, start, end, graph, metric) {
|
|
const path = [];
|
|
let current = end;
|
|
let totalDistance = 0;
|
|
let totalDays = 0;
|
|
let maxDanger = "";
|
|
const modesUsed = new Set();
|
|
|
|
// Reconstruire le chemin en remontant
|
|
while (current !== start) {
|
|
const prev = previous[current];
|
|
if (!prev) break;
|
|
|
|
const mode = prev.edge.mode || 'road';
|
|
modesUsed.add(mode);
|
|
|
|
path.unshift({
|
|
from: prev.from,
|
|
to: current,
|
|
distance: prev.edge.distance,
|
|
days: prev.edge.days,
|
|
danger: prev.edge.danger,
|
|
mode: mode
|
|
});
|
|
|
|
totalDistance += prev.edge.distance;
|
|
totalDays += prev.edge.days;
|
|
|
|
// Calculer le danger maximum
|
|
const dangerLevel = (prev.edge.danger || "").length;
|
|
const currentMaxLevel = maxDanger.length;
|
|
if (dangerLevel > currentMaxLevel) {
|
|
maxDanger = prev.edge.danger;
|
|
}
|
|
|
|
current = prev.from;
|
|
}
|
|
|
|
return {
|
|
path: path,
|
|
totalDistance: totalDistance,
|
|
totalDays: totalDays,
|
|
maxDanger: maxDanger,
|
|
steps: path.length,
|
|
modesUsed: Array.from(modesUsed)
|
|
};
|
|
}
|
|
}
|