- historial arreglado
- solucion 1/2 - quitar segundo score y colocar el score bien calculado - score equilibrado a - no usar pendientes - flechas arregaldas con el zoom - intinerario permitidos hasta el 99 - añadir peso a los transbordos factor equilibrado a 6 visualizar pendientes en la confi avanzada cambiar pisos por metros
This commit is contained in:
@@ -50,9 +50,10 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private const double FactorPenalizacionDistanciaPiePorDefecto = 1.35;
|
private const double FactorPenalizacionDistanciaPiePorDefecto = 1.35;
|
||||||
private const double ParametroPenalizacionDistanciaPieMetrosPorDefecto = 200.0;
|
private const double ParametroPenalizacionDistanciaPieMetrosPorDefecto = 200.0;
|
||||||
private const double CoeficienteEsfuerzoAlturaPorDefecto = 8.2;
|
private const double CoeficienteEsfuerzoAlturaPorDefecto = 8.2;
|
||||||
|
private const double FactorEquilibradoAndarBusPorDefecto = 6.0;
|
||||||
private const double UmbralPendienteSubidaRutaPorDefecto = 2.0;
|
private const double UmbralPendienteSubidaRutaPorDefecto = 2.0;
|
||||||
private const double UmbralPendienteBajadaRutaPorDefecto = 5.0;
|
private const double UmbralPendienteBajadaRutaPorDefecto = 5.0;
|
||||||
private const double UmbralPisosPendienteRutaPorDefecto = 2.0;
|
private const double UmbralAlturaAlertaPendienteRutaMetrosPorDefecto = 6.0;
|
||||||
private const double MetrosPorPiso = 3.0;
|
private const double MetrosPorPiso = 3.0;
|
||||||
|
|
||||||
private double _velocidadPieKmH = 3.6;
|
private double _velocidadPieKmH = 3.6;
|
||||||
@@ -60,12 +61,12 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private double _parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
private double _parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||||
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
private double _pesoTransbordoRankingMinutos = 4.0;
|
private double _pesoTransbordoRankingMinutos = 4.0;
|
||||||
private double _factorEquilibradoAndarBus = 12.0;
|
private double _factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||||
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.35";
|
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.35";
|
||||||
private string _parametroPenalizacionDistanciaPieTextoEdicion = "200";
|
private string _parametroPenalizacionDistanciaPieTextoEdicion = "200";
|
||||||
private string _coeficienteEsfuerzoAlturaTextoEdicion = "8.20";
|
private string _coeficienteEsfuerzoAlturaTextoEdicion = "8.20";
|
||||||
private string _pesoTransbordoRankingTextoEdicion = "4,0";
|
private string _pesoTransbordoRankingTextoEdicion = "4,0";
|
||||||
private string _factorEquilibradoAndarBusTextoEdicion = "12";
|
private string _factorEquilibradoAndarBusTextoEdicion = "6";
|
||||||
private double _velocidadBusKmH = 16.0;
|
private double _velocidadBusKmH = 16.0;
|
||||||
private int _minutosTransbordo = 5;
|
private int _minutosTransbordo = 5;
|
||||||
private int _margenTransbordoSegundos = 120;
|
private int _margenTransbordoSegundos = 120;
|
||||||
@@ -275,7 +276,11 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
{
|
{
|
||||||
SincronizarTextosPenalizacionPie();
|
SincronizarTextosPenalizacionPie();
|
||||||
await PersistirConfiguracionAsync();
|
await PersistirConfiguracionAsync();
|
||||||
|
|
||||||
|
if (_mostrarElevacionesRuta)
|
||||||
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
||||||
|
else
|
||||||
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -285,6 +290,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
{
|
{
|
||||||
SincronizarTextosPenalizacionPie();
|
SincronizarTextosPenalizacionPie();
|
||||||
await PersistirConfiguracionAsync();
|
await PersistirConfiguracionAsync();
|
||||||
|
await RecalcularRutaEquilibradaTrasCambioFactorAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -364,14 +370,14 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private bool _panelRutasMovilContraido = false;
|
private bool _panelRutasMovilContraido = false;
|
||||||
private double _umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
private double _umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||||
private double _umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
private double _umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||||
private double _umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
private double _umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||||
private double _alturaPendientePositivaSignificativaRutaMetros = 0.0;
|
private double _alturaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||||
private double _distanciaPendientePositivaSignificativaRutaMetros = 0.0;
|
private double _distanciaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||||
|
|
||||||
private bool MostrarAvisoPendientesPositivasSignificativas =>
|
private bool MostrarAlertaPendientesPositivasSignificativas =>
|
||||||
_mostrarElevacionesRuta &&
|
_mostrarElevacionesRuta &&
|
||||||
_alturaPendientePositivaSignificativaRutaMetros >
|
_alturaPendientePositivaSignificativaRutaMetros >
|
||||||
(NormalizarUmbralPisosPendienteRuta(_umbralPisosPendienteRuta) * MetrosPorPiso);
|
NormalizarUmbralAlturaAlertaPendienteRutaMetros(_umbralAlturaAlertaPendienteRutaMetros);
|
||||||
private readonly Dictionary<string, List<Parada>> _paradasItinerarioVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, List<Parada>> _paradasItinerarioVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly Dictionary<string, List<Parada>> _paradasRutaVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, List<Parada>> _paradasRutaVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -1351,15 +1357,18 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Construye los parametros actuales que afectan a la penalizacion peatonal.
|
/// Construye los parametros actuales que afectan al ranking de rutas.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private ParametrosPlanificadorRuta CrearParametrosPenalizacionActuales()
|
private ParametrosPlanificadorRuta CrearParametrosPenalizacionActuales()
|
||||||
{
|
{
|
||||||
return new ParametrosPlanificadorRuta
|
return new ParametrosPlanificadorRuta
|
||||||
{
|
{
|
||||||
VelocidadPieKmH = _velocidadPieKmH,
|
VelocidadPieKmH = _velocidadPieKmH,
|
||||||
|
VelocidadBusKmH = _velocidadBusKmH,
|
||||||
FactorPenalizacionDistanciaPie = _factorPenalizacionDistanciaPie,
|
FactorPenalizacionDistanciaPie = _factorPenalizacionDistanciaPie,
|
||||||
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros
|
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros,
|
||||||
|
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
||||||
|
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1386,7 +1395,8 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
var distanciaCorregidaBase = UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaTotalMetros(
|
var distanciaCorregidaBase = UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaTotalMetros(
|
||||||
distanciasPiePorTramo,
|
distanciasPiePorTramo,
|
||||||
parametros);
|
parametros);
|
||||||
var esfuerzoExtra = double.IsFinite(alternativa.EsfuerzoExtraPendienteMetros)
|
var esfuerzoExtra = _mostrarElevacionesRuta &&
|
||||||
|
double.IsFinite(alternativa.EsfuerzoExtraPendienteMetros)
|
||||||
? Math.Max(0, alternativa.EsfuerzoExtraPendienteMetros)
|
? Math.Max(0, alternativa.EsfuerzoExtraPendienteMetros)
|
||||||
: 0;
|
: 0;
|
||||||
|
|
||||||
@@ -1416,7 +1426,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
var distanciaAndando = CalcularDistanciaCorregidaAlternativa(alternativa);
|
var distanciaAndando = CalcularDistanciaCorregidaAlternativa(alternativa);
|
||||||
var factor = double.IsFinite(_factorEquilibradoAndarBus)
|
var factor = double.IsFinite(_factorEquilibradoAndarBus)
|
||||||
? Math.Max(0, _factorEquilibradoAndarBus)
|
? Math.Max(0, _factorEquilibradoAndarBus)
|
||||||
: 12.0;
|
: FactorEquilibradoAndarBusPorDefecto;
|
||||||
var distanciaEquilibrada = Math.Max(0, distanciaAndando) * factor;
|
var distanciaEquilibrada = Math.Max(0, distanciaAndando) * factor;
|
||||||
|
|
||||||
return double.IsFinite(distanciaEquilibrada)
|
return double.IsFinite(distanciaEquilibrada)
|
||||||
@@ -1431,7 +1441,10 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
|
|
||||||
var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
|
var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
|
||||||
var distanciaEquilibradaAndando = CalcularDistanciaEquilibradaAndandoAlternativa(alternativa);
|
var distanciaEquilibradaAndando = CalcularDistanciaEquilibradaAndandoAlternativa(alternativa);
|
||||||
var score = distanciaBus + distanciaEquilibradaAndando;
|
var penalizacionTransbordos = UtilidadesTransitoDbus.CalcularPenalizacionTransbordosRankingMetros(
|
||||||
|
alternativa.NumeroTransbordosReales,
|
||||||
|
CrearParametrosPenalizacionActuales());
|
||||||
|
var score = distanciaBus + distanciaEquilibradaAndando + penalizacionTransbordos;
|
||||||
return double.IsFinite(score) && score >= 0
|
return double.IsFinite(score) && score >= 0
|
||||||
? score
|
? score
|
||||||
: double.PositiveInfinity;
|
: double.PositiveInfinity;
|
||||||
@@ -1443,7 +1456,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
if (!double.IsFinite(score))
|
if (!double.IsFinite(score))
|
||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
return $"Score {FormatearDistanciaTarjeta(score)}";
|
return FormatearDistanciaTarjeta(score);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync()
|
private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync()
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ public partial class PlanificadorRutas
|
|||||||
private const string ClaveRespaldoHistorialLocal = "rutasdbus.planificador.routeHistory.recovery.v1";
|
private const string ClaveRespaldoHistorialLocal = "rutasdbus.planificador.routeHistory.recovery.v1";
|
||||||
private const int MaxItemsHistorialLocal = 100;
|
private const int MaxItemsHistorialLocal = 100;
|
||||||
private const int IntervaloRefrescoHistorialCompartidoSegundos = 3;
|
private const int IntervaloRefrescoHistorialCompartidoSegundos = 3;
|
||||||
|
private const int TiempoMaxOperacionHistorialLocalSegundos = 8;
|
||||||
private static readonly JsonSerializerOptions OpcionesLecturaHistorialLocal = new()
|
private static readonly JsonSerializerOptions OpcionesLecturaHistorialLocal = new()
|
||||||
{
|
{
|
||||||
PropertyNameCaseInsensitive = true,
|
PropertyNameCaseInsensitive = true,
|
||||||
@@ -36,6 +37,27 @@ public partial class PlanificadorRutas
|
|||||||
private bool _historialLocalCargado = false;
|
private bool _historialLocalCargado = false;
|
||||||
private bool _historialLocalCargando = false;
|
private bool _historialLocalCargando = false;
|
||||||
private int _versionAperturaHistorial;
|
private int _versionAperturaHistorial;
|
||||||
|
private int _numeroIntentoCargaHistorialLocal;
|
||||||
|
private string _resumenErrorHistorialLocal = string.Empty;
|
||||||
|
private string _detalleErrorHistorialLocal = string.Empty;
|
||||||
|
|
||||||
|
private sealed class ResultadoStorageNavegador
|
||||||
|
{
|
||||||
|
[JsonPropertyName("ok")]
|
||||||
|
public bool Ok { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("found")]
|
||||||
|
public bool Found { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("value")]
|
||||||
|
public string? Value { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("errorName")]
|
||||||
|
public string? ErrorName { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("errorMessage")]
|
||||||
|
public string? ErrorMessage { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
private async Task ToggleHistorialPanel()
|
private async Task ToggleHistorialPanel()
|
||||||
{
|
{
|
||||||
@@ -80,8 +102,9 @@ public partial class PlanificadorRutas
|
|||||||
{
|
{
|
||||||
return await tareaCarga;
|
return await tareaCarga;
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
RegistrarErrorHistorialLocal("coordinacion de la carga", ex);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -93,62 +116,159 @@ public partial class PlanificadorRutas
|
|||||||
|
|
||||||
private async Task<bool> CargarHistorialLocalDesdeNavegadorAsync()
|
private async Task<bool> CargarHistorialLocalDesdeNavegadorAsync()
|
||||||
{
|
{
|
||||||
string? json;
|
var numeroIntento = ++_numeroIntentoCargaHistorialLocal;
|
||||||
|
ResultadoStorageNavegador? resultadoLectura;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
json = await JS.InvokeAsync<string?>("rt.loadLocalValue", ClaveHistorialLocal);
|
using var cts = new CancellationTokenSource(
|
||||||
|
TimeSpan.FromSeconds(TiempoMaxOperacionHistorialLocalSegundos));
|
||||||
|
resultadoLectura = await JS.InvokeAsync<ResultadoStorageNavegador?>(
|
||||||
|
"rt.loadLocalValueResult",
|
||||||
|
cts.Token,
|
||||||
|
ClaveHistorialLocal);
|
||||||
}
|
}
|
||||||
catch (JSException)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"lectura de localStorage",
|
||||||
|
new TimeoutException(
|
||||||
|
$"La lectura no respondio en {TiempoMaxOperacionHistorialLocalSegundos} segundos.",
|
||||||
|
ex),
|
||||||
|
$"Intento de carga: {numeroIntento}.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"lectura de localStorage",
|
||||||
|
ex,
|
||||||
|
$"Intento de carga: {numeroIntento}.");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(json))
|
if (resultadoLectura is null)
|
||||||
|
{
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"lectura de localStorage",
|
||||||
|
"RespuestaVacia",
|
||||||
|
"JavaScript no devolvio ningun resultado de lectura.",
|
||||||
|
$"Intento de carga: {numeroIntento}.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!resultadoLectura.Ok)
|
||||||
|
{
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"lectura de localStorage",
|
||||||
|
resultadoLectura.ErrorName ?? "StorageError",
|
||||||
|
resultadoLectura.ErrorMessage ?? "El navegador no ha indicado el motivo del error.",
|
||||||
|
$"Intento de carga: {numeroIntento}.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var json = resultadoLectura.Value;
|
||||||
|
if (!resultadoLectura.Found || string.IsNullOrWhiteSpace(json))
|
||||||
{
|
{
|
||||||
FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
|
FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
|
||||||
|
LimpiarErrorHistorialLocal();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var items = DeserializarHistorialLocal(json, out var registrosOmitidos);
|
var items = DeserializarHistorialLocal(
|
||||||
|
json,
|
||||||
|
out var registrosOmitidos,
|
||||||
|
out var primerErrorRegistro);
|
||||||
FinalizarCargaHistorialLocal(items);
|
FinalizarCargaHistorialLocal(items);
|
||||||
|
|
||||||
if (registrosOmitidos > 0)
|
if (registrosOmitidos > 0)
|
||||||
{
|
{
|
||||||
await GuardarHistorialLocalRecuperadoAsync(json);
|
var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync(json);
|
||||||
|
var detalleFalloRecuperacion = recuperacionGuardada
|
||||||
|
? null
|
||||||
|
: _detalleErrorHistorialLocal;
|
||||||
|
if (!recuperacionGuardada)
|
||||||
|
_historialLocalCargado = false;
|
||||||
|
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"validacion de registros",
|
||||||
|
"RegistrosDaniados",
|
||||||
|
$"Se omitieron {registrosOmitidos} registros que no se podian utilizar.",
|
||||||
|
$"Primer fallo: {primerErrorRegistro ?? "sin detalle"}. " +
|
||||||
|
$"Registros recuperados: {_historialLocal.Count}. " +
|
||||||
|
$"Tamano leido: {json.Length} caracteres. " +
|
||||||
|
$"Copia de recuperacion: {(recuperacionGuardada ? "guardada" : "no guardada")}." +
|
||||||
|
(string.IsNullOrWhiteSpace(detalleFalloRecuperacion)
|
||||||
|
? string.Empty
|
||||||
|
: $"{Environment.NewLine}Fallo al recuperar: {detalleFalloRecuperacion}"));
|
||||||
MostrarMensajeHistorial(
|
MostrarMensajeHistorial(
|
||||||
$"Se han recuperado {_historialLocal.Count} registros del historial; {registrosOmitidos} estaban dañados.",
|
$"Se han recuperado {_historialLocal.Count} registros del historial; {registrosOmitidos} estaban dañados.",
|
||||||
esError: true);
|
esError: true);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
return true;
|
{
|
||||||
|
LimpiarErrorHistorialLocal();
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException)
|
|
||||||
|
return _historialLocalCargado;
|
||||||
|
}
|
||||||
|
catch (Exception ex) when (
|
||||||
|
ex is JsonException or
|
||||||
|
NotSupportedException or
|
||||||
|
InvalidOperationException or
|
||||||
|
ArgumentException)
|
||||||
{
|
{
|
||||||
FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
|
FinalizarCargaHistorialLocal(Array.Empty<RegistroHistorialRuta>());
|
||||||
await GuardarHistorialLocalRecuperadoAsync(json);
|
var recuperacionGuardada = await GuardarHistorialLocalRecuperadoAsync(json);
|
||||||
|
var detalleFalloRecuperacion = recuperacionGuardada
|
||||||
|
? null
|
||||||
|
: _detalleErrorHistorialLocal;
|
||||||
|
if (!recuperacionGuardada)
|
||||||
|
_historialLocalCargado = false;
|
||||||
|
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"deserializacion del historial",
|
||||||
|
ex,
|
||||||
|
$"Tamano leido: {json.Length} caracteres. " +
|
||||||
|
$"Copia de recuperacion: {(recuperacionGuardada ? "guardada" : "no guardada")}." +
|
||||||
|
(string.IsNullOrWhiteSpace(detalleFalloRecuperacion)
|
||||||
|
? string.Empty
|
||||||
|
: $"{Environment.NewLine}Fallo al recuperar: {detalleFalloRecuperacion}"));
|
||||||
MostrarMensajeHistorial(
|
MostrarMensajeHistorial(
|
||||||
"El historial personal guardado estaba dañado. Se ha conservado una copia y se ha iniciado uno nuevo.",
|
recuperacionGuardada
|
||||||
|
? "El historial personal guardado estaba dañado. Se ha conservado una copia y se ha iniciado uno nuevo."
|
||||||
|
: "El historial personal esta dañado, pero no se ha modificado porque no se pudo guardar la copia. Reintenta la carga.",
|
||||||
esError: true);
|
esError: true);
|
||||||
return true;
|
return _historialLocalCargado;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_historialLocalCargado = false;
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"fallo inesperado al procesar el historial",
|
||||||
|
ex,
|
||||||
|
$"Tamano leido: {json.Length} caracteres. El contenido original no se ha modificado.");
|
||||||
|
MostrarMensajeHistorial(
|
||||||
|
"El codigo ha producido un error inesperado al cargar el historial. Consulta el diagnostico; los datos originales no se han modificado.",
|
||||||
|
esError: true);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IReadOnlyList<RegistroHistorialRuta> DeserializarHistorialLocal(
|
private static IReadOnlyList<RegistroHistorialRuta> DeserializarHistorialLocal(
|
||||||
string json,
|
string json,
|
||||||
out int registrosOmitidos)
|
out int registrosOmitidos,
|
||||||
|
out string? primerErrorRegistro)
|
||||||
{
|
{
|
||||||
using var documento = JsonDocument.Parse(json);
|
using var documento = JsonDocument.Parse(json);
|
||||||
if (documento.RootElement.ValueKind != JsonValueKind.Array)
|
if (documento.RootElement.ValueKind != JsonValueKind.Array)
|
||||||
throw new JsonException("El historial local no contiene una lista.");
|
throw new JsonException("El historial local no contiene una lista.");
|
||||||
|
|
||||||
registrosOmitidos = 0;
|
registrosOmitidos = 0;
|
||||||
|
primerErrorRegistro = null;
|
||||||
var recuperados = new List<RegistroHistorialRuta>();
|
var recuperados = new List<RegistroHistorialRuta>();
|
||||||
|
var idsVistos = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var elemento in documento.RootElement.EnumerateArray())
|
foreach (var elemento in documento.RootElement.EnumerateArray())
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -157,18 +277,27 @@ public partial class PlanificadorRutas
|
|||||||
if (item is null)
|
if (item is null)
|
||||||
{
|
{
|
||||||
registrosOmitidos++;
|
registrosOmitidos++;
|
||||||
|
primerErrorRegistro ??= "El registro deserializado era nulo.";
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(item.Id))
|
if (string.IsNullOrWhiteSpace(item.Id) || !idsVistos.Add(item.Id))
|
||||||
|
{
|
||||||
item.Id = Guid.NewGuid().ToString("N");
|
item.Id = Guid.NewGuid().ToString("N");
|
||||||
|
idsVistos.Add(item.Id);
|
||||||
|
}
|
||||||
|
|
||||||
item.LineasPermitidas ??= new List<int>();
|
NormalizarRegistroHistorialLocal(item);
|
||||||
recuperados.Add(item);
|
recuperados.Add(item);
|
||||||
}
|
}
|
||||||
catch (Exception ex) when (ex is JsonException or NotSupportedException or InvalidOperationException)
|
catch (Exception ex) when (
|
||||||
|
ex is JsonException or
|
||||||
|
NotSupportedException or
|
||||||
|
InvalidOperationException or
|
||||||
|
ArgumentException)
|
||||||
{
|
{
|
||||||
registrosOmitidos++;
|
registrosOmitidos++;
|
||||||
|
primerErrorRegistro ??= $"{ex.GetType().Name}: {ex.Message}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,6 +307,33 @@ public partial class PlanificadorRutas
|
|||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void NormalizarRegistroHistorialLocal(RegistroHistorialRuta item)
|
||||||
|
{
|
||||||
|
if (!CoordenadaHistorialValida(item.OrigenLatitud, item.OrigenLongitud))
|
||||||
|
throw new JsonException("Las coordenadas de origen no son validas.");
|
||||||
|
if (!CoordenadaHistorialValida(item.DestinoLatitud, item.DestinoLongitud))
|
||||||
|
throw new JsonException("Las coordenadas de destino no son validas.");
|
||||||
|
|
||||||
|
item.OrigenTexto = string.IsNullOrWhiteSpace(item.OrigenTexto)
|
||||||
|
? $"{item.OrigenLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.OrigenLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}"
|
||||||
|
: item.OrigenTexto.Trim();
|
||||||
|
item.DestinoTexto = string.IsNullOrWhiteSpace(item.DestinoTexto)
|
||||||
|
? $"{item.DestinoLatitud.ToString("0.000000", CultureInfo.InvariantCulture)}, {item.DestinoLongitud.ToString("0.000000", CultureInfo.InvariantCulture)}"
|
||||||
|
: item.DestinoTexto.Trim();
|
||||||
|
item.LineasPermitidas = NormalizarLineasHistorial(item.LineasPermitidas).ToList();
|
||||||
|
|
||||||
|
if (item.FechaCreacionUtc == default)
|
||||||
|
item.FechaCreacionUtc = item.FechaActualizacionUtc == default ? DateTime.UtcNow : item.FechaActualizacionUtc;
|
||||||
|
if (item.FechaActualizacionUtc == default)
|
||||||
|
item.FechaActualizacionUtc = item.FechaCreacionUtc;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool CoordenadaHistorialValida(double latitud, double longitud)
|
||||||
|
=> double.IsFinite(latitud) &&
|
||||||
|
double.IsFinite(longitud) &&
|
||||||
|
latitud is >= -90 and <= 90 &&
|
||||||
|
longitud is >= -180 and <= 180;
|
||||||
|
|
||||||
private void FinalizarCargaHistorialLocal(IEnumerable<RegistroHistorialRuta> items)
|
private void FinalizarCargaHistorialLocal(IEnumerable<RegistroHistorialRuta> items)
|
||||||
{
|
{
|
||||||
_historialLocal.Clear();
|
_historialLocal.Clear();
|
||||||
@@ -187,17 +343,19 @@ public partial class PlanificadorRutas
|
|||||||
_historialLocalCargado = true;
|
_historialLocalCargado = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task GuardarHistorialLocalRecuperadoAsync(string jsonOriginal)
|
private async Task<bool> GuardarHistorialLocalRecuperadoAsync(string jsonOriginal)
|
||||||
{
|
{
|
||||||
try
|
if (!await GuardarValorHistorialLocalAsync(
|
||||||
{
|
ClaveRespaldoHistorialLocal,
|
||||||
await JS.InvokeVoidAsync("rt.saveLocalValue", ClaveRespaldoHistorialLocal, jsonOriginal);
|
jsonOriginal,
|
||||||
|
"guardado de la copia de recuperacion"))
|
||||||
|
return false;
|
||||||
|
|
||||||
var jsonRecuperado = JsonSerializer.Serialize(_historialLocal);
|
var jsonRecuperado = JsonSerializer.Serialize(_historialLocal);
|
||||||
await JS.InvokeVoidAsync("rt.saveLocalValue", ClaveHistorialLocal, jsonRecuperado);
|
return await GuardarValorHistorialLocalAsync(
|
||||||
}
|
ClaveHistorialLocal,
|
||||||
catch (JSException)
|
jsonRecuperado,
|
||||||
{
|
"guardado del historial recuperado");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task PersistirHistorialLocalAsync()
|
private async Task PersistirHistorialLocalAsync()
|
||||||
@@ -205,14 +363,143 @@ public partial class PlanificadorRutas
|
|||||||
if (!_historialLocalCargado)
|
if (!_historialLocalCargado)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
var json = JsonSerializer.Serialize(_historialLocal);
|
||||||
|
if (!await GuardarValorHistorialLocalAsync(
|
||||||
|
ClaveHistorialLocal,
|
||||||
|
json,
|
||||||
|
"persistencia del historial"))
|
||||||
|
{
|
||||||
|
MostrarMensajeHistorial(
|
||||||
|
"No se ha podido guardar el historial personal. Consulta el diagnostico.",
|
||||||
|
esError: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> GuardarValorHistorialLocalAsync(string clave, string valor, string fase)
|
||||||
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var json = JsonSerializer.Serialize(_historialLocal);
|
using var cts = new CancellationTokenSource(
|
||||||
await JS.InvokeVoidAsync("rt.saveLocalValue", ClaveHistorialLocal, json);
|
TimeSpan.FromSeconds(TiempoMaxOperacionHistorialLocalSegundos));
|
||||||
|
var resultado = await JS.InvokeAsync<ResultadoStorageNavegador?>(
|
||||||
|
"rt.saveLocalValueResult",
|
||||||
|
cts.Token,
|
||||||
|
clave,
|
||||||
|
valor);
|
||||||
|
|
||||||
|
if (resultado?.Ok == true)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
fase,
|
||||||
|
resultado?.ErrorName ?? "RespuestaVacia",
|
||||||
|
resultado?.ErrorMessage ?? "JavaScript no devolvio un resultado valido al guardar.",
|
||||||
|
$"Tamano que se intentaba guardar: {valor.Length} caracteres.");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
catch (JSException)
|
catch (OperationCanceledException ex)
|
||||||
{
|
{
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
fase,
|
||||||
|
new TimeoutException(
|
||||||
|
$"La escritura no respondio en {TiempoMaxOperacionHistorialLocalSegundos} segundos.",
|
||||||
|
ex),
|
||||||
|
$"Tamano que se intentaba guardar: {valor.Length} caracteres.");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
fase,
|
||||||
|
ex,
|
||||||
|
$"Tamano que se intentaba guardar: {valor.Length} caracteres.");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ReintentarCargaHistorialLocalAsync()
|
||||||
|
{
|
||||||
|
if (_historialLocalCargando)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_historialLocalCargado = false;
|
||||||
|
_tareaCargaHistorialLocal = null;
|
||||||
|
_historialLocalCargando = true;
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var cargado = await AsegurarHistorialLocalCargadoAsync();
|
||||||
|
MostrarMensajeHistorial(
|
||||||
|
cargado
|
||||||
|
? $"Historial personal cargado: {_historialLocal.Count} registro(s)."
|
||||||
|
: "El historial personal sigue sin poder cargarse. Consulta el diagnostico.",
|
||||||
|
esError: !cargado);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_historialLocalCargando = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegistrarErrorHistorialLocal(
|
||||||
|
string fase,
|
||||||
|
Exception excepcion,
|
||||||
|
string? contexto = null)
|
||||||
|
{
|
||||||
|
var causa = excepcion.GetBaseException();
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
fase,
|
||||||
|
causa.GetType().Name,
|
||||||
|
causa.Message,
|
||||||
|
CombinarDetalle(contexto, excepcion.ToString()));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RegistrarErrorHistorialLocal(
|
||||||
|
string fase,
|
||||||
|
string tipo,
|
||||||
|
string mensaje,
|
||||||
|
string? contexto = null)
|
||||||
|
{
|
||||||
|
var marcaTiempo = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss", CultureInfo.CurrentCulture);
|
||||||
|
_resumenErrorHistorialLocal = $"{tipo}: {LimitarTextoDiagnostico(mensaje, 240)}";
|
||||||
|
_detalleErrorHistorialLocal = string.Join(
|
||||||
|
Environment.NewLine,
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
$"Fecha: {marcaTiempo}",
|
||||||
|
$"Fase: {fase}",
|
||||||
|
$"Tipo: {tipo}",
|
||||||
|
$"Mensaje: {mensaje}",
|
||||||
|
$"Estado: cargado={_historialLocalCargado}, cargando={_historialLocalCargando}, registrosEnMemoria={_historialLocal.Count}",
|
||||||
|
string.IsNullOrWhiteSpace(contexto) ? null : $"Contexto: {contexto}"
|
||||||
|
}.Where(linea => linea is not null));
|
||||||
|
|
||||||
|
Console.Error.WriteLine($"[Historial personal] {_detalleErrorHistorialLocal}");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void LimpiarErrorHistorialLocal()
|
||||||
|
{
|
||||||
|
_resumenErrorHistorialLocal = string.Empty;
|
||||||
|
_detalleErrorHistorialLocal = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string? CombinarDetalle(string? contexto, string? detalle)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(contexto))
|
||||||
|
return detalle;
|
||||||
|
if (string.IsNullOrWhiteSpace(detalle))
|
||||||
|
return contexto;
|
||||||
|
return $"{contexto}{Environment.NewLine}{detalle}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string LimitarTextoDiagnostico(string texto, int maximo)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(texto) || texto.Length <= maximo)
|
||||||
|
return texto;
|
||||||
|
|
||||||
|
return texto[..maximo] + "...";
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CargarHistorialCompartidoAsync(bool mostrarError = true)
|
private async Task CargarHistorialCompartidoAsync(bool mostrarError = true)
|
||||||
@@ -338,6 +625,8 @@ public partial class PlanificadorRutas
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task CargarHistorialLocalAsync(string id)
|
private async Task CargarHistorialLocalAsync(string id)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var item = _historialLocal.FirstOrDefault(x => x.Id == id);
|
var item = _historialLocal.FirstOrDefault(x => x.Id == id);
|
||||||
if (item is null)
|
if (item is null)
|
||||||
@@ -350,6 +639,17 @@ public partial class PlanificadorRutas
|
|||||||
SeleccionarHistorialLocal(id);
|
SeleccionarHistorialLocal(id);
|
||||||
MostrarMensajeHistorial("Combinacion personal cargada.");
|
MostrarMensajeHistorial("Combinacion personal cargada.");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"aplicacion de un registro personal",
|
||||||
|
ex,
|
||||||
|
$"Id del registro: {id}.");
|
||||||
|
MostrarMensajeHistorial(
|
||||||
|
"La combinacion personal ha producido un error. Consulta el diagnostico.",
|
||||||
|
esError: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CargarHistorialCompartidoAsync(string id)
|
private async Task CargarHistorialCompartidoAsync(string id)
|
||||||
{
|
{
|
||||||
@@ -428,6 +728,8 @@ public partial class PlanificadorRutas
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async Task EliminarHistorialLocalAsync(string id)
|
private async Task EliminarHistorialLocalAsync(string id)
|
||||||
|
{
|
||||||
|
try
|
||||||
{
|
{
|
||||||
if (!await AsegurarHistorialLocalCargadoAsync())
|
if (!await AsegurarHistorialLocalCargadoAsync())
|
||||||
return;
|
return;
|
||||||
@@ -457,6 +759,17 @@ public partial class PlanificadorRutas
|
|||||||
|
|
||||||
MostrarMensajeHistorial("Combinacion eliminada del historial personal.");
|
MostrarMensajeHistorial("Combinacion eliminada del historial personal.");
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
RegistrarErrorHistorialLocal(
|
||||||
|
"eliminacion de un registro personal",
|
||||||
|
ex,
|
||||||
|
$"Id del registro: {id}.");
|
||||||
|
MostrarMensajeHistorial(
|
||||||
|
"No se ha podido completar la eliminacion. Consulta el diagnostico.",
|
||||||
|
esError: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void PedirConfirmacionEliminarHistorialCompartido(string id)
|
private void PedirConfirmacionEliminarHistorialCompartido(string id)
|
||||||
{
|
{
|
||||||
@@ -638,7 +951,7 @@ public partial class PlanificadorRutas
|
|||||||
PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta,
|
PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta,
|
||||||
FactorEquilibradoAndarBus = double.IsFinite(_factorEquilibradoAndarBus)
|
FactorEquilibradoAndarBus = double.IsFinite(_factorEquilibradoAndarBus)
|
||||||
? Math.Max(0, _factorEquilibradoAndarBus)
|
? Math.Max(0, _factorEquilibradoAndarBus)
|
||||||
: 12.0,
|
: FactorEquilibradoAndarBusPorDefecto,
|
||||||
ActivarFiltroLineas = _activarRangoLineas,
|
ActivarFiltroLineas = _activarRangoLineas,
|
||||||
LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(),
|
LineasPermitidas = ObtenerLineasPermitidasSeleccionadas().ToList(),
|
||||||
FechaCreacionUtc = DateTime.UtcNow,
|
FechaCreacionUtc = DateTime.UtcNow,
|
||||||
|
|||||||
@@ -1204,7 +1204,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private bool _elevPopupMinimizado = false;
|
private bool _elevPopupMinimizado = false;
|
||||||
|
|
||||||
|
|
||||||
private const string _versionApp = "(v-20260728a)";
|
private const string _versionApp = "(v-20260731a)";
|
||||||
|
|
||||||
|
|
||||||
private double? _paradaElevG;
|
private double? _paradaElevG;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ namespace RutasDBUS.Components.Pages;
|
|||||||
public partial class PlanificadorRutas
|
public partial class PlanificadorRutas
|
||||||
{
|
{
|
||||||
private const string ClaveConfiguracionLocal = "rutasdbus.planificador.config.v1";
|
private const string ClaveConfiguracionLocal = "rutasdbus.planificador.config.v1";
|
||||||
|
private const int VersionPersistenciaFactorEquilibrado = 2;
|
||||||
private bool _configuracionLocalCargada = false;
|
private bool _configuracionLocalCargada = false;
|
||||||
private bool _mostrarConfirmacionRestablecer = false;
|
private bool _mostrarConfirmacionRestablecer = false;
|
||||||
|
|
||||||
@@ -36,8 +37,12 @@ public partial class PlanificadorRutas
|
|||||||
public double? CoeficienteEsfuerzoAltura { get; set; }
|
public double? CoeficienteEsfuerzoAltura { get; set; }
|
||||||
public double? PesoTransbordoRankingMinutos { get; set; }
|
public double? PesoTransbordoRankingMinutos { get; set; }
|
||||||
public double? FactorEquilibradoAndarBus { get; set; }
|
public double? FactorEquilibradoAndarBus { get; set; }
|
||||||
|
public int? VersionFactorEquilibrado { get; set; }
|
||||||
|
public bool? MostrarElevacionesRuta { get; set; }
|
||||||
public double? UmbralPendienteSubidaRuta { get; set; }
|
public double? UmbralPendienteSubidaRuta { get; set; }
|
||||||
public double? UmbralPendienteBajadaRuta { get; set; }
|
public double? UmbralPendienteBajadaRuta { get; set; }
|
||||||
|
public double? UmbralAlturaAlertaPendienteRutaMetros { get; set; }
|
||||||
|
// Compatibilidad con configuraciones guardadas cuando el umbral se expresaba en pisos.
|
||||||
public double? UmbralPisosPendienteRuta { get; set; }
|
public double? UmbralPisosPendienteRuta { get; set; }
|
||||||
public double VelocidadPieKmH { get; set; }
|
public double VelocidadPieKmH { get; set; }
|
||||||
public double VelocidadBusKmH { get; set; }
|
public double VelocidadBusKmH { get; set; }
|
||||||
@@ -139,9 +144,11 @@ public partial class PlanificadorRutas
|
|||||||
CoeficienteEsfuerzoAltura = _coeficienteEsfuerzoAltura,
|
CoeficienteEsfuerzoAltura = _coeficienteEsfuerzoAltura,
|
||||||
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
||||||
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
||||||
|
VersionFactorEquilibrado = VersionPersistenciaFactorEquilibrado,
|
||||||
|
MostrarElevacionesRuta = _mostrarElevacionesRuta,
|
||||||
UmbralPendienteSubidaRuta = _umbralPendienteSubidaRuta,
|
UmbralPendienteSubidaRuta = _umbralPendienteSubidaRuta,
|
||||||
UmbralPendienteBajadaRuta = _umbralPendienteBajadaRuta,
|
UmbralPendienteBajadaRuta = _umbralPendienteBajadaRuta,
|
||||||
UmbralPisosPendienteRuta = _umbralPisosPendienteRuta,
|
UmbralAlturaAlertaPendienteRutaMetros = _umbralAlturaAlertaPendienteRutaMetros,
|
||||||
VelocidadPieKmH = _velocidadPieKmH,
|
VelocidadPieKmH = _velocidadPieKmH,
|
||||||
VelocidadBusKmH = _velocidadBusKmH,
|
VelocidadBusKmH = _velocidadBusKmH,
|
||||||
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
|
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
|
||||||
@@ -206,7 +213,11 @@ public partial class PlanificadorRutas
|
|||||||
if (config.PesoTransbordoRankingMinutos is { } pesoTransbordo && double.IsFinite(pesoTransbordo))
|
if (config.PesoTransbordoRankingMinutos is { } pesoTransbordo && double.IsFinite(pesoTransbordo))
|
||||||
_pesoTransbordoRankingMinutos = Math.Abs(pesoTransbordo - 8.0) < 0.0001 ? 4.0 : Math.Max(0, pesoTransbordo);
|
_pesoTransbordoRankingMinutos = Math.Abs(pesoTransbordo - 8.0) < 0.0001 ? 4.0 : Math.Max(0, pesoTransbordo);
|
||||||
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
|
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
|
||||||
_factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
|
_factorEquilibradoAndarBus = (config.VersionFactorEquilibrado ?? 0) < VersionPersistenciaFactorEquilibrado &&
|
||||||
|
Math.Abs(factorEquilibrado - 12.0) < 0.0001
|
||||||
|
? FactorEquilibradoAndarBusPorDefecto
|
||||||
|
: Math.Max(0, factorEquilibrado);
|
||||||
|
_mostrarElevacionesRuta = config.MostrarElevacionesRuta ?? false;
|
||||||
if (config.UmbralPendienteSubidaRuta is { } umbralSubida && double.IsFinite(umbralSubida))
|
if (config.UmbralPendienteSubidaRuta is { } umbralSubida && double.IsFinite(umbralSubida))
|
||||||
_umbralPendienteSubidaRuta = Math.Abs(umbralSubida - 3.0) < 0.0001
|
_umbralPendienteSubidaRuta = Math.Abs(umbralSubida - 3.0) < 0.0001
|
||||||
? UmbralPendienteSubidaRutaPorDefecto
|
? UmbralPendienteSubidaRutaPorDefecto
|
||||||
@@ -217,8 +228,16 @@ public partial class PlanificadorRutas
|
|||||||
Math.Abs(umbralBajada - 6.0) < 0.0001
|
Math.Abs(umbralBajada - 6.0) < 0.0001
|
||||||
? UmbralPendienteBajadaRutaPorDefecto
|
? UmbralPendienteBajadaRutaPorDefecto
|
||||||
: NormalizarUmbralPendienteRuta(umbralBajada);
|
: NormalizarUmbralPendienteRuta(umbralBajada);
|
||||||
if (config.UmbralPisosPendienteRuta is { } umbralPisos && double.IsFinite(umbralPisos))
|
if (config.UmbralAlturaAlertaPendienteRutaMetros is { } umbralAltura && double.IsFinite(umbralAltura))
|
||||||
_umbralPisosPendienteRuta = NormalizarUmbralPisosPendienteRuta(umbralPisos);
|
{
|
||||||
|
_umbralAlturaAlertaPendienteRutaMetros =
|
||||||
|
NormalizarUmbralAlturaAlertaPendienteRutaMetros(umbralAltura);
|
||||||
|
}
|
||||||
|
else if (config.UmbralPisosPendienteRuta is { } umbralPisos && double.IsFinite(umbralPisos))
|
||||||
|
{
|
||||||
|
_umbralAlturaAlertaPendienteRutaMetros =
|
||||||
|
NormalizarUmbralAlturaAlertaPendienteRutaMetros(umbralPisos * MetrosPorPiso);
|
||||||
|
}
|
||||||
_velocidadPieKmH = Math.Clamp(config.VelocidadPieKmH, 0.5, 25.0);
|
_velocidadPieKmH = Math.Clamp(config.VelocidadPieKmH, 0.5, 25.0);
|
||||||
_velocidadBusKmH = Math.Clamp(
|
_velocidadBusKmH = Math.Clamp(
|
||||||
Math.Abs(config.VelocidadBusKmH - 15.0) < 0.0001 ||
|
Math.Abs(config.VelocidadBusKmH - 15.0) < 0.0001 ||
|
||||||
@@ -321,10 +340,11 @@ public partial class PlanificadorRutas
|
|||||||
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
_pesoTransbordoRankingMinutos = 4.0;
|
_pesoTransbordoRankingMinutos = 4.0;
|
||||||
_factorEquilibradoAndarBus = 12.0;
|
_factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||||
|
_mostrarElevacionesRuta = false;
|
||||||
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||||
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||||
_umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
_umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||||
_velocidadPieKmH = 3.6;
|
_velocidadPieKmH = 3.6;
|
||||||
_velocidadBusKmH = 16.0;
|
_velocidadBusKmH = 16.0;
|
||||||
_minutosTransbordo = 5;
|
_minutosTransbordo = 5;
|
||||||
@@ -371,10 +391,11 @@ public partial class PlanificadorRutas
|
|||||||
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
|
||||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
_pesoTransbordoRankingMinutos = 4.0;
|
_pesoTransbordoRankingMinutos = 4.0;
|
||||||
_factorEquilibradoAndarBus = 12.0;
|
_factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||||
|
_mostrarElevacionesRuta = false;
|
||||||
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||||
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||||
_umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
_umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||||
_velocidadPieKmH = 3.6;
|
_velocidadPieKmH = 3.6;
|
||||||
_velocidadBusKmH = 16.0;
|
_velocidadBusKmH = 16.0;
|
||||||
_minutosTransbordo = 5;
|
_minutosTransbordo = 5;
|
||||||
|
|||||||
@@ -384,6 +384,12 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
IReadOnlyList<AlternativaRuta> alternativas,
|
IReadOnlyList<AlternativaRuta> alternativas,
|
||||||
CancellationToken ct)
|
CancellationToken ct)
|
||||||
{
|
{
|
||||||
|
foreach (var alternativa in alternativas)
|
||||||
|
alternativa.EsfuerzoExtraPendienteMetros = 0;
|
||||||
|
|
||||||
|
if (!_mostrarElevacionesRuta || alternativas.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
var lineasPorAlternativa = alternativas
|
var lineasPorAlternativa = alternativas
|
||||||
.Select(alternativa => new
|
.Select(alternativa => new
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -81,16 +81,40 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
|
|
||||||
private async Task ToggleElevacionesRutaAsync(ChangeEventArgs e)
|
private async Task ToggleElevacionesRutaAsync(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_mostrarElevacionesRuta = e.Value switch
|
var mostrarElevaciones = e.Value switch
|
||||||
{
|
{
|
||||||
bool valor => valor,
|
bool valor => valor,
|
||||||
string texto when bool.TryParse(texto, out var valor) => valor,
|
string texto when bool.TryParse(texto, out var valor) => valor,
|
||||||
_ => !_mostrarElevacionesRuta
|
_ => !_mostrarElevacionesRuta
|
||||||
};
|
};
|
||||||
|
|
||||||
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
if (_mostrarElevacionesRuta == mostrarElevaciones)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_mostrarElevacionesRuta = mostrarElevaciones;
|
||||||
|
await PersistirConfiguracionAsync();
|
||||||
|
if (_opcionesRuta.Count == 0)
|
||||||
|
{
|
||||||
|
StateHasChanged();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pintandoElevacionesRuta = true;
|
||||||
|
_mensajeRuta = _mostrarElevacionesRuta
|
||||||
? "Aplicando pendientes..."
|
? "Aplicando pendientes..."
|
||||||
: "Representando ruta seleccionada...");
|
: "Recalculando sin pendientes...";
|
||||||
|
StateHasChanged();
|
||||||
|
await Task.Yield();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync(forzarRepintado: true);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_pintandoElevacionesRuta = false;
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CambiarUmbralElevacionRutaAsync(ChangeEventArgs e, bool esSubida)
|
private async Task CambiarUmbralElevacionRutaAsync(ChangeEventArgs e, bool esSubida)
|
||||||
@@ -107,7 +131,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
|
|
||||||
await PersistirConfiguracionAsync();
|
await PersistirConfiguracionAsync();
|
||||||
|
|
||||||
if (esSubida)
|
if (esSubida && _mostrarElevacionesRuta)
|
||||||
{
|
{
|
||||||
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
||||||
}
|
}
|
||||||
@@ -117,7 +141,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task RecalcularRankingTrasCambioEsfuerzoPendienteAsync()
|
private async Task RecalcularRankingTrasCambioEsfuerzoPendienteAsync(bool forzarRepintado = false)
|
||||||
{
|
{
|
||||||
if (_opcionesRuta.Count == 0)
|
if (_opcionesRuta.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -143,7 +167,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
!ReferenceEquals(opcionSeleccionadaAnterior, _opcionesRuta[nuevoIndice]);
|
!ReferenceEquals(opcionSeleccionadaAnterior, _opcionesRuta[nuevoIndice]);
|
||||||
_indiceAlternativaSeleccionada = nuevoIndice;
|
_indiceAlternativaSeleccionada = nuevoIndice;
|
||||||
|
|
||||||
if (_mostrarElevacionesRuta || cambioSeleccion)
|
if (forzarRepintado || _mostrarElevacionesRuta || cambioSeleccion)
|
||||||
{
|
{
|
||||||
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
||||||
? "Aplicando pendientes..."
|
? "Aplicando pendientes..."
|
||||||
@@ -155,11 +179,11 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task CambiarUmbralPisosPendienteRutaAsync(ChangeEventArgs e)
|
private async Task CambiarUmbralAlturaAlertaPendienteRutaAsync(ChangeEventArgs e)
|
||||||
{
|
{
|
||||||
_umbralPisosPendienteRuta = ParseUmbralPisosPendienteRuta(
|
_umbralAlturaAlertaPendienteRutaMetros = ParseUmbralAlturaAlertaPendienteRutaMetros(
|
||||||
e.Value,
|
e.Value,
|
||||||
_umbralPisosPendienteRuta);
|
_umbralAlturaAlertaPendienteRutaMetros);
|
||||||
|
|
||||||
await PersistirConfiguracionAsync();
|
await PersistirConfiguracionAsync();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -210,22 +234,22 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
? Math.Clamp(Math.Abs(valor), 0.0, 100.0)
|
? Math.Clamp(Math.Abs(valor), 0.0, 100.0)
|
||||||
: UmbralPendienteSubidaRutaPorDefecto;
|
: UmbralPendienteSubidaRutaPorDefecto;
|
||||||
|
|
||||||
private static double ParseUmbralPisosPendienteRuta(object? valor, double actual)
|
private static double ParseUmbralAlturaAlertaPendienteRutaMetros(object? valor, double actual)
|
||||||
{
|
{
|
||||||
var texto = Convert.ToString(valor, CultureInfo.InvariantCulture)?.Replace(',', '.');
|
var texto = Convert.ToString(valor, CultureInfo.InvariantCulture)?.Replace(',', '.');
|
||||||
if (!double.TryParse(texto, NumberStyles.Float, CultureInfo.InvariantCulture, out var nuevo) ||
|
if (!double.TryParse(texto, NumberStyles.Float, CultureInfo.InvariantCulture, out var nuevo) ||
|
||||||
!double.IsFinite(nuevo))
|
!double.IsFinite(nuevo))
|
||||||
{
|
{
|
||||||
return NormalizarUmbralPisosPendienteRuta(actual);
|
return NormalizarUmbralAlturaAlertaPendienteRutaMetros(actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
return NormalizarUmbralPisosPendienteRuta(nuevo);
|
return NormalizarUmbralAlturaAlertaPendienteRutaMetros(nuevo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double NormalizarUmbralPisosPendienteRuta(double valor)
|
private static double NormalizarUmbralAlturaAlertaPendienteRutaMetros(double valor)
|
||||||
=> double.IsFinite(valor)
|
=> double.IsFinite(valor)
|
||||||
? Math.Clamp(Math.Abs(valor), 0.0, 100.0)
|
? Math.Clamp(Math.Abs(valor), 0.0, 1000.0)
|
||||||
: UmbralPisosPendienteRutaPorDefecto;
|
: UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||||
|
|
||||||
private async Task DrawWalkingRouteJsAsync(
|
private async Task DrawWalkingRouteJsAsync(
|
||||||
string id,
|
string id,
|
||||||
|
|||||||
@@ -613,88 +613,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
|
||||||
Visualizar Pendientes
|
|
||||||
</span>
|
|
||||||
</label>
|
|
||||||
|
|
||||||
<div class="route-elevation-settings">
|
|
||||||
<button type="button"
|
|
||||||
class="route-elevation-settings-trigger @(_mostrarConfiguracionPendientesRuta ? "route-elevation-settings-trigger--active" : "")"
|
|
||||||
title="Configurar pendientes"
|
|
||||||
aria-label="Configurar pendientes"
|
|
||||||
aria-haspopup="dialog"
|
|
||||||
aria-expanded="@_mostrarConfiguracionPendientesRuta"
|
|
||||||
disabled="@_pintandoElevacionesRuta"
|
|
||||||
@onclick="() => _mostrarConfiguracionPendientesRuta = !_mostrarConfiguracionPendientesRuta">
|
|
||||||
<span aria-hidden="true">⚙</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
@if (_mostrarConfiguracionPendientesRuta)
|
|
||||||
{
|
|
||||||
<div class="route-elevation-settings-popover"
|
|
||||||
role="dialog"
|
|
||||||
aria-label="Umbrales de pendientes"
|
|
||||||
@onclick:stopPropagation="true">
|
|
||||||
<div class="route-elevation-settings-header">
|
|
||||||
<span>Configurar Pendientes</span>
|
|
||||||
<button type="button"
|
|
||||||
class="route-elevation-settings-close"
|
|
||||||
title="Cerrar"
|
|
||||||
aria-label="Cerrar configuración de pendientes"
|
|
||||||
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
|
|
||||||
<span aria-hidden="true">×</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="route-elevation-settings-fields">
|
|
||||||
<label class="route-elevation-setting-row">
|
|
||||||
<span>Subida</span>
|
|
||||||
<input type="text"
|
|
||||||
inputmode="decimal"
|
|
||||||
aria-label="Porcentaje de subida"
|
|
||||||
value="@_umbralPendienteSubidaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
|
||||||
disabled="@_pintandoElevacionesRuta"
|
|
||||||
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, true))" />
|
|
||||||
<span>%</span>
|
|
||||||
</label>
|
|
||||||
<label class="route-elevation-setting-row">
|
|
||||||
<span>Bajada</span>
|
|
||||||
<input type="text"
|
|
||||||
inputmode="decimal"
|
|
||||||
aria-label="Porcentaje de bajada"
|
|
||||||
value="@_umbralPendienteBajadaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
|
||||||
disabled="@_pintandoElevacionesRuta"
|
|
||||||
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, false))" />
|
|
||||||
<span>%</span>
|
|
||||||
</label>
|
|
||||||
<label class="route-elevation-setting-row"
|
|
||||||
title="Pisos acumulados para mostrar el aviso de pendientes positivas">
|
|
||||||
<span>Aviso</span>
|
|
||||||
<input type="text"
|
|
||||||
inputmode="decimal"
|
|
||||||
aria-label="Pisos para mostrar el aviso"
|
|
||||||
value="@_umbralPisosPendienteRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
|
||||||
disabled="@_pintandoElevacionesRuta"
|
|
||||||
@onchange="CambiarUmbralPisosPendienteRutaAsync" />
|
|
||||||
<span>pisos</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@if (_opcionesRuta.Count > 0)
|
@if (_opcionesRuta.Count > 0)
|
||||||
{
|
{
|
||||||
<div class="route-alt-list">
|
<div class="route-alt-list">
|
||||||
@@ -763,16 +681,13 @@
|
|||||||
{
|
{
|
||||||
var alt = _opcionesRuta[_indiceAlternativaSeleccionada];
|
var alt = _opcionesRuta[_indiceAlternativaSeleccionada];
|
||||||
|
|
||||||
@if (MostrarAvisoPendientesPositivasSignificativas)
|
@if (MostrarAlertaPendientesPositivasSignificativas)
|
||||||
{
|
{
|
||||||
<div class="route-slope-warning" role="status">
|
<div class="route-slope-warning" role="status">
|
||||||
<div>Pendientes positivas significativas.</div>
|
<div>Pendientes positivas significativas.</div>
|
||||||
<div style="margin-top:.2rem; padding-left:.55rem;">
|
<div style="margin-top:.2rem; padding-left:.55rem;">
|
||||||
<div>- Distancia con subidas: @FormatearDistanciaResumenElevacion(_distanciaPendientePositivaSignificativaRutaMetros)</div>
|
<div>- Distancia con subidas: @FormatearDistanciaResumenElevacion(_distanciaPendientePositivaSignificativaRutaMetros)</div>
|
||||||
<div>
|
<div>- Subidas acumuladas: @_alturaPendientePositivaSignificativaRutaMetros.ToString("0.00", CultureInfo.InvariantCulture) m</div>
|
||||||
- Subidas acumuladas: @_alturaPendientePositivaSignificativaRutaMetros.ToString("0.00", CultureInfo.InvariantCulture) m
|
|
||||||
(@((_alturaPendientePositivaSignificativaRutaMetros / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture)) pisos)
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -975,7 +890,7 @@
|
|||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(scoreEquilibradoSeleccionado))
|
@if (!string.IsNullOrWhiteSpace(scoreEquilibradoSeleccionado))
|
||||||
{
|
{
|
||||||
<div>Score equilibrado: @scoreEquilibradoSeleccionado</div>
|
<div>Score equilibrado A: @scoreEquilibradoSeleccionado</div>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(xyzOrigen))
|
@if (!string.IsNullOrWhiteSpace(xyzOrigen))
|
||||||
{
|
{
|
||||||
@@ -1113,6 +1028,27 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (!string.IsNullOrWhiteSpace(_resumenErrorHistorialLocal))
|
||||||
|
{
|
||||||
|
<div class="history-local-diagnostic" role="alert">
|
||||||
|
<div class="history-local-diagnostic__title">Diagnóstico del historial personal</div>
|
||||||
|
<div class="history-local-diagnostic__summary">@_resumenErrorHistorialLocal</div>
|
||||||
|
<details class="history-local-diagnostic__details">
|
||||||
|
<summary>Detalle técnico</summary>
|
||||||
|
<pre>@_detalleErrorHistorialLocal</pre>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
@if (!_historialLocalCargado && !_historialLocalCargando)
|
||||||
|
{
|
||||||
|
<button type="button"
|
||||||
|
class="history-local-diagnostic__retry"
|
||||||
|
@onclick="ReintentarCargaHistorialLocalAsync">
|
||||||
|
Reintentar carga
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="history-section">
|
<div class="history-section">
|
||||||
@@ -1262,10 +1198,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="config-row">
|
<div class="config-row">
|
||||||
<label>Bus/paradas:</label>
|
<label>Paradas:</label>
|
||||||
<div class="config-dual-labeled">
|
<div class="config-dual-labeled">
|
||||||
<label class="config-dual-labeled__field">
|
<label class="config-dual-labeled__field">
|
||||||
<span>Seg. parada</span>
|
<span>Seg./parada</span>
|
||||||
@if (_usarHorariosTeoricos)
|
@if (_usarHorariosTeoricos)
|
||||||
{
|
{
|
||||||
<input type="number" class="config-input" step="1" min="0" value="0" disabled readonly />
|
<input type="number" class="config-input" step="1" min="0" value="0" disabled readonly />
|
||||||
@@ -1348,7 +1284,7 @@
|
|||||||
@bind="_itinerarioMin"
|
@bind="_itinerarioMin"
|
||||||
@bind:event="onchange"
|
@bind:event="onchange"
|
||||||
@bind:after="PersistirConfiguracionAsync">
|
@bind:after="PersistirConfiguracionAsync">
|
||||||
@foreach (var n in Range(1, 150, 1))
|
@foreach (var n in Range(1, 99, 1))
|
||||||
{
|
{
|
||||||
<option value="@n">@n</option>
|
<option value="@n">@n</option>
|
||||||
}
|
}
|
||||||
@@ -1361,7 +1297,7 @@
|
|||||||
@bind="_itinerarioMax"
|
@bind="_itinerarioMax"
|
||||||
@bind:event="onchange"
|
@bind:event="onchange"
|
||||||
@bind:after="PersistirConfiguracionAsync">
|
@bind:after="PersistirConfiguracionAsync">
|
||||||
@foreach (var n in Range(1, 150, 1))
|
@foreach (var n in Range(1, 99, 1))
|
||||||
{
|
{
|
||||||
<option value="@n">@n</option>
|
<option value="@n">@n</option>
|
||||||
}
|
}
|
||||||
@@ -1441,19 +1377,23 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="config-row config-row--radio-legacy">
|
<div class="config-row">
|
||||||
<label>Coef. / Param.(m) andando:</label>
|
<label>Andando:</label>
|
||||||
<div class="config-dual-inline config-dual-inline--bare">
|
<div class="config-dual-labeled">
|
||||||
|
<label class="config-dual-labeled__field">
|
||||||
|
<span>Coef.</span>
|
||||||
<input type="number" class="config-input" step="any"
|
<input type="number" class="config-input" step="any"
|
||||||
placeholder="Coef."
|
title="Coeficiente de penalización andando"
|
||||||
title="Coeficiente de penalizacion"
|
|
||||||
@bind="FactorPenalizacionDistanciaPieTexto" @bind:event="oninput"
|
@bind="FactorPenalizacionDistanciaPieTexto" @bind:event="oninput"
|
||||||
@onblur="OnBlurFactorPenalizacionDistanciaPie" />
|
@onblur="OnBlurFactorPenalizacionDistanciaPie" />
|
||||||
|
</label>
|
||||||
|
<label class="config-dual-labeled__field">
|
||||||
|
<span>Base (m)</span>
|
||||||
<input type="number" class="config-input" step="any"
|
<input type="number" class="config-input" step="any"
|
||||||
placeholder="Param."
|
title="Parámetro base en metros"
|
||||||
title="Parametro base en metros"
|
|
||||||
@bind="ParametroPenalizacionDistanciaPieTexto" @bind:event="oninput"
|
@bind="ParametroPenalizacionDistanciaPieTexto" @bind:event="oninput"
|
||||||
@onblur="OnBlurParametroPenalizacionDistanciaPie" />
|
@onblur="OnBlurParametroPenalizacionDistanciaPie" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1464,7 +1404,7 @@
|
|||||||
<span>Peso rank.</span>
|
<span>Peso rank.</span>
|
||||||
<input type="text" class="config-input"
|
<input type="text" class="config-input"
|
||||||
placeholder="4,0"
|
placeholder="4,0"
|
||||||
title="Penalizacion interna por cada transbordo real en minutos decimales"
|
title="Penalización temporal y distancia equivalente por cada transbordo real"
|
||||||
@bind="PesoTransbordoRankingTexto" @bind:event="oninput"
|
@bind="PesoTransbordoRankingTexto" @bind:event="oninput"
|
||||||
@onblur="OnBlurPesoTransbordoRanking" />
|
@onblur="OnBlurPesoTransbordoRanking" />
|
||||||
</label>
|
</label>
|
||||||
@@ -1478,57 +1418,88 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="config-row">
|
<div class="config-row">
|
||||||
<label>Equilibrado:</label>
|
<label>Factor / Método:</label>
|
||||||
<div class="config-dual-labeled config-dual-labeled--equilibrado">
|
<div class="config-dual-labeled">
|
||||||
<label class="config-dual-labeled__field">
|
<label class="config-dual-labeled__field">
|
||||||
<span>Factor</span>
|
<span>Equilib. A</span>
|
||||||
<input type="number" class="config-input" step="0.1" min="0"
|
<input type="number" class="config-input" step="0.1" min="0"
|
||||||
placeholder="12"
|
placeholder="6"
|
||||||
title="Factor del modo equilibrado"
|
title="Factor del modo equilibrado"
|
||||||
@bind="FactorEquilibradoAndarBusTexto" @bind:event="oninput"
|
@bind="FactorEquilibradoAndarBusTexto" @bind:event="oninput"
|
||||||
@onblur="OnBlurFactorEquilibradoAndarBus" />
|
@onblur="OnBlurFactorEquilibradoAndarBus" />
|
||||||
</label>
|
</label>
|
||||||
|
<label class="config-dual-labeled__field">
|
||||||
|
<span>Andando</span>
|
||||||
|
<select class="config-input"
|
||||||
|
title="Método de cálculo andando"
|
||||||
|
@bind="_walkProvider" @bind:after="PersistirConfiguracionAsync">
|
||||||
|
<option value="@ProveedorCaminata.Osrm">v1</option>
|
||||||
|
<option value="@ProveedorCaminata.Valhalla">v2</option>
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="config-row">
|
<div class="config-row">
|
||||||
<label>Vel. Andando/Bus (km/h):</label>
|
<label>Velocidad:</label>
|
||||||
<div class="config-dual-inline config-dual-inline--bare">
|
<div class="config-dual-labeled">
|
||||||
|
<label class="config-dual-labeled__field">
|
||||||
|
<span>Andar (km/h)</span>
|
||||||
<input type="number" class="config-input" step="0.1"
|
<input type="number" class="config-input" step="0.1"
|
||||||
placeholder="Andar"
|
|
||||||
title="Velocidad andando (km/h)"
|
title="Velocidad andando (km/h)"
|
||||||
@bind="_velocidadPieKmH" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
@bind="_velocidadPieKmH" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||||
|
</label>
|
||||||
|
<label class="config-dual-labeled__field">
|
||||||
|
<span>Bus (km/h)</span>
|
||||||
<input type="number" class="config-input" step="0.1"
|
<input type="number" class="config-input" step="0.1"
|
||||||
placeholder="Bus"
|
|
||||||
title="Velocidad bus (km/h)"
|
title="Velocidad bus (km/h)"
|
||||||
@bind="_velocidadBusKmH" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
@bind="_velocidadBusKmH" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="config-row">
|
<div class="config-row">
|
||||||
<label>Altitud: Max Puntos / Coef</label>
|
<label>Altitud:</label>
|
||||||
<div class="config-dual-inline config-dual-inline--bare">
|
<div class="config-dual-labeled">
|
||||||
|
<label class="config-dual-labeled__field">
|
||||||
|
<span>Máx. puntos</span>
|
||||||
<input type="number" class="config-input" min="2" max="999" step="1"
|
<input type="number" class="config-input" min="2" max="999" step="1"
|
||||||
placeholder="Puntos"
|
|
||||||
title="Máximo de puntos de altitud por tramo"
|
title="Máximo de puntos de altitud por tramo"
|
||||||
@bind="_elevMaxPuntosPorTramo" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
@bind="_elevMaxPuntosPorTramo" @bind:event="oninput" @bind:after="PersistirConfiguracionAsync" />
|
||||||
|
</label>
|
||||||
|
<label class="config-dual-labeled__field">
|
||||||
|
<span>Coef.</span>
|
||||||
<input type="number" class="config-input" step="any"
|
<input type="number" class="config-input" step="any"
|
||||||
placeholder="Coef."
|
|
||||||
title="Coeficiente de esfuerzo por altura"
|
title="Coeficiente de esfuerzo por altura"
|
||||||
@bind="CoeficienteEsfuerzoAlturaTexto" @bind:event="oninput"
|
@bind="CoeficienteEsfuerzoAlturaTexto" @bind:event="oninput"
|
||||||
@onblur="OnBlurCoeficienteEsfuerzoAltura" />
|
@onblur="OnBlurCoeficienteEsfuerzoAltura" />
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="config-row">
|
|
||||||
<label>Método cálculo andando:</label>
|
|
||||||
|
|
||||||
<div class="num-inline">
|
<div class="config-row config-row-pendientes">
|
||||||
<select class="config-input"
|
<label>Pendientes:</label>
|
||||||
style="width:100%; min-width:0;"
|
<div class="config-pendientes-inline">
|
||||||
@bind="_walkProvider" @bind:after="PersistirConfiguracionAsync">
|
<label class="route-elevation-switch @(_mostrarElevacionesRuta ? "route-elevation-switch--active" : "")"
|
||||||
<option value="@ProveedorCaminata.Osrm">v1</option>
|
title="Activar o desactivar las pendientes en el cálculo">
|
||||||
<option value="@ProveedorCaminata.Valhalla">v2</option>
|
<input type="checkbox"
|
||||||
</select>
|
checked="@_mostrarElevacionesRuta"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="ToggleElevacionesRutaAsync" />
|
||||||
|
<span class="route-elevation-switch-track" aria-hidden="true">
|
||||||
|
<span class="route-elevation-switch-thumb"></span>
|
||||||
|
</span>
|
||||||
|
<span class="config-pendientes-inline__label">Visualizar</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<button type="button"
|
||||||
|
class="config-pendientes-settings-button"
|
||||||
|
title="Configurar Pendientes"
|
||||||
|
aria-label="Configurar Pendientes"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onclick="() => _mostrarConfiguracionPendientesRuta = true">
|
||||||
|
<span aria-hidden="true">⚙</span>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1562,6 +1533,57 @@
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (_mostrarConfiguracionPendientesRuta)
|
||||||
|
{
|
||||||
|
<div class="custom-modal-backdrop pendientes-config-modal-backdrop"
|
||||||
|
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
|
||||||
|
<div class="custom-modal-card pendientes-config-modal" @onclick:stopPropagation="true">
|
||||||
|
<div class="pendientes-config-modal__header">
|
||||||
|
<div class="custom-modal-title">Configurar Pendientes</div>
|
||||||
|
<button type="button"
|
||||||
|
class="config-close-btn pendientes-config-modal__close"
|
||||||
|
title="Cerrar"
|
||||||
|
aria-label="Cerrar"
|
||||||
|
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="pendientes-config-modal__fields">
|
||||||
|
<label class="pendientes-config-modal__field">
|
||||||
|
<span>Subida (%)</span>
|
||||||
|
<input type="text"
|
||||||
|
class="config-input"
|
||||||
|
inputmode="decimal"
|
||||||
|
value="@_umbralPendienteSubidaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, true))" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="pendientes-config-modal__field">
|
||||||
|
<span>Bajada (%)</span>
|
||||||
|
<input type="text"
|
||||||
|
class="config-input"
|
||||||
|
inputmode="decimal"
|
||||||
|
value="@_umbralPendienteBajadaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, false))" />
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<label class="pendientes-config-modal__field">
|
||||||
|
<span>Alerta (m)</span>
|
||||||
|
<input type="text"
|
||||||
|
class="config-input"
|
||||||
|
inputmode="decimal"
|
||||||
|
value="@_umbralAlturaAlertaPendienteRutaMetros.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="CambiarUmbralAlturaAlertaPendienteRutaAsync" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (_mostrarModelosHorariosPanel)
|
@if (_mostrarModelosHorariosPanel)
|
||||||
{
|
{
|
||||||
<div class="custom-modal-backdrop modelos-horarios-modal-backdrop" @onclick="CerrarModelosHorariosPanel">
|
<div class="custom-modal-backdrop modelos-horarios-modal-backdrop" @onclick="CerrarModelosHorariosPanel">
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public sealed class ParametrosPlanificadorRuta
|
|||||||
public double FactorPenalizacionDistanciaPie { get; set; } = 1.35;
|
public double FactorPenalizacionDistanciaPie { get; set; } = 1.35;
|
||||||
public double ParametroPenalizacionDistanciaPieMetros { get; set; } = 200.0;
|
public double ParametroPenalizacionDistanciaPieMetros { get; set; } = 200.0;
|
||||||
public double PesoTransbordoRankingMinutos { get; set; } = 4.0;
|
public double PesoTransbordoRankingMinutos { get; set; } = 4.0;
|
||||||
public double FactorEquilibradoAndarBus { get; set; } = 12.0;
|
public double FactorEquilibradoAndarBus { get; set; } = 6.0;
|
||||||
|
|
||||||
// selección candidatas
|
// selección candidatas
|
||||||
public int RadioParadasCandidatasMetros { get; set; } = 1200;
|
public int RadioParadasCandidatasMetros { get; set; } = 1200;
|
||||||
|
|||||||
@@ -828,9 +828,12 @@ public sealed class BuscadorRutasTemporalesDbus
|
|||||||
parametros);
|
parametros);
|
||||||
var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
||||||
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
||||||
: 12.0;
|
: 6.0;
|
||||||
|
var penalizacionTransbordos = UtilidadesTransitoDbus.CalcularPenalizacionTransbordosRankingMetros(
|
||||||
|
UtilidadesTransitoDbus.ContarTransbordosReales(resultado.RutaBus.Tramos),
|
||||||
|
parametros);
|
||||||
|
|
||||||
var score = distanciaBus + (distanciaAndando * factor);
|
var score = distanciaBus + (distanciaAndando * factor) + penalizacionTransbordos;
|
||||||
return double.IsFinite(score) && score >= 0
|
return double.IsFinite(score) && score >= 0
|
||||||
? score
|
? score
|
||||||
: double.PositiveInfinity;
|
: double.PositiveInfinity;
|
||||||
|
|||||||
@@ -1449,6 +1449,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
|||||||
var limiteCoste = Math.Max(1, maxPorCoste);
|
var limiteCoste = Math.Max(1, maxPorCoste);
|
||||||
var limiteAndar = Math.Max(8, limiteCoste / 2);
|
var limiteAndar = Math.Max(8, limiteCoste / 2);
|
||||||
var limiteEquilibrado = Math.Max(8, limiteCoste / 2);
|
var limiteEquilibrado = Math.Max(8, limiteCoste / 2);
|
||||||
|
var limiteDirectas = Math.Max(8, limiteCoste / 4);
|
||||||
var duracionMinima = lista
|
var duracionMinima = lista
|
||||||
.Select(x => x.DuracionEstimadaSegundos)
|
.Select(x => x.DuracionEstimadaSegundos)
|
||||||
.Where(double.IsFinite)
|
.Where(double.IsFinite)
|
||||||
@@ -1479,6 +1480,15 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
|||||||
.ThenBy(x => x.PuntuacionCoste)
|
.ThenBy(x => x.PuntuacionCoste)
|
||||||
.Take(limiteEquilibrado));
|
.Take(limiteEquilibrado));
|
||||||
|
|
||||||
|
// Las directas no deben depender de que una reconstruccion circular las vuelva a inyectar.
|
||||||
|
Agregar(lista
|
||||||
|
.Where(x => x.RutaBus.Tramos.Count == 1)
|
||||||
|
.OrderBy(x => CalcularScoreBusquedaEquilibrado(x, parametros))
|
||||||
|
.ThenBy(x => x.PuntuacionCoste)
|
||||||
|
.ThenBy(x => x.DuracionEstimadaSegundos)
|
||||||
|
.ThenBy(x => x.DistanciaPieTotal)
|
||||||
|
.Take(limiteDirectas));
|
||||||
|
|
||||||
Agregar(lista
|
Agregar(lista
|
||||||
.Where(x => x.DuracionEstimadaSegundos <= limiteDuracionAndarRazonable)
|
.Where(x => x.DuracionEstimadaSegundos <= limiteDuracionAndarRazonable)
|
||||||
.OrderBy(x => x.DistanciaPieTotal)
|
.OrderBy(x => x.DistanciaPieTotal)
|
||||||
@@ -1527,9 +1537,14 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
|||||||
parametros);
|
parametros);
|
||||||
var factor = parametros is not null && double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
var factor = parametros is not null && double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
||||||
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
||||||
: 12.0;
|
: 6.0;
|
||||||
|
var penalizacionTransbordos = parametros is null
|
||||||
|
? 0
|
||||||
|
: UtilidadesTransitoDbus.CalcularPenalizacionTransbordosRankingMetros(
|
||||||
|
UtilidadesTransitoDbus.ContarTransbordosReales(resultado.RutaBus.Tramos),
|
||||||
|
parametros);
|
||||||
|
|
||||||
var score = distanciaBus + (distanciaAndando * factor);
|
var score = distanciaBus + (distanciaAndando * factor) + penalizacionTransbordos;
|
||||||
return double.IsFinite(score) && score >= 0
|
return double.IsFinite(score) && score >= 0
|
||||||
? score
|
? score
|
||||||
: double.PositiveInfinity;
|
: double.PositiveInfinity;
|
||||||
@@ -3199,9 +3214,12 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
|||||||
var distanciaAndando = CalcularDistanciaAndandoCorregidaMetros(alternativa, parametros);
|
var distanciaAndando = CalcularDistanciaAndandoCorregidaMetros(alternativa, parametros);
|
||||||
var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
||||||
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
||||||
: 12.0;
|
: 6.0;
|
||||||
|
var penalizacionTransbordos = UtilidadesTransitoDbus.CalcularPenalizacionTransbordosRankingMetros(
|
||||||
|
alternativa.NumeroTransbordosReales,
|
||||||
|
parametros);
|
||||||
|
|
||||||
var score = distanciaBus + (distanciaAndando * factor);
|
var score = distanciaBus + (distanciaAndando * factor) + penalizacionTransbordos;
|
||||||
return double.IsFinite(score) && score >= 0
|
return double.IsFinite(score) && score >= 0
|
||||||
? score
|
? score
|
||||||
: double.PositiveInfinity;
|
: double.PositiveInfinity;
|
||||||
@@ -3458,7 +3476,8 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Firma orientada a UI: agrupa alternativas que usan la misma familia de lineas aunque cambie la parada exacta.
|
/// Firma orientada a UI: agrupa alternativas con la misma secuencia de lineas y sentidos
|
||||||
|
/// aunque cambie la parada exacta.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private static string CrearFirmaFamiliaRutaBus(RutaBusAlternativa alternativa)
|
private static string CrearFirmaFamiliaRutaBus(RutaBusAlternativa alternativa)
|
||||||
{
|
{
|
||||||
@@ -3466,7 +3485,8 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
|||||||
foreach (var tramo in alternativa.Tramos)
|
foreach (var tramo in alternativa.Tramos)
|
||||||
{
|
{
|
||||||
var linea = UtilidadesTransitoDbus.LineaCodigoStr(tramo.Variante);
|
var linea = UtilidadesTransitoDbus.LineaCodigoStr(tramo.Variante);
|
||||||
var parte = $"{linea}.{tramo.Variante.CodigoItinerario}";
|
var sentido = UtilidadesTransitoDbus.NormalizarSentidoCodigo(tramo.Variante.Sentido?.Codigo);
|
||||||
|
var parte = $"{linea}.{tramo.Variante.CodigoItinerario}-{sentido}";
|
||||||
if (partes.Count == 0 || !string.Equals(partes[^1], parte, StringComparison.OrdinalIgnoreCase))
|
if (partes.Count == 0 || !string.Equals(partes[^1], parte, StringComparison.OrdinalIgnoreCase))
|
||||||
partes.Add(parte);
|
partes.Add(parte);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -384,6 +384,28 @@ internal static class UtilidadesTransitoDbus
|
|||||||
return numeroTransbordosReales * pesoMinutos * 60.0;
|
return numeroTransbordosReales * pesoMinutos * 60.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convierte la penalizacion temporal de los transbordos en distancia equivalente de bus.
|
||||||
|
/// </summary>
|
||||||
|
public static double CalcularPenalizacionTransbordosRankingMetros(
|
||||||
|
int numeroTransbordosReales,
|
||||||
|
ParametrosPlanificadorRuta parametros)
|
||||||
|
{
|
||||||
|
var penalizacionSegundos = CalcularPenalizacionTransbordosRankingSegundos(
|
||||||
|
numeroTransbordosReales,
|
||||||
|
parametros);
|
||||||
|
if (penalizacionSegundos <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var velocidadBusKmH = double.IsFinite(parametros.VelocidadBusKmH) && parametros.VelocidadBusKmH > 0
|
||||||
|
? parametros.VelocidadBusKmH
|
||||||
|
: 16.0;
|
||||||
|
var penalizacionMetros = penalizacionSegundos * (velocidadBusKmH * 1000.0 / 3600.0);
|
||||||
|
return double.IsFinite(penalizacionMetros) && penalizacionMetros > 0
|
||||||
|
? penalizacionMetros
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calcula el coste interno aplicando preferencias de caminata y transbordos reales.
|
/// Calcula el coste interno aplicando preferencias de caminata y transbordos reales.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -872,24 +872,6 @@ html, body {
|
|||||||
background: #2563eb;
|
background: #2563eb;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-visual-options {
|
|
||||||
position: relative;
|
|
||||||
display: inline-flex;
|
|
||||||
align-self: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-start;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
width: fit-content;
|
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
gap: .25rem;
|
|
||||||
margin: -.15rem 0 .4rem;
|
|
||||||
padding: 2px 3px 2px 5px;
|
|
||||||
border: 1px solid rgba(148, 163, 184, .28);
|
|
||||||
border-radius: 7px;
|
|
||||||
background: rgba(2, 6, 23, .3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-switch {
|
.route-elevation-switch {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -952,130 +934,6 @@ html, body {
|
|||||||
opacity: .55;
|
opacity: .55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-settings {
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
margin-left: .05rem;
|
|
||||||
padding-left: .3rem;
|
|
||||||
border-left: 1px solid rgba(148, 163, 184, .35);
|
|
||||||
pointer-events: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-trigger {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
width: 26px;
|
|
||||||
height: 24px;
|
|
||||||
padding: 0;
|
|
||||||
border: 1px solid rgba(148, 163, 184, .55);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: rgba(2, 6, 23, .78);
|
|
||||||
color: #cbd5e1;
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: "Segoe UI Symbol", sans-serif;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-trigger:hover,
|
|
||||||
.route-elevation-settings-trigger--active {
|
|
||||||
border-color: rgba(56, 189, 248, .9);
|
|
||||||
background: rgba(30, 64, 175, .55);
|
|
||||||
color: #f8fafc;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-trigger:disabled {
|
|
||||||
cursor: default;
|
|
||||||
opacity: .55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-popover {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 20;
|
|
||||||
top: calc(100% + 6px);
|
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
width: 238px;
|
|
||||||
max-width: calc(100vw - 24px);
|
|
||||||
box-sizing: border-box;
|
|
||||||
padding: .55rem;
|
|
||||||
border: 1px solid rgba(148, 163, 184, .55);
|
|
||||||
border-radius: 7px;
|
|
||||||
background: rgba(15, 23, 42, .99);
|
|
||||||
box-shadow: 0 14px 32px rgba(0, 0, 0, .5);
|
|
||||||
color: #e5e7eb;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-header {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: .5rem;
|
|
||||||
margin-bottom: .45rem;
|
|
||||||
font-size: .72rem;
|
|
||||||
font-weight: 800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-close {
|
|
||||||
display: grid;
|
|
||||||
place-items: center;
|
|
||||||
width: 22px;
|
|
||||||
height: 22px;
|
|
||||||
padding: 0;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 4px;
|
|
||||||
background: transparent;
|
|
||||||
color: #cbd5e1;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 1rem;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-close:hover {
|
|
||||||
background: rgba(71, 85, 105, .55);
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-fields {
|
|
||||||
display: grid;
|
|
||||||
gap: .35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-setting-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr) 44px 32px;
|
|
||||||
align-items: center;
|
|
||||||
gap: .35rem;
|
|
||||||
min-height: 26px;
|
|
||||||
color: #cbd5e1;
|
|
||||||
font-size: .68rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-setting-row input {
|
|
||||||
width: 44px;
|
|
||||||
height: 22px;
|
|
||||||
box-sizing: border-box;
|
|
||||||
border: 1px solid rgba(148, 163, 184, .55);
|
|
||||||
border-radius: 6px;
|
|
||||||
background: rgba(2, 6, 23, .78);
|
|
||||||
color: #f8fafc;
|
|
||||||
font-size: .68rem;
|
|
||||||
font-weight: 800;
|
|
||||||
text-align: center;
|
|
||||||
padding: 0 .18rem;
|
|
||||||
outline: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-setting-row input:focus {
|
|
||||||
border-color: rgba(56, 189, 248, .95);
|
|
||||||
box-shadow: 0 0 0 2px rgba(56, 189, 248, .18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-setting-row input:disabled {
|
|
||||||
opacity: .55;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-slope-warning {
|
.route-slope-warning {
|
||||||
margin: 0 0 .45rem;
|
margin: 0 0 .45rem;
|
||||||
padding: .36rem .5rem;
|
padding: .36rem .5rem;
|
||||||
@@ -1414,6 +1272,70 @@ html, body {
|
|||||||
color: #fca5a5;
|
color: #fca5a5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history-local-diagnostic {
|
||||||
|
margin-top: .35rem;
|
||||||
|
padding: .5rem .58rem;
|
||||||
|
border-left: 3px solid #ef4444;
|
||||||
|
background: rgba(127, 29, 29, .18);
|
||||||
|
color: #fecaca;
|
||||||
|
font-size: .72rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-local-diagnostic__title {
|
||||||
|
color: #fee2e2;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-local-diagnostic__summary {
|
||||||
|
margin-top: .14rem;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-local-diagnostic__details {
|
||||||
|
margin-top: .3rem;
|
||||||
|
color: #fca5a5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-local-diagnostic__details summary {
|
||||||
|
width: fit-content;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #fda4af;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-local-diagnostic__details pre {
|
||||||
|
max-height: 150px;
|
||||||
|
margin: .35rem 0 0;
|
||||||
|
padding: .38rem .42rem;
|
||||||
|
overflow: auto;
|
||||||
|
border: 1px solid rgba(248, 113, 113, .25);
|
||||||
|
background: rgba(15, 23, 42, .72);
|
||||||
|
color: #fecaca;
|
||||||
|
font-family: Consolas, monospace;
|
||||||
|
font-size: .66rem;
|
||||||
|
line-height: 1.35;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-local-diagnostic__retry {
|
||||||
|
margin-top: .42rem;
|
||||||
|
padding: .3rem .55rem;
|
||||||
|
border: 1px solid rgba(248, 113, 113, .6);
|
||||||
|
border-radius: .35rem;
|
||||||
|
background: rgba(127, 29, 29, .32);
|
||||||
|
color: #fee2e2;
|
||||||
|
font-size: .7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-local-diagnostic__retry:hover {
|
||||||
|
border-color: #fca5a5;
|
||||||
|
background: rgba(153, 27, 27, .48);
|
||||||
|
}
|
||||||
|
|
||||||
.config-panel {
|
.config-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: 2.4rem;
|
bottom: 2.4rem;
|
||||||
@@ -1755,12 +1677,36 @@ html, body {
|
|||||||
border: none;
|
border: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-stop-direction-icon {
|
.rt-highlighted-stop-icon {
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-highlighted-stop-marker {
|
||||||
|
position: relative;
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-highlighted-stop-marker__disc,
|
||||||
|
.rt-highlighted-stop-marker__ring {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rt-highlighted-stop-marker__ring {
|
||||||
|
inset: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-highlighted-stop-marker__disc {
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.rt-stop-direction-arrow {
|
.rt-stop-direction-arrow {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -1770,6 +1716,8 @@ html, body {
|
|||||||
transform: translate(-50%, -50%) rotate(var(--stop-bearing, 0deg));
|
transform: translate(-50%, -50%) rotate(var(--stop-bearing, 0deg));
|
||||||
transform-origin: 50% 50%;
|
transform-origin: 50% 50%;
|
||||||
filter: drop-shadow(0 0 1px #0f172a) drop-shadow(0 1px 0 #0f172a);
|
filter: drop-shadow(0 0 1px #0f172a) drop-shadow(0 1px 0 #0f172a);
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-stop-direction-arrow::before {
|
.rt-stop-direction-arrow::before {
|
||||||
@@ -1863,53 +1811,6 @@ html, body {
|
|||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
|
|
||||||
.num-inline {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1.6rem 1.6rem; /* input | ? | ? */
|
|
||||||
gap: .25rem;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
.num-inline .config-input {
|
|
||||||
width: 100% !important; /* pisa width:90px anterior */
|
|
||||||
min-width: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-dual-inline {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: .5rem;
|
|
||||||
align-items: center;
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-dual-inline .config-input {
|
|
||||||
width: 100% !important;
|
|
||||||
min-width: 0;
|
|
||||||
box-sizing: border-box;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-dual-inline--bare {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 4.2rem));
|
|
||||||
justify-content: end;
|
|
||||||
gap: .35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-dual-inline--bare .config-input {
|
|
||||||
max-width: none;
|
|
||||||
text-align: center;
|
|
||||||
padding-left: .35rem;
|
|
||||||
padding-right: .35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-dual-inline--bare .config-input::placeholder {
|
|
||||||
color: #94a3b8;
|
|
||||||
opacity: 1;
|
|
||||||
font-size: .72rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.config-dual-labeled {
|
.config-dual-labeled {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(2, minmax(0, 4.2rem));
|
grid-template-columns: repeat(2, minmax(0, 4.2rem));
|
||||||
@@ -1941,19 +1842,113 @@ html, body {
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-dual-labeled--equilibrado {
|
|
||||||
grid-template-columns: minmax(0, 4rem);
|
|
||||||
align-items: end;
|
|
||||||
justify-content: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* ✅ Solo esta fila: label arriba + control abajo, ancho completo */
|
/* ✅ Solo esta fila: label arriba + control abajo, ancho completo */
|
||||||
.config-row.config-row-full {
|
.config-row.config-row-full {
|
||||||
grid-template-columns: 1fr !important;
|
grid-template-columns: 1fr !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.config-pendientes-inline {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: .28rem;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pendientes-inline .route-elevation-switch {
|
||||||
|
min-height: 30px;
|
||||||
|
font-size: .68rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pendientes-inline .route-elevation-switch--active .route-elevation-switch-track {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
background: #2563eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pendientes-settings-button {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
flex: 0 0 28px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 6px;
|
||||||
|
background: transparent;
|
||||||
|
color: #94a3b8;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: "Segoe UI Symbol", sans-serif;
|
||||||
|
font-size: .92rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pendientes-settings-button:hover,
|
||||||
|
.config-pendientes-settings-button:focus-visible {
|
||||||
|
background: rgba(51, 65, 85, .72);
|
||||||
|
color: #f8fafc;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pendientes-settings-button:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: .5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal-backdrop {
|
||||||
|
z-index: 5200;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal {
|
||||||
|
width: min(286px, calc(100vw - 24px));
|
||||||
|
padding: .75rem .8rem .8rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5rem;
|
||||||
|
min-height: 30px;
|
||||||
|
margin-bottom: .65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal__header .custom-modal-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: .86rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal__close {
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
flex: 0 0 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal__fields {
|
||||||
|
display: grid;
|
||||||
|
gap: .45rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal__field {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 86px;
|
||||||
|
align-items: center;
|
||||||
|
gap: .65rem;
|
||||||
|
min-width: 0;
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-size: .76rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal__field .config-input {
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
.config-row-map-opacity {
|
.config-row-map-opacity {
|
||||||
margin-top: .7rem !important;
|
margin-top: .7rem !important;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
@@ -3171,37 +3166,11 @@ html, body {
|
|||||||
font-size: .73rem;
|
font-size: .73rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-visual-options {
|
|
||||||
justify-content: flex-start;
|
|
||||||
flex-wrap: nowrap;
|
|
||||||
gap: 4px;
|
|
||||||
margin: 0 0 8px;
|
|
||||||
padding: 2px 3px 2px 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-switch {
|
.route-elevation-switch {
|
||||||
min-height: 44px;
|
min-height: 44px;
|
||||||
font-size: .72rem;
|
font-size: .72rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-settings-trigger {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-settings-popover {
|
|
||||||
right: auto;
|
|
||||||
left: 0;
|
|
||||||
width: 250px;
|
|
||||||
max-width: calc(100vw - 20px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-setting-row {
|
|
||||||
grid-template-columns: minmax(0, 1fr) 48px 36px;
|
|
||||||
min-height: 40px;
|
|
||||||
font-size: .72rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.route-elevation-setting-row input {
|
.route-elevation-setting-row input {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
@@ -3283,7 +3252,6 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.route-panel-overlay--mobile-collapsed .route-priority-switch,
|
.route-panel-overlay--mobile-collapsed .route-priority-switch,
|
||||||
.route-panel-overlay--mobile-collapsed .route-visual-options,
|
|
||||||
.route-panel-overlay--mobile-collapsed .route-alt-list,
|
.route-panel-overlay--mobile-collapsed .route-alt-list,
|
||||||
.route-panel-overlay--mobile-collapsed .route-panel-divider,
|
.route-panel-overlay--mobile-collapsed .route-panel-divider,
|
||||||
.route-panel-overlay--mobile-collapsed .route-panel-body {
|
.route-panel-overlay--mobile-collapsed .route-panel-body {
|
||||||
@@ -3400,9 +3368,23 @@ html, body {
|
|||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.config-pendientes-inline .route-elevation-switch,
|
||||||
|
.config-pendientes-settings-button {
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.config-pendientes-settings-button {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
flex-basis: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pendientes-config-modal__field .config-input {
|
||||||
|
height: 42px;
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
.config-row > .config-input,
|
.config-row > .config-input,
|
||||||
.config-row > .num-inline,
|
|
||||||
.config-row > .config-dual-inline,
|
|
||||||
.config-row > .config-dual-labeled {
|
.config-row > .config-dual-labeled {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -901,29 +901,89 @@ window.rt.ensureItStopsLayer = function () {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.rt.addStopDirectionMarker = function (group, stop, pane, radius) {
|
window.rt.createHighlightedStopMarker = function (stop, pane, options) {
|
||||||
if (!group || !stop || !window.L) return;
|
if (!stop || !window.L) return null;
|
||||||
|
|
||||||
const bearing = Number(stop.bearing);
|
|
||||||
const lat = Number(stop.lat);
|
const lat = Number(stop.lat);
|
||||||
const lon = Number(stop.lon);
|
const lon = Number(stop.lon);
|
||||||
if (!Number.isFinite(bearing) || !Number.isFinite(lat) || !Number.isFinite(lon)) return;
|
if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;
|
||||||
|
|
||||||
|
const toPositiveNumber = function (value, fallback) {
|
||||||
|
const parsed = Number(value);
|
||||||
|
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
||||||
|
};
|
||||||
|
const toOpacity = function (value, fallback) {
|
||||||
|
const parsed = Number(value);
|
||||||
|
return Number.isFinite(parsed) ? Math.min(1, Math.max(0, parsed)) : fallback;
|
||||||
|
};
|
||||||
|
|
||||||
|
const radius = toPositiveNumber(options && options.radius, 7);
|
||||||
|
const weight = toPositiveNumber(options && options.weight, 2);
|
||||||
|
const color = String((options && options.color) || "#0f172a");
|
||||||
|
const fillColor = String((options && options.fillColor) || "#22c55e");
|
||||||
|
const opacity = toOpacity(options && options.opacity, 1);
|
||||||
|
const fillOpacity = toOpacity(options && options.fillOpacity, 1);
|
||||||
|
const size = Math.max(14, Math.ceil((radius * 2) + weight));
|
||||||
|
|
||||||
|
const markerElement = document.createElement("span");
|
||||||
|
markerElement.className = "rt-highlighted-stop-marker";
|
||||||
|
|
||||||
|
const disc = document.createElement("span");
|
||||||
|
disc.className = "rt-highlighted-stop-marker__disc";
|
||||||
|
disc.style.inset = `${weight}px`;
|
||||||
|
disc.style.backgroundColor = fillColor;
|
||||||
|
disc.style.opacity = String(fillOpacity);
|
||||||
|
markerElement.appendChild(disc);
|
||||||
|
|
||||||
|
const ring = document.createElement("span");
|
||||||
|
ring.className = "rt-highlighted-stop-marker__ring";
|
||||||
|
ring.style.borderColor = color;
|
||||||
|
ring.style.borderWidth = `${weight}px`;
|
||||||
|
ring.style.opacity = String(opacity);
|
||||||
|
markerElement.appendChild(ring);
|
||||||
|
|
||||||
|
const rawBearing = stop.bearing;
|
||||||
|
const bearing = rawBearing === null || rawBearing === undefined || rawBearing === ""
|
||||||
|
? null
|
||||||
|
: Number(rawBearing);
|
||||||
|
if (Number.isFinite(bearing)) {
|
||||||
|
const arrow = document.createElement("span");
|
||||||
|
arrow.className = "rt-stop-direction-arrow";
|
||||||
|
arrow.style.setProperty("--stop-bearing", `${bearing}deg`);
|
||||||
|
markerElement.appendChild(arrow);
|
||||||
|
}
|
||||||
|
|
||||||
const size = Math.max(14, Number(radius || 7) * 2);
|
|
||||||
const icon = window.L.divIcon({
|
const icon = window.L.divIcon({
|
||||||
className: "rt-stop-direction-icon",
|
className: "rt-highlighted-stop-icon",
|
||||||
html: `<span class="rt-stop-direction-arrow" style="--stop-bearing:${bearing}deg"></span>`,
|
html: markerElement,
|
||||||
iconSize: [size, size],
|
iconSize: [size, size],
|
||||||
iconAnchor: [size / 2, size / 2]
|
iconAnchor: [size / 2, size / 2]
|
||||||
});
|
});
|
||||||
|
|
||||||
window.L.marker([lat, lon], {
|
const marker = window.L.marker([lat, lon], {
|
||||||
pane,
|
pane,
|
||||||
icon,
|
icon,
|
||||||
interactive: false,
|
interactive: true,
|
||||||
keyboard: false,
|
keyboard: false,
|
||||||
|
bubblingMouseEvents: false,
|
||||||
zIndexOffset: 1000
|
zIndexOffset: 1000
|
||||||
}).addTo(group);
|
});
|
||||||
|
|
||||||
|
if (stop.code && window.rt._dotNetHelper) {
|
||||||
|
marker.on("click", function (event) {
|
||||||
|
try {
|
||||||
|
if (event && event.originalEvent) {
|
||||||
|
if (event.originalEvent.preventDefault) event.originalEvent.preventDefault();
|
||||||
|
if (event.originalEvent.stopPropagation) event.originalEvent.stopPropagation();
|
||||||
|
if (event.originalEvent.stopImmediatePropagation) event.originalEvent.stopImmediatePropagation();
|
||||||
|
}
|
||||||
|
|
||||||
|
window.rt._dotNetHelper.invokeMethodAsync("OnHighlightedStopClick", stop.code);
|
||||||
|
} catch (error) { }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return marker;
|
||||||
};
|
};
|
||||||
|
|
||||||
// stops: [{lat, lon, code, bearing}]
|
// stops: [{lat, lon, code, bearing}]
|
||||||
@@ -949,34 +1009,15 @@ window.rt.toggleItStops = function (id, stops, options) {
|
|||||||
if (stops && stops.length) {
|
if (stops && stops.length) {
|
||||||
for (let i = 0; i < stops.length; i++) {
|
for (let i = 0; i < stops.length; i++) {
|
||||||
const s = stops[i];
|
const s = stops[i];
|
||||||
|
const marker = window.rt.createHighlightedStopMarker(s, "rtStopsPane", {
|
||||||
const m = window.L.circleMarker([s.lat, s.lon], {
|
|
||||||
pane: "rtStopsPane",
|
|
||||||
radius,
|
radius,
|
||||||
color,
|
color,
|
||||||
weight,
|
weight,
|
||||||
opacity,
|
opacity,
|
||||||
fillColor,
|
fillColor,
|
||||||
fillOpacity,
|
fillOpacity
|
||||||
interactive: true
|
|
||||||
});
|
});
|
||||||
|
if (marker) marker.addTo(group);
|
||||||
if (s.code && window.rt._dotNetHelper) {
|
|
||||||
m.on("click", function (e) {
|
|
||||||
try {
|
|
||||||
if (e && e.originalEvent) {
|
|
||||||
if (e.originalEvent.preventDefault) e.originalEvent.preventDefault();
|
|
||||||
if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation();
|
|
||||||
if (e.originalEvent.stopImmediatePropagation) e.originalEvent.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.rt._dotNetHelper.invokeMethodAsync("OnHighlightedStopClick", s.code);
|
|
||||||
} catch (err) { }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
m.addTo(group);
|
|
||||||
window.rt.addStopDirectionMarker(group, s, "rtStopsPane", radius);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1027,34 +1068,15 @@ window.rt.drawRouteStops = function (id, stops, options) {
|
|||||||
|
|
||||||
for (let i = 0; i < stops.length; i++) {
|
for (let i = 0; i < stops.length; i++) {
|
||||||
const s = stops[i];
|
const s = stops[i];
|
||||||
|
const marker = window.rt.createHighlightedStopMarker(s, "rtRouteStopsPane", {
|
||||||
const m = window.L.circleMarker([s.lat, s.lon], {
|
|
||||||
pane: "rtRouteStopsPane",
|
|
||||||
radius,
|
radius,
|
||||||
color,
|
color,
|
||||||
weight,
|
weight,
|
||||||
opacity,
|
opacity,
|
||||||
fillColor,
|
fillColor,
|
||||||
fillOpacity,
|
fillOpacity
|
||||||
interactive: true
|
|
||||||
});
|
});
|
||||||
|
if (marker) marker.addTo(group);
|
||||||
if (s.code && window.rt._dotNetHelper) {
|
|
||||||
m.on("click", function (e) {
|
|
||||||
try {
|
|
||||||
if (e && e.originalEvent) {
|
|
||||||
if (e.originalEvent.preventDefault) e.originalEvent.preventDefault();
|
|
||||||
if (e.originalEvent.stopPropagation) e.originalEvent.stopPropagation();
|
|
||||||
if (e.originalEvent.stopImmediatePropagation) e.originalEvent.stopImmediatePropagation();
|
|
||||||
}
|
|
||||||
|
|
||||||
window.rt._dotNetHelper.invokeMethodAsync("OnHighlightedStopClick", s.code);
|
|
||||||
} catch (err) { }
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
m.addTo(group);
|
|
||||||
window.rt.addStopDirectionMarker(group, s, "rtRouteStopsPane", radius);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group.addTo(window.rt._routeStopsLayer);
|
group.addTo(window.rt._routeStopsLayer);
|
||||||
@@ -1537,20 +1559,65 @@ window.rt.initDraggablePopups = function () {
|
|||||||
applySavedPositions();
|
applySavedPositions();
|
||||||
};
|
};
|
||||||
|
|
||||||
window.rt.saveLocalValue = function (key, value) {
|
window.rt.saveLocalValueResult = function (key, value) {
|
||||||
try {
|
try {
|
||||||
if (!window.localStorage) return;
|
const storage = window.localStorage;
|
||||||
window.localStorage.setItem(key, value ?? "");
|
if (!storage) {
|
||||||
} catch { }
|
return {
|
||||||
|
ok: false,
|
||||||
|
errorName: "StorageUnavailable",
|
||||||
|
errorMessage: "El navegador no expone localStorage."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.setItem(key, value ?? "");
|
||||||
|
return { ok: true };
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
errorName: error?.name ? String(error.name) : "StorageError",
|
||||||
|
errorMessage: error?.message ? String(error.message) : String(error ?? "Error desconocido al guardar en localStorage.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.rt.loadLocalValueResult = function (key) {
|
||||||
|
try {
|
||||||
|
const storage = window.localStorage;
|
||||||
|
if (!storage) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
found: false,
|
||||||
|
value: null,
|
||||||
|
errorName: "StorageUnavailable",
|
||||||
|
errorMessage: "El navegador no expone localStorage."
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const value = storage.getItem(key);
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
found: value !== null,
|
||||||
|
value
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
found: false,
|
||||||
|
value: null,
|
||||||
|
errorName: error?.name ? String(error.name) : "StorageError",
|
||||||
|
errorMessage: error?.message ? String(error.message) : String(error ?? "Error desconocido al leer localStorage.")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
window.rt.saveLocalValue = function (key, value) {
|
||||||
|
window.rt.saveLocalValueResult(key, value);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.rt.loadLocalValue = function (key) {
|
window.rt.loadLocalValue = function (key) {
|
||||||
try {
|
const result = window.rt.loadLocalValueResult(key);
|
||||||
if (!window.localStorage) return null;
|
return result.ok ? result.value : null;
|
||||||
return window.localStorage.getItem(key);
|
|
||||||
} catch {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
window.rt.clearLocalBrowserData = async function () {
|
window.rt.clearLocalBrowserData = async function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user