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:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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: </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">
|
||||
|
||||
Reference in New Issue
Block a user