Añadidas mejoras en rutas sugeridas, elevaciones e historial

- Añadido interruptor de elevaciones en rutas sugeridas.
- Los tramos a pie mantienen la línea punteada y muestran puntos coloreados por pendiente.
- Se quita el score equilibrado de las tarjetas para limpiar la vista.
- Se destaca la distancia a pie también en modo Equilib. A.
- El factor equilibrado se guarda y restaura desde el historial local y compartido.
- Ajustes visuales y limpieza de estilos asociados.
This commit is contained in:
2026-07-09 14:56:53 +02:00
parent 8b106a8319
commit 7edb2dd2e9
14 changed files with 763 additions and 289 deletions

View File

@@ -55,10 +55,12 @@ public partial class PlanificadorRutas : ComponentBase
private const double CoeficienteEsfuerzoAlturaPorDefecto = 5.2;
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
private double _pesoTransbordoRankingMinutos = 4.0;
private double _factorEquilibradoAndarBus = 12.0;
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.60";
private string _parametroPenalizacionDistanciaPieTextoEdicion = "100";
private string _coeficienteEsfuerzoAlturaTextoEdicion = "5.20";
private string _pesoTransbordoRankingTextoEdicion = "4,0";
private string _factorEquilibradoAndarBusTextoEdicion = "12";
private double _velocidadBusKmH = 16.0;
private int _minutosTransbordo = 5;
private int _margenTransbordoSegundos = 120;
@@ -142,6 +144,25 @@ public partial class PlanificadorRutas : ComponentBase
}
}
/// <summary>
/// Expone el factor que convierte metros andando a metros equivalentes de bus en el modo equilibrado.
/// </summary>
private string FactorEquilibradoAndarBusTexto
{
get => _factorEquilibradoAndarBusTextoEdicion;
set
{
var texto = (value ?? "").Replace(',', '.');
_factorEquilibradoAndarBusTextoEdicion = texto;
if (!double.TryParse(texto, NumberStyles.Float, CultureInfo.InvariantCulture, out var valor))
return;
if (double.IsFinite(valor))
_factorEquilibradoAndarBus = Math.Max(0, valor);
}
}
private static bool TryParseDuracionMinutos(string texto, out double minutos)
{
minutos = 0;
@@ -220,6 +241,7 @@ public partial class PlanificadorRutas : ComponentBase
_parametroPenalizacionDistanciaPieTextoEdicion = _parametroPenalizacionDistanciaPieMetros.ToString("0", CultureInfo.InvariantCulture);
_coeficienteEsfuerzoAlturaTextoEdicion = _coeficienteEsfuerzoAltura.ToString("0.00", CultureInfo.InvariantCulture);
_pesoTransbordoRankingTextoEdicion = FormatearDuracionMinutos(_pesoTransbordoRankingMinutos);
_factorEquilibradoAndarBusTextoEdicion = _factorEquilibradoAndarBus.ToString("0.##", CultureInfo.InvariantCulture);
}
/// <summary>
@@ -257,6 +279,16 @@ public partial class PlanificadorRutas : ComponentBase
SincronizarTextosPenalizacionPie();
await PersistirConfiguracionAsync();
}
/// <summary>
/// Normaliza el factor equilibrado al salir del campo.
/// </summary>
private async Task OnBlurFactorEquilibradoAndarBus()
{
SincronizarTextosPenalizacionPie();
await PersistirConfiguracionAsync();
await RecalcularRutaEquilibradaTrasCambioFactorAsync();
}
private int _radioCandidatasMetros = 1200;
private int _maxCandidatasPorLado = 12;
@@ -317,6 +349,8 @@ public partial class PlanificadorRutas : ComponentBase
private int _indiceAlternativaSeleccionada = 0;
private DateTime? _ultimaHoraReferenciaRuta = null;
private TimeSpan? _tiempoUltimoCalculoRuta = null;
private bool _mostrarElevacionesRuta = false;
private bool _pintandoElevacionesRuta = false;
private readonly Dictionary<string, List<Parada>> _paradasItinerarioVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, List<Parada>> _paradasRutaVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
@@ -1016,6 +1050,23 @@ public partial class PlanificadorRutas : ComponentBase
}
catch { }
}
private async Task DrawRouteElevationPointsJsAsync(string id, IReadOnlyList<object> points)
{
try
{
await JS.InvokeVoidAsync("rt.drawElevPoints", $"route_elev_{id}", points, new
{
radius = 4,
opacity = 0.98,
fillOpacity = 0.95,
showLabels = false,
showSlopeTooltips = true,
className = "rt-route-elev-point"
});
}
catch { }
}
/// <summary>
/// Ajusta rutas js.
@@ -1168,55 +1219,6 @@ public partial class PlanificadorRutas : ComponentBase
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieFinAprox, parametros);
}
/// <summary>
/// Estima si una alternativa equilibrada se parece mas al criterio Tiempo o al criterio Andar.
/// </summary>
private string FormatearBalanceEquilibradoAlternativa(AlternativaRuta? alternativa)
{
if (alternativa is null || alternativa.EsSoloAPie)
return string.Empty;
var alternativasBus = _opcionesRuta
.Where(opcion => !opcion.EsSoloAPie)
.ToList();
if (alternativasBus.Count < 2)
return string.Empty;
var duraciones = alternativasBus
.Select(DuracionOrdenacion)
.Where(double.IsFinite)
.ToList();
var costesAndar = alternativasBus
.Select(CalcularCosteAndarEquilibradoSegundos)
.Where(double.IsFinite)
.ToList();
if (duraciones.Count == 0 || costesAndar.Count == 0)
return string.Empty;
var ventajaTiempo = CalcularVentajaNormalizada(
DuracionOrdenacion(alternativa),
duraciones.Min(),
duraciones.Max());
var ventajaAndar = CalcularVentajaNormalizada(
CalcularCosteAndarEquilibradoSegundos(alternativa),
costesAndar.Min(),
costesAndar.Max());
var total = ventajaTiempo + ventajaAndar;
if (!double.IsFinite(total) || total <= 0.000001)
return string.Empty;
var porcentajeTiempo = (int)Math.Round((ventajaTiempo / total) * 100.0);
porcentajeTiempo = Math.Clamp(porcentajeTiempo, 0, 100);
var porcentajeAndar = 100 - porcentajeTiempo;
return $"Tiempo {porcentajeTiempo}% / Andar {porcentajeAndar}%";
}
private double CalcularCosteAndarEquilibradoSegundos(AlternativaRuta? alternativa)
{
if (alternativa is null)
@@ -1229,30 +1231,70 @@ public partial class PlanificadorRutas : ComponentBase
return EstimarDuracionPieSegundos(distanciaCorregida);
}
private static double CalcularVentajaNormalizada(double valor, double mejor, double peor)
private double CalcularScoreEquilibradoFactor(AlternativaRuta? alternativa)
{
if (!double.IsFinite(valor) ||
!double.IsFinite(mejor) ||
!double.IsFinite(peor) ||
peor <= mejor)
{
return 0;
}
if (alternativa is null || alternativa.EsSoloAPie || alternativa.AlternativaBus is null)
return double.PositiveInfinity;
return Math.Clamp((peor - valor) / (peor - mejor), 0.0, 1.0);
var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
var distanciaAndando = Math.Max(0, alternativa.DistanciaPieTotalAprox);
var factor = double.IsFinite(_factorEquilibradoAndarBus)
? Math.Max(0, _factorEquilibradoAndarBus)
: 12.0;
var score = distanciaBus + (distanciaAndando * factor);
return double.IsFinite(score) && score >= 0
? score
: double.PositiveInfinity;
}
private static double CalcularSacrificioNormalizado(double valor, double mejor, double peor)
private string FormatearScoreEquilibradoAlternativa(AlternativaRuta? alternativa)
{
if (!double.IsFinite(valor) ||
!double.IsFinite(mejor) ||
!double.IsFinite(peor) ||
peor <= mejor)
var score = CalcularScoreEquilibradoFactor(alternativa);
if (!double.IsFinite(score))
return string.Empty;
return $"Score {FormatearDistanciaTarjeta(score)} (x{_factorEquilibradoAndarBus.ToString("0.##", CultureInfo.InvariantCulture)})";
}
private string FactorEquilibradoAndarBusResumen
=> _factorEquilibradoAndarBus.ToString("0.##", CultureInfo.InvariantCulture);
private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync()
{
if (!CriterioPrioritarioEquilibradoActivo)
return;
if (_mapa is not null && _coordenadasOrigen is not null && _coordenadasDestino is not null)
{
return 0;
_omitirRegistroHistorialEnSiguienteCalculo = true;
await CalcularRutaCompletaAsync();
return;
}
return Math.Clamp((valor - mejor) / (peor - mejor), 0.0, 1.0);
if (_opcionesRuta.Count == 0)
{
StateHasChanged();
return;
}
OrdenarOpcionesRutaSegunCriterio();
var indicePreferido = BuscarIndiceEquilibrado();
if (indicePreferido < 0 || indicePreferido >= _opcionesRuta.Count)
{
StateHasChanged();
return;
}
await CerrarTodasLasPreviewsAsync();
_indiceAlternativaSeleccionada = indicePreferido;
await CerrarPopupsAsync();
ResetEstadoRuta(limpiarOpciones: false, limpiarMensaje: false);
_mensajeRuta = "Representando ruta seleccionada...";
StateHasChanged();
await Task.Yield();
await PintarOpcionRutaCompleta(_opcionesRuta[indicePreferido]);
StateHasChanged();
}
/// <summary>