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>

View File

@@ -184,6 +184,7 @@ public partial class PlanificadorRutas
existente.HoraSimulada = actual.HoraSimulada;
existente.CriterioPrioritarioRuta = actual.CriterioPrioritarioRuta;
existente.PreferenciaEtiquetaRuta = actual.PreferenciaEtiquetaRuta;
existente.FactorEquilibradoAndarBus = actual.FactorEquilibradoAndarBus;
existente.ActivarFiltroLineas = actual.ActivarFiltroLineas;
existente.LineasPermitidas = actual.LineasPermitidas.ToList();
existente.FechaActualizacionUtc = DateTime.UtcNow;
@@ -369,6 +370,15 @@ public partial class PlanificadorRutas
_criterioPrioritarioRuta = NormalizarCriterioRuta(item.CriterioPrioritarioRuta, item.PreferenciaEtiquetaRuta);
SincronizarPreferenciaDesdeCriterioRuta();
if (item.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
{
_factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
SincronizarTextosPenalizacionPie();
if (_configuracionTemporalPreviaHistorial is not null)
_configuracionTemporalPreviaHistorial.FactorEquilibradoAndarBus = _factorEquilibradoAndarBus;
}
_activarRangoLineas = item.ActivarFiltroLineas;
_lineasPermitidas.Clear();
foreach (var linea in NormalizarLineasHistorial(item.LineasPermitidas))
@@ -445,6 +455,9 @@ public partial class PlanificadorRutas
HoraSimulada = TimeOnly.FromDateTime(ahora).ToString("HH:mm:ss", CultureInfo.InvariantCulture),
CriterioPrioritarioRuta = _criterioPrioritarioRuta,
PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta,
FactorEquilibradoAndarBus = double.IsFinite(_factorEquilibradoAndarBus)
? Math.Max(0, _factorEquilibradoAndarBus)
: 12.0,
ActivarFiltroLineas = _activarRangoLineas,
LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(),
FechaCreacionUtc = DateTime.UtcNow,
@@ -573,6 +586,9 @@ public partial class PlanificadorRutas
var fecha = item.UsarHoraSimulada ? item.FechaSimulada?.Trim() ?? string.Empty : string.Empty;
var hora = item.UsarHoraSimulada ? item.HoraSimulada?.Trim() ?? string.Empty : string.Empty;
var criterio = NormalizarCriterioRuta(item.CriterioPrioritarioRuta, item.PreferenciaEtiquetaRuta);
var factorEquilibrado = item.FactorEquilibradoAndarBus is { } factor && double.IsFinite(factor)
? Math.Max(0, factor).ToString("0.######", CultureInfo.InvariantCulture)
: string.Empty;
var lineas = item.ActivarFiltroLineas
? string.Join(",", NormalizarLineasHistorial(item.LineasPermitidas))
: string.Empty;
@@ -587,6 +603,7 @@ public partial class PlanificadorRutas
fecha,
hora,
criterio,
factorEquilibrado,
item.ActivarFiltroLineas ? "1" : "0",
lineas);
}

View File

@@ -1261,7 +1261,7 @@ public partial class PlanificadorRutas : ComponentBase
private bool _elevPopupMinimizado = false;
private const string _versionApp = "(v-20260626a)";
private const string _versionApp = "(v-20260709a)";
private double? _paradaElevG;

View File

@@ -35,6 +35,7 @@ public partial class PlanificadorRutas
public double? ParametroPenalizacionDistanciaPieMetros { get; set; }
public double? CoeficienteEsfuerzoAltura { get; set; }
public double? PesoTransbordoRankingMinutos { get; set; }
public double? FactorEquilibradoAndarBus { get; set; }
public double VelocidadPieKmH { get; set; }
public double VelocidadBusKmH { get; set; }
public int ElevMaxPuntosPorTramo { get; set; }
@@ -134,6 +135,7 @@ public partial class PlanificadorRutas
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros,
CoeficienteEsfuerzoAltura = _coeficienteEsfuerzoAltura,
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
VelocidadPieKmH = _velocidadPieKmH,
VelocidadBusKmH = _velocidadBusKmH,
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
@@ -188,6 +190,8 @@ public partial class PlanificadorRutas
: coeficienteAltura;
if (config.PesoTransbordoRankingMinutos is { } pesoTransbordo && double.IsFinite(pesoTransbordo))
_pesoTransbordoRankingMinutos = Math.Abs(pesoTransbordo - 8.0) < 0.0001 ? 4.0 : Math.Max(0, pesoTransbordo);
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
_factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
_velocidadPieKmH = Math.Clamp(config.VelocidadPieKmH, 0.5, 25.0);
_velocidadBusKmH = Math.Clamp(
Math.Abs(config.VelocidadBusKmH - 15.0) < 0.0001 ||
@@ -290,6 +294,7 @@ public partial class PlanificadorRutas
_parametroPenalizacionDistanciaPieMetros = 100.0;
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
_pesoTransbordoRankingMinutos = 4.0;
_factorEquilibradoAndarBus = 12.0;
_velocidadPieKmH = 3.6;
_velocidadBusKmH = 16.0;
_minutosTransbordo = 5;
@@ -336,6 +341,7 @@ public partial class PlanificadorRutas
_parametroPenalizacionDistanciaPieMetros = 100.0;
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
_pesoTransbordoRankingMinutos = 4.0;
_factorEquilibradoAndarBus = 12.0;
_velocidadPieKmH = 3.6;
_velocidadBusKmH = 16.0;
_minutosTransbordo = 5;

View File

@@ -1458,49 +1458,8 @@ public partial class PlanificadorRutas : ComponentBase
if (candidatas.Count == 0)
return _opcionesRuta.Count > 0 ? 0 : -1;
var duraciones = candidatas
.Select(x => DuracionOrdenacion(x.opcion))
.Where(double.IsFinite)
.ToList();
var costesAndar = candidatas
.Select(x => CalcularCosteAndarEquilibradoSegundos(x.opcion))
.Where(double.IsFinite)
.ToList();
if (duraciones.Count == 0 || costesAndar.Count == 0)
return candidatas
.OrderBy(x => x.opcion.PuntuacionCoste)
.ThenBy(x => DuracionOrdenacion(x.opcion))
.Select(x => x.indice)
.FirstOrDefault();
var minDuracion = duraciones.Min();
var maxDuracion = duraciones.Max();
var minAndar = costesAndar.Min();
var maxAndar = costesAndar.Max();
return candidatas
.Select(x =>
{
var sacrificioTiempo = CalcularSacrificioNormalizado(
DuracionOrdenacion(x.opcion),
minDuracion,
maxDuracion);
var sacrificioAndar = CalcularSacrificioNormalizado(
CalcularCosteAndarEquilibradoSegundos(x.opcion),
minAndar,
maxAndar);
return new
{
x.indice,
x.opcion,
PeorSacrificio = Math.Max(sacrificioTiempo, sacrificioAndar),
SacrificioMedio = (sacrificioTiempo + sacrificioAndar) / 2.0
};
})
.OrderBy(x => x.PeorSacrificio)
.ThenBy(x => x.SacrificioMedio)
.OrderBy(x => CalcularScoreEquilibradoFactor(x.opcion))
.ThenBy(x => x.opcion.NumeroTransbordosReales)
.ThenBy(x => x.opcion.TiempoEsperaTotalSegundos)
.ThenBy(x => x.opcion.PuntuacionCoste)
@@ -1528,6 +1487,14 @@ public partial class PlanificadorRutas : ComponentBase
.ThenBy(TiempoPieOrdenacion)
.ThenBy(opcion => opcion.PuntuacionCoste);
}
else if (CriterioPrioritarioEquilibradoActivo)
{
rutasBus = rutasBus
.OrderBy(CalcularScoreEquilibradoFactor)
.ThenBy(opcion => opcion.NumeroTransbordosReales)
.ThenBy(opcion => opcion.TiempoEsperaTotalSegundos)
.ThenBy(opcion => opcion.PuntuacionCoste);
}
var soloPie = _opcionesRuta
.Where(opcion => opcion.EsSoloAPie)
@@ -2141,6 +2108,7 @@ public partial class PlanificadorRutas : ComponentBase
FactorPenalizacionDistanciaPie = _factorPenalizacionDistanciaPie,
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros,
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
VelocidadBusKmH = _velocidadBusKmH,
MinutosPorTransbordo = _minutosTransbordo,
MargenTransbordoSegundos = Math.Clamp(_margenTransbordoSegundos, 0, 1800),

View File

@@ -66,6 +66,148 @@ public partial class PlanificadorRutas : ComponentBase
StateHasChanged();
}
}
private async Task ToggleElevacionesRutaAsync(ChangeEventArgs e)
{
_mostrarElevacionesRuta = e.Value switch
{
bool valor => valor,
string texto when bool.TryParse(texto, out var valor) => valor,
_ => !_mostrarElevacionesRuta
};
if (_opcionesRuta.Count == 0 ||
_indiceAlternativaSeleccionada < 0 ||
_indiceAlternativaSeleccionada >= _opcionesRuta.Count)
{
StateHasChanged();
return;
}
_pintandoElevacionesRuta = true;
_mensajeRuta = _mostrarElevacionesRuta
? "Aplicando elevaciones..."
: "Representando ruta seleccionada...";
StateHasChanged();
await Task.Yield();
try
{
await CerrarPopupsAsync();
await PintarOpcionRutaCompleta(_opcionesRuta[_indiceAlternativaSeleccionada]);
}
finally
{
_pintandoElevacionesRuta = false;
StateHasChanged();
}
}
private async Task DrawWalkingRouteJsAsync(
string id,
double[,] linea,
int weight,
int versionPintado,
bool arrowEnd = true)
{
var puntosElevacion = await ConstruirPuntosElevacionMapaAsync(linea, CancellationToken.None);
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteJsAsync(
id,
linea,
"#dc2626",
weight,
0.9,
dashArray: DashArrayPiePunteado,
arrowEnd: arrowEnd,
lineCap: "round");
if (puntosElevacion is { Count: > 0 })
{
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteElevationPointsJsAsync(id, puntosElevacion);
}
}
private async Task<List<object>?> ConstruirPuntosElevacionMapaAsync(double[,] linea, CancellationToken ct)
{
if (!_mostrarElevacionesRuta || linea.GetLength(0) < 2)
return null;
var muestras = await ConstruirMuestrasElevacionAsync(linea, ct);
if (muestras.Count < 2)
return null;
var puntos = new List<object>();
var hayPendienteCalculada = false;
for (var i = 1; i < muestras.Count; i++)
{
ct.ThrowIfCancellationRequested();
var anterior = muestras[i - 1];
var actual = muestras[i];
var distancia = CalcularDistanciaMetros(
anterior.Latitud,
anterior.Longitud,
actual.Latitud,
actual.Longitud);
if (distancia <= 0.5)
continue;
var elevAnterior = ElevacionPreferidaMapa(anterior);
var elevActual = ElevacionPreferidaMapa(actual);
double? pendiente = null;
if (elevAnterior is not null && elevActual is not null)
{
pendiente = (elevActual.Value - elevAnterior.Value) / distancia * 100.0;
hayPendienteCalculada = true;
}
var (categoria, color) = ClasificarPendienteMapa(pendiente);
puntos.Add(new
{
lat = actual.Latitud,
lon = actual.Longitud,
color,
category = categoria,
slope = pendiente,
label = pendiente is null
? "-"
: pendiente.Value.ToString("+0.0;-0.0;0.0", CultureInfo.InvariantCulture) + " %"
});
}
return hayPendienteCalculada && puntos.Count > 0
? puntos
: null;
}
private static double? ElevacionPreferidaMapa(MuestraElevacion muestra)
=> muestra.ElevacionIdee ?? muestra.Elevacion;
private static (string Categoria, string Color) ClasificarPendienteMapa(double? pendientePorcentaje)
{
if (pendientePorcentaje is null || !double.IsFinite(pendientePorcentaje.Value))
return ("sin-datos", "#94a3b8");
if (pendientePorcentaje.Value >= 8.0)
return ("subida-fuerte", "#ef4444");
if (pendientePorcentaje.Value >= 3.0)
return ("subida", "#f97316");
if (pendientePorcentaje.Value <= -8.0)
return ("bajada-fuerte", "#1d4ed8");
if (pendientePorcentaje.Value <= -3.0)
return ("bajada", "#38bdf8");
return ("llano", "#facc15");
}
@@ -95,6 +237,8 @@ public partial class PlanificadorRutas : ComponentBase
await PintarParadasSolucionSeleccionadaAsync(opcion, versionPintado);
}
ThrowIfPintadoObsoleto(versionPintado);
await ActualizarAlturasResumenRutaAsync(CancellationToken.None);
ThrowIfPintadoObsoleto(versionPintado);
await CerrarPopupsAsync();
@@ -207,7 +351,7 @@ public partial class PlanificadorRutas : ComponentBase
});
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteJsAsync("walk_full", linea, "#dc2626", 5, 0.9, dashArray: DashArrayPiePunteado, arrowEnd: true, lineCap: "round");
await DrawWalkingRouteJsAsync("walk_full", linea, 5, versionPintado);
ThrowIfPintadoObsoleto(versionPintado);
_mensajeRuta = CapitalizarPalabrasClave(
@@ -307,7 +451,7 @@ public partial class PlanificadorRutas : ComponentBase
});
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteJsAsync("walk_1", w1.line, "#dc2626", 4, 0.9, dashArray: DashArrayPiePunteado, arrowEnd: true, lineCap: "round");
await DrawWalkingRouteJsAsync("walk_1", w1.line, 4, versionPintado);
ThrowIfPintadoObsoleto(versionPintado);
}
@@ -384,7 +528,7 @@ public partial class PlanificadorRutas : ComponentBase
});
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteJsAsync($"walk_union_{i}", lineaUnion, "#dc2626", 4, 0.9, dashArray: DashArrayPiePunteado, arrowEnd: true, lineCap: "round");
await DrawWalkingRouteJsAsync($"walk_union_{i}", lineaUnion, 4, versionPintado);
ThrowIfPintadoObsoleto(versionPintado);
ultimoPuntoDibujado = ObtenerUltimoPunto(lineaUnion);
}
@@ -435,7 +579,7 @@ public partial class PlanificadorRutas : ComponentBase
});
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteJsAsync($"walk_transfer_real_{i}", lineaTransferencia, "#dc2626", 4, 0.9, dashArray: DashArrayPiePunteado, arrowEnd: true, lineCap: "round");
await DrawWalkingRouteJsAsync($"walk_transfer_real_{i}", lineaTransferencia, 4, versionPintado);
ThrowIfPintadoObsoleto(versionPintado);
ultimoPuntoDibujado = ObtenerUltimoPunto(lineaTransferencia);
@@ -478,7 +622,7 @@ public partial class PlanificadorRutas : ComponentBase
});
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteJsAsync($"walk_transfer_{i}", wt.line, "#dc2626", 4, 0.9, dashArray: DashArrayPiePunteado, arrowEnd: true, lineCap: "round");
await DrawWalkingRouteJsAsync($"walk_transfer_{i}", wt.line, 4, versionPintado);
ThrowIfPintadoObsoleto(versionPintado);
}
}
@@ -688,7 +832,7 @@ public partial class PlanificadorRutas : ComponentBase
});
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteJsAsync("walk_union_final", lineaUnionFinal, "#dc2626", 4, 0.9, dashArray: DashArrayPiePunteado, arrowEnd: true, lineCap: "round");
await DrawWalkingRouteJsAsync("walk_union_final", lineaUnionFinal, 4, versionPintado);
ThrowIfPintadoObsoleto(versionPintado);
EstablecerPrimerPunto(w2.line, ObtenerUltimoPunto(lineaUnionFinal));
}
@@ -715,7 +859,7 @@ public partial class PlanificadorRutas : ComponentBase
});
ThrowIfPintadoObsoleto(versionPintado);
await DrawRouteJsAsync("walk_2", w2.line, "#dc2626", 4, 0.9, dashArray: DashArrayPiePunteado, arrowEnd: true, lineCap: "round");
await DrawWalkingRouteJsAsync("walk_2", w2.line, 4, versionPintado);
ThrowIfPintadoObsoleto(versionPintado);
}

View File

@@ -628,7 +628,7 @@
class="route-priority-option @(CriterioPrioritarioEquilibradoActivo ? "route-priority-option-active" : "")"
title="Mantener el criterio equilibrado actual"
@onclick="@(() => CambiarCriterioPrioritarioRutasAsync(CriterioRutaEquilibrado))">
Equilibrado*
Equilib. A
</button>
<button type="button"
class="route-priority-option @(CriterioPrioritarioTiempoActivo ? "route-priority-option-active" : "")"
@@ -639,6 +639,22 @@
</div>
</div>
<div class="route-visual-options">
<label class="route-elevation-switch @(_mostrarElevacionesRuta ? "route-elevation-switch--active" : "")"
title="Colorear tramos a pie segun pendiente">
<input type="checkbox"
checked="@_mostrarElevacionesRuta"
disabled="@(_pintandoElevacionesRuta || _opcionesRuta.Count == 0)"
@onchange="ToggleElevacionesRutaAsync" />
<span class="route-elevation-switch-track">
<span class="route-elevation-switch-thumb"></span>
</span>
<span class="route-elevation-switch-text">
@(_pintandoElevacionesRuta ? "Elevaciones..." : "Elevaciones")
</span>
</label>
</div>
@if (_opcionesRuta.Count > 0)
{
<div class="route-alt-list">
@@ -684,20 +700,9 @@
{
<span>@FormatearDistanciaTarjeta(alt.AlternativaBus!.DistanciaTotalBus) en bus</span>
<span> · @alt.NumeroTransbordosReales Transbordos</span>
<span class="route-alt-walk @(CriterioPrioritarioAndarActivo ? "route-alt-walk-emphasis" : "")">
<span class="route-alt-walk @((CriterioPrioritarioAndarActivo || CriterioPrioritarioEquilibradoActivo) ? "route-alt-walk-emphasis" : "")">
· @FormatearDistanciaTarjeta(alt.DistanciaPieTotalAprox) a pie aprox.
</span>
@if (CriterioPrioritarioEquilibradoActivo)
{
var balanceEquilibrado = FormatearBalanceEquilibradoAlternativa(alt);
if (!string.IsNullOrWhiteSpace(balanceEquilibrado))
{
<span class="route-alt-balance"
title="Comparacion dentro de esta lista: cercania a la solucion mas rapida frente a la que menos camina">
- Eq. @balanceEquilibrado
</span>
}
}
}
</div>
@@ -881,8 +886,8 @@
var xyzOrigen = FormatearResumenXYZOrigen();
var xyzDestino = FormatearResumenXYZDestino();
var tiempoCalculo = FormatearTiempoCalculoRuta();
var balanceEquilibradoSeleccionado = CriterioPrioritarioEquilibradoActivo
? FormatearBalanceEquilibradoAlternativa(altSeleccionada)
var scoreEquilibradoSeleccionado = CriterioPrioritarioEquilibradoActivo
? FormatearScoreEquilibradoAlternativa(altSeleccionada)
: string.Empty;
// var costePenalizado = ObtenerCostePenalizadoAlternativa(altSeleccionada);
// var incrementoPenalizacion = Math.Max(0, costePenalizado - Math.Max(0, duracionVisible));
@@ -907,9 +912,9 @@
{
<div>d.Corregida a pie: @FormatearDistanciaDetalleRuta(dCorregida)</div>
}
@if (!string.IsNullOrWhiteSpace(balanceEquilibradoSeleccionado))
@if (!string.IsNullOrWhiteSpace(scoreEquilibradoSeleccionado))
{
<div>Balance equilibrado: @balanceEquilibradoSeleccionado</div>
<div>Score equilibrado: @scoreEquilibradoSeleccionado</div>
}
@if (!string.IsNullOrWhiteSpace(xyzOrigen))
{
@@ -1152,24 +1157,29 @@
}
</label>
<div class="line-filter__range line-filter__range--segundos-parada">
<span class="muted">Segundos por parada:&nbsp;</span>
<div class="num-inline">
@if (_usarHorariosTeoricos)
{
<input type="number" class="config-input" step="1" min="0" value="0" disabled readonly />
<button type="button" class="step-btn" disabled>▲</button>
<button type="button" class="step-btn" disabled>▼</button>
}
else
{
<input type="number" class="config-input" step="1" min="0"
@bind="_segundosPorParadaBus" @bind:event="oninput" @bind:after="OnSegundosPorParadaBusChanged" />
<button type="button" class="step-btn" @onclick="async () => { _segundosPorParadaBus = _segundosPorParadaBus + 1; await OnSegundosPorParadaBusChanged(); }">▲</button>
<button type="button" class="step-btn" @onclick="async () => { _segundosPorParadaBus = Math.Max(0, _segundosPorParadaBus - 1); await OnSegundosPorParadaBusChanged(); }">▼</button>
}
</div>
</div>
</div>
</div>
<div class="config-row">
<label>Bus/paradas:</label>
<div class="config-dual-labeled">
<label class="config-dual-labeled__field">
<span>Seg. parada</span>
@if (_usarHorariosTeoricos)
{
<input type="number" class="config-input" step="1" min="0" value="0" disabled readonly />
}
else
{
<input type="number" class="config-input" step="1" min="0"
@bind="_segundosPorParadaBus" @bind:event="oninput" @bind:after="OnSegundosPorParadaBusChanged" />
}
</label>
<label class="config-dual-labeled__field">
<span>Radio (m)</span>
<input type="number" class="config-input" min="200" max="3500" step="50"
@bind="_radioCandidatasMetros" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
</label>
</div>
</div>
@@ -1346,7 +1356,7 @@
</div>
</div>
<div class="config-row">
<div class="config-row config-row--radio-legacy">
<label>Coef. / Param.(m) andando:</label>
<div class="config-dual-inline config-dual-inline--bare">
<input type="number" class="config-input" step="any"
@@ -1382,6 +1392,23 @@
</div>
</div>
<div class="config-row">
<label>Equilibrado:</label>
<div class="config-dual-labeled config-dual-labeled--equilibrado">
<label class="config-dual-labeled__field">
<span>Factor</span>
<input type="number" class="config-input" step="0.1" min="0"
placeholder="12"
title="1 metro andando pesa como este numero de metros en bus"
@bind="FactorEquilibradoAndarBusTexto" @bind:event="oninput"
@onblur="OnBlurFactorEquilibradoAndarBus" />
</label>
<span class="muted config-equilibrado-help" title="Formula: metros en bus + metros andando x factor">
1 m andando = @FactorEquilibradoAndarBusResumen m bus
</span>
</div>
</div>
<div class="config-row">
<label>Vel. Andando/Bus (km/h):</label>
<div class="config-dual-inline config-dual-inline--bare">
@@ -1410,20 +1437,6 @@
@onblur="OnBlurCoeficienteEsfuerzoAltura" />
</div>
</div>
<div class="config-row">
<label>Radio paradas candidatas (m):</label>
<div class="num-inline">
<input type="number" class="config-input" min="200" max="3500" step="50"
@bind="_radioCandidatasMetros" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
<button type="button" class="step-btn"
@onclick="async () => { _radioCandidatasMetros = Math.Min(3500, _radioCandidatasMetros + 50); await PersistirConfiguracionAsync(); }">▲</button>
<button type="button" class="step-btn"
@onclick="async () => { _radioCandidatasMetros = Math.Max(200, _radioCandidatasMetros - 50); await PersistirConfiguracionAsync(); }">▼</button>
</div>
</div>
@* <div class="config-row">
<label>Minutos por Transbordo:</label>
<div class="num-inline">