Optimizacion de codigo, limpieza residual y actualizacionde hover de elevaciones

This commit is contained in:
2026-07-14 12:13:53 +02:00
parent 4c71402e24
commit ba502e8ebf
12 changed files with 22 additions and 2931 deletions

View File

@@ -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>

View File

@@ -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;

View File

@@ -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
// =========================================================

View File

@@ -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) + " %"

View File

@@ -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 = "";

View File

@@ -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>