Optimizacion de codigo, limpieza residual y actualizacionde hover de elevaciones
This commit is contained in:
@@ -29,9 +29,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _pintandoParadas = false;
|
||||
private Task? _tareaCargaRed;
|
||||
|
||||
private readonly RealTimeMap.PointSymbol _simboloOrigen = new() { color = "green", fillColor = "green", radius = 8 };
|
||||
private readonly RealTimeMap.PointSymbol _simboloDestino = new() { color = "red", fillColor = "red", radius = 8 };
|
||||
|
||||
private int _versionCalculo = 0;
|
||||
private int _versionPintadoRuta = 0;
|
||||
private CancellationTokenSource? _ctsCalculo;
|
||||
@@ -511,23 +508,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
return stops;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Construye el payload JS de paradas de un tramo de bus.
|
||||
/// </summary>
|
||||
private List<object> BuildStopsForTramoBus(TramoBus tramo)
|
||||
=> BuildStopsDesdeParadas(BuildParadasForTramoBus(tramo));
|
||||
|
||||
/// <summary>
|
||||
/// Registra paradas visibles de un itinerario activo para permitir popup aunque la capa general esté oculta.
|
||||
/// </summary>
|
||||
private void RegistrarParadasItinerarioVisibles(string id, IEnumerable<Parada> paradas)
|
||||
{
|
||||
_paradasItinerarioVisiblesPorId[id] = paradas
|
||||
.GroupBy(p => p.Codigo, StringComparer.OrdinalIgnoreCase)
|
||||
.Select(g => g.First())
|
||||
.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Elimina el registro de paradas visibles de un itinerario activo.
|
||||
/// </summary>
|
||||
@@ -638,53 +618,12 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona linea completa variante.
|
||||
/// </summary>
|
||||
private double[,]? ConstruirLineaCompletaVariante(InfoVariante v)
|
||||
{
|
||||
if (v is null || v.CodigosParadas is null || v.CodigosParadas.Count < 2) return null;
|
||||
|
||||
var pts = new List<double[]>(v.CodigosParadas.Count);
|
||||
|
||||
foreach (var cod in v.CodigosParadas)
|
||||
{
|
||||
if (_paradaByCode.TryGetValue(cod, out var p))
|
||||
pts.Add(new[] { p.Latitud, p.Longitud });
|
||||
}
|
||||
|
||||
if (pts.Count < 2) return null;
|
||||
|
||||
var arr = new double[pts.Count, 2];
|
||||
for (int i = 0; i < pts.Count; i++)
|
||||
{
|
||||
arr[i, 0] = pts[i][0];
|
||||
arr[i, 1] = pts[i][1];
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
|
||||
// ✅ Rango de líneas
|
||||
private bool _activarRangoLineas = false;
|
||||
private int _lineaMin = 1;
|
||||
private int _lineaMax = 200;
|
||||
private readonly HashSet<int> _lineasPermitidas = new();
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona dentro rango.
|
||||
/// </summary>
|
||||
private bool LineaDentroRango(InfoVariante v)
|
||||
{
|
||||
if (!_activarRangoLineas) return true;
|
||||
|
||||
int codLinea = CodigoLineaAsInt(v); // ya lo tienes hecho
|
||||
if (codLinea == int.MaxValue) return false; // si no se puede parsear, fuera
|
||||
|
||||
return _lineasPermitidas.Contains(codLinea);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
@@ -859,19 +798,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
linea[0, 1] = punto[1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sobrescribe el ultimo punto de una linea.
|
||||
/// </summary>
|
||||
private static void EstablecerUltimoPunto(double[,]? linea, double[]? punto)
|
||||
{
|
||||
if (linea is null || punto is null || linea.GetLength(0) == 0)
|
||||
return;
|
||||
|
||||
var ultimo = linea.GetLength(0) - 1;
|
||||
linea[ultimo, 0] = punto[0];
|
||||
linea[ultimo, 1] = punto[1];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene la distancia entre dos puntos geograficos.
|
||||
/// </summary>
|
||||
@@ -1617,45 +1543,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
? alternativa.Etiqueta
|
||||
: $"Opcion {indice + 1}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene las siglas compactas de categorías adicionales para una alternativa final.
|
||||
/// </summary>
|
||||
private List<string> ObtenerSiglasExtrasAlternativa(AlternativaRuta alternativa)
|
||||
{
|
||||
if (alternativa.EsSoloAPie)
|
||||
return new();
|
||||
|
||||
var alternativasBus = _opcionesRuta
|
||||
.Where(x => !x.EsSoloAPie && x.AlternativaBus is not null)
|
||||
.ToList();
|
||||
|
||||
if (alternativasBus.Count == 0)
|
||||
return new();
|
||||
|
||||
var extras = new List<string>(2);
|
||||
var etiqueta = alternativa.Etiqueta ?? string.Empty;
|
||||
|
||||
var minCoste = alternativasBus.Min(x => x.PuntuacionCoste);
|
||||
if (!EtiquetaPrincipalEs(etiqueta, "Más rápida") && alternativa.PuntuacionCoste <= minCoste + 0.001)
|
||||
extras.Add("MR");
|
||||
|
||||
var minTransbordos = alternativasBus.Min(x => x.NumeroTransbordosReales);
|
||||
if (!EtiquetaPrincipalEs(etiqueta, "Menos transbordos") && alternativa.NumeroTransbordosReales == minTransbordos)
|
||||
extras.Add("MT");
|
||||
|
||||
var minDistanciaPie = alternativasBus.Min(x => x.DistanciaPieTotalAprox);
|
||||
if (!EtiquetaPrincipalEs(etiqueta, "Menos andar") && alternativa.DistanciaPieTotalAprox <= minDistanciaPie + 0.001)
|
||||
extras.Add("MA");
|
||||
|
||||
return extras;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Comprueba si la etiqueta principal ya corresponde a una categoría destacada concreta.
|
||||
/// </summary>
|
||||
private static bool EtiquetaPrincipalEs(string? etiquetaActual, string etiquetaEsperada)
|
||||
=> string.Equals(etiquetaActual?.Trim(), etiquetaEsperada, StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -289,22 +289,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indica si match wildcard.
|
||||
/// </summary>
|
||||
private static bool MatchWildcard(string input, string pattern)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(pattern)) return true;
|
||||
if (string.IsNullOrEmpty(input)) return false;
|
||||
|
||||
// escapamos regex y convertimos comodines
|
||||
var rx = "^" + Regex.Escape(pattern.Trim())
|
||||
.Replace("\\*", ".*")
|
||||
.Replace("\\?", ".") + "$";
|
||||
|
||||
return Regex.IsMatch(input, rx, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
private Parada? _paradaPrimerClickPendiente;
|
||||
|
||||
// Detecta si el click cae realmente sobre el círculo de una parada (en píxeles)
|
||||
@@ -553,24 +537,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private int _minTransbordosPermitidos = 0;
|
||||
private int _maxTransbordosPermitidos = 3;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indica si cumple filtro transbordos.
|
||||
/// </summary>
|
||||
private bool CumpleFiltroTransbordos(int transbordos)
|
||||
{
|
||||
if (!_activarRangoTransbordos)
|
||||
return true;
|
||||
|
||||
int minT = Math.Max(0, _minTransbordosPermitidos);
|
||||
int maxT = Math.Max(0, _maxTransbordosPermitidos);
|
||||
|
||||
// por si el usuario mete al revés
|
||||
if (minT > maxT) (minT, maxT) = (maxT, minT);
|
||||
|
||||
return transbordos >= minT && transbordos <= maxT;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indica si esta popup puntos abierto.
|
||||
/// </summary>
|
||||
@@ -639,15 +605,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
var idx = Math.Abs(StableHash(key)) % _palette.Length;
|
||||
return _palette[idx];
|
||||
}
|
||||
/// <summary>
|
||||
/// Gestiona color clave for variante.
|
||||
/// </summary>
|
||||
private static string ColorKeyForVariante(InfoVariante v)
|
||||
{
|
||||
var linea = v.Linea.Codigo?.ToString() ?? "?";
|
||||
var itin = v.CodigoItinerario.ToString(CultureInfo.InvariantCulture);
|
||||
return $"{linea}.{itin}";
|
||||
}
|
||||
|
||||
|
||||
private string _itFiltro = "";
|
||||
@@ -678,15 +635,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
_selectItKey++;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona it filtro busqueda.
|
||||
/// </summary>
|
||||
private void OnItFiltroSearch(ChangeEventArgs e)
|
||||
{
|
||||
ItFiltro = e?.Value?.ToString() ?? string.Empty;
|
||||
StateHasChanged();
|
||||
}
|
||||
private IEnumerable<ElementoItinerario> _itItemsFiltrados =>
|
||||
string.IsNullOrWhiteSpace(_itFiltro)
|
||||
? _itItems
|
||||
@@ -882,23 +830,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Indica si match wildcard contains.
|
||||
/// </summary>
|
||||
private static bool MatchWildcardContains(string input, string pattern)
|
||||
{
|
||||
// en modo “contains”, envolvemos con *...*
|
||||
if (!pattern.StartsWith("*")) pattern = "*" + pattern;
|
||||
if (!pattern.EndsWith("*")) pattern = pattern + "*";
|
||||
|
||||
var rx = Regex.Escape(pattern)
|
||||
.Replace("\\*", ".*")
|
||||
.Replace("\\?", ".");
|
||||
|
||||
return Regex.IsMatch(input, rx, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1214,17 +1145,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
return resultado;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona itis de alternativa.
|
||||
/// </summary>
|
||||
private string ItisDeAlternativa(AlternativaRuta alt)
|
||||
{
|
||||
if (alt is null || alt.EsSoloAPie || alt.AlternativaBus?.Tramos is null)
|
||||
return "";
|
||||
|
||||
return TextoLineasDeTramos(alt.AlternativaBus.Tramos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1261,7 +1181,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _elevPopupMinimizado = false;
|
||||
|
||||
|
||||
private const string _versionApp = "(v-20260713a)";
|
||||
private const string _versionApp = "(v-20260714a)";
|
||||
|
||||
|
||||
private double? _paradaElevG;
|
||||
|
||||
@@ -18,26 +18,6 @@ namespace RutasDBUS.Components.Pages;
|
||||
|
||||
public partial class PlanificadorRutas : ComponentBase
|
||||
{
|
||||
// =========================================================
|
||||
// Itinerarios: rango (estricto durante BFS)
|
||||
// =========================================================
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona dentro rango.
|
||||
/// </summary>
|
||||
private bool ItinerarioDentroRango(int codigoItinerario)
|
||||
{
|
||||
if (!_activarRangoItinerarios) return true;
|
||||
int min = Math.Min(_itinerarioMin, _itinerarioMax);
|
||||
int max = Math.Max(_itinerarioMin, _itinerarioMax);
|
||||
return codigoItinerario >= min && codigoItinerario <= max;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona permitida.
|
||||
/// </summary>
|
||||
private bool VariantePermitida(InfoVariante v)
|
||||
=> ItinerarioDentroRango(v.CodigoItinerario) && LineaDentroRango(v);
|
||||
|
||||
private IReadOnlyList<int> ObtenerLineasPermitidasSeleccionadas()
|
||||
=> _lineasPermitidas
|
||||
@@ -121,158 +101,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
_busFilteredCache.Clear();
|
||||
await PersistirConfiguracionAsync();
|
||||
}
|
||||
|
||||
|
||||
// =========================================================
|
||||
// Selección de candidatas: por ubicación, pero SOLO si están
|
||||
// en itinerarios permitidos
|
||||
// =========================================================
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona tiene aparicion permitida.
|
||||
/// </summary>
|
||||
private bool ParadaTieneAparicionPermitida(string codigoParada)
|
||||
{
|
||||
if (!_indiceParadas.TryGetValue(codigoParada, out var apps)) return false;
|
||||
foreach (var a in apps)
|
||||
if (VariantePermitida(a.Variante))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selecciona candidatas solo itinerarios.
|
||||
/// </summary>
|
||||
private async Task<List<CandidataParada>> SeleccionarCandidatasSoloItinerariosAsync(double lat, double lon, CancellationToken ct)
|
||||
{
|
||||
EnsureBusIndexBuilt();
|
||||
|
||||
int radio = Math.Clamp(_radioCandidatasMetros, 200, 3500);
|
||||
int k = Math.Clamp(_maxCandidatasPorLado, 3, 20);
|
||||
|
||||
// 1) pool por grid
|
||||
var pool = QueryParadasEnRadio(lat, lon, radio);
|
||||
|
||||
// 2) filtrar: solo paradas que estén en itinerarios permitidos
|
||||
var tmp = new List<CandidataParada>(pool.Count);
|
||||
|
||||
foreach (var p in pool)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
if ((_activarRangoItinerarios || _activarRangoLineas) && !ParadaTieneAparicionPermitida(p.Codigo))
|
||||
continue;
|
||||
|
||||
|
||||
var d = CalcularDistanciaMetros(lat, lon, p.Latitud, p.Longitud);
|
||||
if (d <= radio * 1.9)
|
||||
{
|
||||
tmp.Add(new CandidataParada
|
||||
{
|
||||
Parada = p,
|
||||
DistEucl = d,
|
||||
DistWalk = d,
|
||||
TiempoWalk = EstimarDuracionPieSegundos(d) // ✅ SIEMPRE con velocidad configurada
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
tmp.Sort((a, b) => a.DistEucl.CompareTo(b.DistEucl));
|
||||
if (tmp.Count == 0) return tmp;
|
||||
|
||||
// Pre-take (barato)
|
||||
int preTake = Math.Min(32, Math.Max(k + 10, k * 3));
|
||||
if (tmp.Count > preTake) tmp = tmp.Take(preTake).ToList();
|
||||
|
||||
// En estricto: NO usamos OSRM para ranking (por rendimiento)
|
||||
if (!_usarOsrmParaRanking || _activarRangoItinerarios)
|
||||
return tmp.Take(k).ToList();
|
||||
|
||||
// Refinar con OSRM con concurrencia limitada (pero tiempo a pie = distancia / velocidad)
|
||||
var sem = new SemaphoreSlim(4, 4);
|
||||
var tasks = new List<Task>(tmp.Count);
|
||||
|
||||
foreach (var c in tmp)
|
||||
{
|
||||
tasks.Add(Task.Run(async () =>
|
||||
{
|
||||
await sem.WaitAsync(ct);
|
||||
try
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var (_, dist, _) = await WalkCached(lat, lon, c.Parada.Latitud, c.Parada.Longitud);
|
||||
if (dist > 0)
|
||||
{
|
||||
c.DistWalk = dist;
|
||||
c.TiempoWalk = EstimarDuracionPieSegundos(dist); // ✅ velocidad configurada
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
finally { sem.Release(); }
|
||||
}, ct));
|
||||
}
|
||||
|
||||
try { await Task.WhenAll(tasks); } catch { }
|
||||
|
||||
return tmp
|
||||
.OrderBy(x => x.TiempoWalk)
|
||||
.ThenBy(x => x.DistEucl)
|
||||
.Take(k)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
// =========================================================
|
||||
// ELEVACIÓN (Open-Elevation) — solo tramos andando
|
||||
// =========================================================
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona alt.
|
||||
/// </summary>
|
||||
private static string FmtAlt(double? v) => v is null ? "-" : $"{Math.Round(v.Value):0} m";
|
||||
|
||||
/// <summary>
|
||||
/// Formatea altitudes tramo.
|
||||
/// </summary>
|
||||
private static string FormatearAltitudesTramo(double? ini, double? fin, double? min, double? max)
|
||||
{
|
||||
var sb = new System.Text.StringBuilder();
|
||||
|
||||
sb.Append("<br /><div style='margin-left:1.25rem'>");
|
||||
sb.Append($"<div>Altitud:</div>");
|
||||
sb.Append($"<div style='margin-left:0.75rem'>• (ini→fin) = {FmtAlt(ini)} → {FmtAlt(fin)}</div>");
|
||||
|
||||
if (min is not null && max is not null)
|
||||
sb.Append($"<div style='margin-left:0.75rem'>• (min/max) = {Math.Round(min.Value):0} m – {Math.Round(max.Value):0} m</div>");
|
||||
|
||||
sb.Append("</div>");
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Obtiene elevacion punto.
|
||||
/// </summary>
|
||||
private async Task<double?> GetElevationPointAsync(double lat, double lon, CancellationToken ct)
|
||||
{
|
||||
var k = ElevKey(lat, lon);
|
||||
|
||||
if (_elevCache.TryGetValue(k, out var cached))
|
||||
return cached;
|
||||
|
||||
try
|
||||
{
|
||||
await ObtenerElevacionesPorLoteAsync(new List<(double lat, double lon)> { (lat, lon) }, ct);
|
||||
return _elevCache.TryGetValue(ElevKey(lat, lon), out var elev) ? elev : (double?)null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza las alturas (Z) de origen y destino para el resumen del detalle de ruta.
|
||||
@@ -768,65 +596,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(double? min, double? max)> GetElevationMinMaxForLineAsync(double[,]? line, CancellationToken ct)
|
||||
{
|
||||
if (line is null) return (null, null);
|
||||
if (line.GetLength(0) <= 0) return (null, null);
|
||||
|
||||
var sampled = SampleLine(line, _elevMaxPuntosPorTramo);
|
||||
if (sampled.Count == 0) return (null, null);
|
||||
|
||||
// misses
|
||||
var misses = new List<(double lat, double lon)>(sampled.Count);
|
||||
foreach (var (lat, lon) in sampled)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var k = ElevKey(lat, lon);
|
||||
if (!_elevCache.ContainsKey(k))
|
||||
{
|
||||
// redondeo para que cachee mejor y mande menos variedad
|
||||
misses.Add((Math.Round(lat, ELEV_DECIMALES_CACHE), Math.Round(lon, ELEV_DECIMALES_CACHE)));
|
||||
}
|
||||
}
|
||||
|
||||
// pedir en lotes
|
||||
if (misses.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < misses.Count; i += ELEV_BATCH_MAX_PUNTOS)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var batch = misses.Skip(i).Take(ELEV_BATCH_MAX_PUNTOS).ToList();
|
||||
await ObtenerElevacionesPorLoteAsync(batch, ct);
|
||||
}
|
||||
}
|
||||
|
||||
// calcular min/max con lo disponible
|
||||
double? min = null;
|
||||
double? max = null;
|
||||
|
||||
foreach (var (lat, lon) in sampled)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var k = ElevKey(lat, lon);
|
||||
if (_elevCache.TryGetValue(k, out var elev))
|
||||
{
|
||||
min = (min is null) ? elev : Math.Min(min.Value, elev);
|
||||
max = (max is null) ? elev : Math.Max(max.Value, elev);
|
||||
}
|
||||
}
|
||||
|
||||
return (min, max);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formatea elevacion minimo maximo.
|
||||
/// </summary>
|
||||
private static string FormatearElevacionMinMax(double? min, double? max)
|
||||
{
|
||||
if (min is null || max is null) return "";
|
||||
return $" · Altitud: {Math.Round(min.Value):0}-{Math.Round(max.Value):0} m";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona geometria completa variante.
|
||||
/// </summary>
|
||||
@@ -1115,167 +884,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
return tramos;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Busca rutas bus alternativas filtradas.
|
||||
/// </summary>
|
||||
private IReadOnlyList<RutaBusAlternativa> BuscarRutasBusAlternativasFiltradas(
|
||||
string codigoParadaOrigen,
|
||||
string codigoParadaDestino,
|
||||
int maxAlternativas)
|
||||
{
|
||||
EnsureBusIndexBuilt();
|
||||
|
||||
// -----------------------------------
|
||||
// RANGO transbordos (único):
|
||||
// - maxTrans entra al BFS como poda (si rango activo)
|
||||
// - minTrans se aplica solo como criterio de aceptación
|
||||
// -----------------------------------
|
||||
int minTrans = 0;
|
||||
int? maxTrans = null;
|
||||
|
||||
if (_activarRangoTransbordos)
|
||||
{
|
||||
minTrans = Math.Max(0, _minTransbordosPermitidos);
|
||||
var maxT = Math.Max(0, _maxTransbordosPermitidos);
|
||||
|
||||
if (minTrans > maxT) (minTrans, maxT) = (maxT, minTrans);
|
||||
|
||||
maxTrans = maxT; // ✅ poda BFS
|
||||
}
|
||||
|
||||
int minIt = Math.Min(_itinerarioMin, _itinerarioMax);
|
||||
int maxIt = Math.Max(_itinerarioMin, _itinerarioMax);
|
||||
|
||||
int minLin = Math.Min(_lineaMin, _lineaMax);
|
||||
int maxLin = Math.Max(_lineaMin, _lineaMax);
|
||||
var lineasPermitidas = ObtenerLineasPermitidasSeleccionadas();
|
||||
var lineasPermitidasKey = string.Join(",", lineasPermitidas);
|
||||
|
||||
// ✅ Cache key incluye rango de transbordos (min/max)
|
||||
var key =
|
||||
$"{codigoParadaOrigen}|{codigoParadaDestino}" +
|
||||
$"|itR:{(_activarRangoItinerarios ? 1 : 0)}|{minIt}|{maxIt}" +
|
||||
$"|lnR:{(_activarRangoLineas ? 1 : 0)}|{minLin}|{maxLin}|lnL:{lineasPermitidasKey}" +
|
||||
$"|alts:{maxAlternativas}" +
|
||||
$"|txR:{(_activarRangoTransbordos ? 1 : 0)}|minT:{minTrans}|maxT:{(maxTrans ?? -1)}";
|
||||
|
||||
if (_busFilteredCache.TryGetValue(key, out var cached))
|
||||
return cached;
|
||||
|
||||
var res = new List<RutaBusAlternativa>(Math.Max(1, maxAlternativas));
|
||||
if (maxAlternativas <= 0)
|
||||
{
|
||||
_busFilteredCache[key] = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
// Filtro por itinerarios/lineas durante BFS
|
||||
Func<InfoVariante, bool> filtro = _ => true;
|
||||
if (_activarRangoItinerarios || _activarRangoLineas)
|
||||
filtro = VariantePermitida;
|
||||
|
||||
string FirmaRuta(List<TramoBus> tramos) =>
|
||||
string.Join(";", tramos.Select(t =>
|
||||
$"{t.Variante.Linea.Codigo}-{t.Variante.CodigoItinerario}-{NormalizarSentidoCodigo(t.Variante.Sentido?.Codigo)}-{t.Variante.Variante.Codigo}-{t.IndiceInicio}-{t.IndiceFin}"));
|
||||
|
||||
// Firmas para evitar duplicados
|
||||
var firmas = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Cola de "prohibiciones" para forzar alternativas
|
||||
var prohibicionesPendientes = new Queue<HashSet<InfoVariante>>();
|
||||
prohibicionesPendientes.Enqueue(new HashSet<InfoVariante>()); // principal
|
||||
|
||||
// Evitar repetir exactamente la misma combinación de variantes prohibidas
|
||||
var firmasProhibiciones = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
firmasProhibiciones.Add(""); // vacío = principal
|
||||
|
||||
// Límite de seguridad para no explotar tiempo
|
||||
// Si hay mínimo > 0, damos más margen de exploración
|
||||
int limiteIntentos = (_activarRangoTransbordos && minTrans > 0) ? 10 : 12;
|
||||
int intentos = 0;
|
||||
|
||||
while (prohibicionesPendientes.Count > 0 &&
|
||||
res.Count < maxAlternativas &&
|
||||
intentos < limiteIntentos)
|
||||
{
|
||||
intentos++;
|
||||
|
||||
var prohibidas = prohibicionesPendientes.Dequeue();
|
||||
|
||||
bool FiltroConProhibidas(InfoVariante v)
|
||||
=> filtro(v) && !prohibidas.Contains(v);
|
||||
|
||||
var ruta = BuscarRutaBusConFiltro(
|
||||
codigoParadaOrigen,
|
||||
codigoParadaDestino,
|
||||
FiltroConProhibidas,
|
||||
out var trans,
|
||||
maxTrans);
|
||||
|
||||
if (ruta is null || ruta.Count == 0)
|
||||
continue;
|
||||
|
||||
var firma = FirmaRuta(ruta);
|
||||
|
||||
// Si la ruta ya la vimos, no la añadimos
|
||||
if (!firmas.Add(firma))
|
||||
continue;
|
||||
|
||||
double dist;
|
||||
_ = RedBusServicio.ConstruirLineasBus(ruta, out dist);
|
||||
|
||||
var alt = new RutaBusAlternativa
|
||||
{
|
||||
Tramos = ruta,
|
||||
CodigosParadasTransbordo = trans,
|
||||
NumeroTransbordos = UtilidadesTransitoDbus.ContarTransbordosReales(ruta),
|
||||
DistanciaTotalBus = dist
|
||||
};
|
||||
|
||||
// ✅ Aceptar solo si cumple rango (min/max)
|
||||
if (CumpleFiltroTransbordos(alt.NumeroTransbordos))
|
||||
{
|
||||
res.Add(alt);
|
||||
}
|
||||
|
||||
// ✅ Seguir explorando alternativas:
|
||||
// generar nuevas búsquedas prohibiendo una variante de esta ruta
|
||||
var variantesRuta = ruta.Select(t => t.Variante).Distinct().ToList();
|
||||
|
||||
foreach (var vProhibir in variantesRuta)
|
||||
{
|
||||
if (prohibicionesPendientes.Count >= 20)
|
||||
break;
|
||||
|
||||
var nuevoSet = new HashSet<InfoVariante>(prohibidas);
|
||||
nuevoSet.Add(vProhibir);
|
||||
|
||||
// firma estable de prohibiciones (por identidad lógica)
|
||||
var firmaProh = string.Join("|",
|
||||
nuevoSet
|
||||
.Select(v =>
|
||||
$"{v.Linea.Codigo}-{v.CodigoItinerario}-{NormalizarSentidoCodigo(v.Sentido?.Codigo)}-{v.Variante.Codigo}")
|
||||
.OrderBy(s => s, StringComparer.OrdinalIgnoreCase));
|
||||
|
||||
if (firmasProhibiciones.Add(firmaProh))
|
||||
prohibicionesPendientes.Enqueue(nuevoSet);
|
||||
}
|
||||
}
|
||||
|
||||
_busFilteredCache[key] = res;
|
||||
return res;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Deduplicación fuerte + coste
|
||||
// =========================================================
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona iv.
|
||||
/// </summary>
|
||||
private static string SentidoIV(int indiceInicio, int indiceFin)
|
||||
=> indiceFin >= indiceInicio ? "I" : "V";
|
||||
|
||||
/// <summary>
|
||||
/// Normaliza sentido codigo.
|
||||
/// </summary>
|
||||
@@ -1335,29 +943,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
return UtilidadesTransitoDbus.ContarTransbordosReales(alternativa?.Tramos);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona aprox seg.
|
||||
/// </summary>
|
||||
private double CosteAproxSeg(AlternativaRuta alt)
|
||||
{
|
||||
if (alt.EsSoloAPie)
|
||||
return alt.DuracionTotalAproxSegundos;
|
||||
|
||||
double t = 0;
|
||||
t += alt.TiempoPieInicio;
|
||||
t += alt.TiempoPieFin;
|
||||
double extraParadas = 0;
|
||||
foreach (var tb in alt.AlternativaBus!.Tramos)
|
||||
extraParadas += ContarParadasRecorridas(tb) * Math.Max(0, _segundosPorParadaBus);
|
||||
|
||||
t += EstimarDuracionBusSegundos(alt.AlternativaBus!.DistanciaTotalBus) + extraParadas;
|
||||
|
||||
t += alt.NumeroTransbordosReales * (_minutosTransbordo * 60.0);
|
||||
return t;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Acciones UI
|
||||
// =========================================================
|
||||
@@ -1629,186 +1214,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// Duración exacta (tarjetas = detalle) + velocidad a pie configurada
|
||||
// =========================================================
|
||||
|
||||
/// <summary>
|
||||
/// Calcula duracion exacta.
|
||||
/// </summary>
|
||||
private async Task<double> CalcularDuracionExactaAsync(AlternativaRuta alt, CancellationToken ct)
|
||||
{
|
||||
if (alt.EsSoloAPie)
|
||||
return alt.DuracionSoloAPie > 0 ? alt.DuracionSoloAPie : alt.DuracionTotalAproxSegundos;
|
||||
|
||||
var tramosBus = alt.AlternativaBus!.Tramos;
|
||||
|
||||
// 0) Hora base (real/simulada)
|
||||
var tCursor = AhoraLocal();
|
||||
|
||||
// Si horarios ON, asegúrate de tenerlos cargados
|
||||
if (_usarHorariosTeoricos)
|
||||
await EnsureHorariosLoadedAsync();
|
||||
|
||||
// 1) Resolver paradas reales inicio/fin de bus
|
||||
Parada paradaInicioBus = alt.OrigenParada;
|
||||
Parada paradaFinBus = alt.DestinoParada;
|
||||
|
||||
if (tramosBus.Count > 0)
|
||||
{
|
||||
var primerTramo = tramosBus.First();
|
||||
var ultimoTramo = tramosBus.Last();
|
||||
|
||||
string codIniReal = primerTramo.Variante.CodigosParadas[primerTramo.IndiceInicio];
|
||||
string codFinReal = ultimoTramo.Variante.CodigosParadas[ultimoTramo.IndiceFin];
|
||||
|
||||
paradaInicioBus = _paradaByCode.TryGetValue(codIniReal, out var pIni) ? pIni : alt.OrigenParada;
|
||||
paradaFinBus = _paradaByCode.TryGetValue(codFinReal, out var pFin) ? pFin : alt.DestinoParada;
|
||||
}
|
||||
|
||||
double total = 0;
|
||||
|
||||
// ✅ resetea distancias (para “Menos andar”, etc.)
|
||||
alt.DistanciaPieInicioAprox = 0;
|
||||
alt.DistanciaPieTransbordosAprox = 0;
|
||||
alt.DistanciaPieFinAprox = 0;
|
||||
|
||||
// 2) Origen -> inicio bus (walk)
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var w1 = await WalkCached(_coordenadasOrigen![0], _coordenadasOrigen[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud);
|
||||
|
||||
double distPie1 = (w1.dist > 0) ? w1.dist
|
||||
: CalcularDistanciaMetros(_coordenadasOrigen[0], _coordenadasOrigen[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud);
|
||||
|
||||
alt.DistanciaPieInicioAprox = distPie1;
|
||||
var durPie1 = EstimarDuracionPieSegundos(distPie1);
|
||||
|
||||
tCursor = AddSecondsSafe(tCursor, durPie1);
|
||||
total += durPie1;
|
||||
|
||||
// 3) BUS tramo a tramo (con esperas reales si horarios OK)
|
||||
Parada paradaUltimaBus = paradaInicioBus;
|
||||
|
||||
for (int i = 0; i < tramosBus.Count; i++)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
|
||||
var tramo = tramosBus[i];
|
||||
var v = tramo.Variante;
|
||||
|
||||
string codParadaIni = v.CodigosParadas[tramo.IndiceInicio];
|
||||
string codParadaFin = v.CodigosParadas[tramo.IndiceFin];
|
||||
|
||||
var pIniTramo = _paradaByCode.TryGetValue(codParadaIni, out var p0) ? p0 : paradaUltimaBus;
|
||||
var pFinTramo = _paradaByCode.TryGetValue(codParadaFin, out var p1) ? p1 : paradaUltimaBus;
|
||||
|
||||
// ---- Transbordo (si i>0): caminar si cambia parada + penalización fija ----
|
||||
if (i > 0)
|
||||
{
|
||||
if (!paradaUltimaBus.Codigo.Equals(codParadaIni, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var wt = await WalkCached(
|
||||
paradaUltimaBus.Latitud, paradaUltimaBus.Longitud,
|
||||
pIniTramo.Latitud, pIniTramo.Longitud);
|
||||
|
||||
double distT = (wt.dist > 0) ? wt.dist
|
||||
: CalcularDistanciaMetros(
|
||||
paradaUltimaBus.Latitud, paradaUltimaBus.Longitud,
|
||||
pIniTramo.Latitud, pIniTramo.Longitud);
|
||||
|
||||
alt.DistanciaPieTransbordosAprox += distT;
|
||||
|
||||
var durWalkT = EstimarDuracionPieSegundos(distT);
|
||||
tCursor = AddSecondsSafe(tCursor, durWalkT);
|
||||
total += durWalkT;
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Bus con horarios reales ----
|
||||
bool usoHorarios = _usarHorariosTeoricos && _horariosOk;
|
||||
|
||||
if (usoHorarios)
|
||||
{
|
||||
var fecha = DateOnly.FromDateTime(tCursor);
|
||||
var horaMin = TimeOnly.FromDateTime(tCursor);
|
||||
|
||||
string lineaCod = LineaCodigoStr(v);
|
||||
int itin = v.CodigoItinerario;
|
||||
string sentido = NormalizarSentidoCodigo(v.Sentido?.Codigo);
|
||||
|
||||
if (Horarios.TryGetNextBusSegment(
|
||||
codParadaIni, codParadaFin,
|
||||
lineaCod, itin, sentido,
|
||||
fecha, horaMin,
|
||||
out var dep, out var arr, out var _))
|
||||
{
|
||||
// Espera real si llegas antes de la salida
|
||||
if (dep > tCursor)
|
||||
{
|
||||
var wait = (dep - tCursor).TotalSeconds;
|
||||
if (wait > 0)
|
||||
{
|
||||
tCursor = dep;
|
||||
total += wait;
|
||||
}
|
||||
}
|
||||
|
||||
// Duración real bus
|
||||
var durBus = (arr - dep).TotalSeconds;
|
||||
if (durBus < 0) durBus = 0;
|
||||
|
||||
tCursor = AddSecondsSafe(tCursor, durBus);
|
||||
total += durBus;
|
||||
|
||||
paradaUltimaBus = pFinTramo;
|
||||
continue;
|
||||
}
|
||||
// si falla el tramo, caemos al fallback geométrico
|
||||
}
|
||||
|
||||
// ---- Fallback (como antes) ----
|
||||
// (geometría + segundos por parada)
|
||||
double distanciaTramo = 0;
|
||||
{
|
||||
// construir línea geom para este tramo (como haces en PintarRutaDesdeAlternativa)
|
||||
double distBusTotal;
|
||||
var lineasBus = RedBusServicio.ConstruirLineasBus(tramosBus, out distBusTotal);
|
||||
var lineaGeom = lineasBus[i];
|
||||
|
||||
for (int p = 1; p < lineaGeom.GetLength(0); p++)
|
||||
distanciaTramo += CalcularDistanciaMetros(lineaGeom[p - 1, 0], lineaGeom[p - 1, 1], lineaGeom[p, 0], lineaGeom[p, 1]);
|
||||
}
|
||||
|
||||
int paradasTramo = ContarParadasRecorridas(tramo);
|
||||
double extraParadasSeg = paradasTramo * Math.Max(0, _segundosPorParadaBus);
|
||||
|
||||
double duracionBusSegundos = EstimarDuracionBusSegundos(distanciaTramo) + extraParadasSeg;
|
||||
|
||||
tCursor = AddSecondsSafe(tCursor, duracionBusSegundos);
|
||||
total += duracionBusSegundos;
|
||||
|
||||
paradaUltimaBus = pFinTramo;
|
||||
}
|
||||
|
||||
// 4) Fin bus -> destino (walk)
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var w2 = await WalkCached(paradaFinBus.Latitud, paradaFinBus.Longitud, _coordenadasDestino![0], _coordenadasDestino[1]);
|
||||
|
||||
double distPie2 = (w2.dist > 0) ? w2.dist
|
||||
: CalcularDistanciaMetros(paradaFinBus.Latitud, paradaFinBus.Longitud, _coordenadasDestino[0], _coordenadasDestino[1]);
|
||||
|
||||
alt.DistanciaPieFinAprox = distPie2;
|
||||
|
||||
var durPie2 = EstimarDuracionPieSegundos(distPie2);
|
||||
tCursor = AddSecondsSafe(tCursor, durPie2);
|
||||
total += durPie2;
|
||||
|
||||
// ✅ guardar transbordos reales (para chips)
|
||||
alt.NumeroTransbordosReales = CalcularTransbordosReales(alt.AlternativaBus);
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Busca ruta desde buscador.
|
||||
/// </summary>
|
||||
@@ -2255,34 +1660,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona alternativa solo a pie.
|
||||
/// </summary>
|
||||
private static AlternativaRuta ClonarAlternativaSoloAPie(AlternativaRuta a)
|
||||
{
|
||||
// Copia “suficiente”: referencia a la línea (geometría) se puede compartir sin problema.
|
||||
// Lo importante es NO compartir la misma instancia porque cambiar Etiqueta afectaría al de abajo.
|
||||
return new AlternativaRuta
|
||||
{
|
||||
EsSoloAPie = true,
|
||||
ProveedorRuta = a.ProveedorRuta,
|
||||
|
||||
LineaSoloAPie = a.LineaSoloAPie,
|
||||
DistanciaSoloAPie = a.DistanciaSoloAPie,
|
||||
DuracionSoloAPie = a.DuracionSoloAPie,
|
||||
|
||||
DuracionTotalAproxSegundos = a.DuracionTotalAproxSegundos,
|
||||
PuntuacionCoste = a.PuntuacionCoste,
|
||||
|
||||
// mantenemos etiqueta original (luego la sobrescribimos a "Más rápida" arriba)
|
||||
Etiqueta = a.Etiqueta
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// =========================================================
|
||||
// Pintado
|
||||
// =========================================================
|
||||
|
||||
@@ -215,6 +215,8 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
color,
|
||||
category = categoria,
|
||||
slope = pendiente,
|
||||
distance = distancia,
|
||||
elevation = elevActual,
|
||||
label = pendiente is null
|
||||
? "-"
|
||||
: pendiente.Value.ToString("+0.0;-0.0;0.0", CultureInfo.InvariantCulture) + " %"
|
||||
|
||||
@@ -23,12 +23,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
/// </summary>
|
||||
private static string HHmm(DateTime dt) => dt.ToString("HH:mm", CultureInfo.InvariantCulture);
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
private static DateTime Compose(DateOnly d, TimeOnly t)
|
||||
=> d.ToDateTime(t);
|
||||
|
||||
/// <summary>
|
||||
/// Añade seconds safe.
|
||||
/// </summary>
|
||||
@@ -225,26 +219,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
private List<ProximaSalidaVm> _nextDeps = new();
|
||||
/// <summary>
|
||||
/// Gestiona txt desde ruta id.
|
||||
/// </summary>
|
||||
private string LineaTxtDesdeRutaId(string? rutaId)
|
||||
{
|
||||
var rid = CodigoSinDecimal(rutaId);
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rid))
|
||||
return $"L\u00ednea ?";
|
||||
|
||||
// Intenta mapear contra tu JSON de RedBus (Lineas)
|
||||
var linea = RedBusServicio.Lineas
|
||||
.FirstOrDefault(l => CodigoSinDecimal(l.Codigo?.ToString()) == rid);
|
||||
|
||||
if (linea is not null)
|
||||
return $"L\u00ednea {CodigoSinDecimal(linea.Codigo?.ToString())} - {linea.Nombre}";
|
||||
|
||||
// Fallback si no encontramos la línea en el JSON
|
||||
return $"L\u00ednea {rid}";
|
||||
}
|
||||
/// <summary>
|
||||
/// Gestiona toggle hora simulada.
|
||||
/// </summary>
|
||||
private async Task OnToggleHoraSimulada(ChangeEventArgs e)
|
||||
@@ -348,19 +322,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Restablece hora pruebas.
|
||||
/// </summary>
|
||||
private async Task ResetHoraPruebas()
|
||||
{
|
||||
_usarHoraSimulada = false;
|
||||
_fechaSimulada = DateOnly.FromDateTime(DateTime.Now);
|
||||
_horaSimulada = TimeOnly.FromDateTime(DateTime.Now);
|
||||
|
||||
StartClock();
|
||||
await RefrescarHoraUIAsync();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private string _horaAhoraTxt = "";
|
||||
|
||||
@@ -1484,14 +1484,6 @@
|
||||
@onblur="OnBlurCoeficienteEsfuerzoAltura" />
|
||||
</div>
|
||||
</div>
|
||||
@* <div class="config-row">
|
||||
<label>Minutos por Transbordo:</label>
|
||||
<div class="num-inline">
|
||||
<input type="number" class="config-input" step="1" @bind="_minutosTransbordo" @bind:event="oninput" />
|
||||
<button type="button" class="step-btn" @onclick="() => _minutosTransbordo = _minutosTransbordo + 1">▲</button>
|
||||
<button type="button" class="step-btn" @onclick="() => _minutosTransbordo = Math.Max(0, _minutosTransbordo - 1)">▼</button>
|
||||
</div>
|
||||
</div> *@
|
||||
<div class="config-row">
|
||||
<label>Método cálculo andando:</label>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user