- Añadidas alternativas que permiten incorporarse directamente al segundo sentido de una línea circular cuando ofrecen mejor puntuación.
- Corregida la pérdida del historial personal al pulsar Historial inmediatamente después de recargar la aplicación. - Evitadas escrituras y eliminaciones antes de completar la carga de localStorage. - Oscurecido el amarillo de la línea 40 para mejorar su visibilidad sobre el mapa. - Actualizada la versión de la aplicación a v-20260713a.
This commit is contained in:
@@ -52,7 +52,7 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
|
||||
try { await JS.InvokeVoidAsync("rt.blockLeafletDoubleClick"); } catch (Exception ex) { Console.WriteLine("rt.blockLeafletDoubleClick: " + ex); }
|
||||
try { await JS.InvokeVoidAsync("rt.initDraggablePopups"); } catch (Exception ex) { Console.WriteLine("rt.initDraggablePopups: " + ex); }
|
||||
await CargarConfiguracionLocalAsync();
|
||||
await CargarHistorialLocalAsync();
|
||||
await AsegurarHistorialLocalCargadoAsync();
|
||||
await CargarHistorialCompartidoAsync();
|
||||
await CargarModelosHorariosAsync();
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ public partial class PlanificadorRutas
|
||||
private bool _omitirRegistroHistorialEnSiguienteCalculo = false;
|
||||
private string? _historialCompartidoPendienteEliminarId;
|
||||
private ConfiguracionLocalPlanificador? _configuracionTemporalPreviaHistorial;
|
||||
private Task<bool>? _tareaCargaHistorialLocal;
|
||||
private bool _historialLocalCargado = false;
|
||||
|
||||
private async Task ToggleHistorialPanel()
|
||||
{
|
||||
@@ -35,41 +37,69 @@ public partial class PlanificadorRutas
|
||||
return;
|
||||
}
|
||||
|
||||
if (!await AsegurarHistorialLocalCargadoAsync())
|
||||
{
|
||||
MostrarMensajeHistorial("No he podido cargar el historial personal. Intentalo de nuevo.", esError: true);
|
||||
return;
|
||||
}
|
||||
|
||||
_mostrarHistorialRutas = true;
|
||||
await CargarHistorialCompartidoAsync();
|
||||
StartRefrescoHistorialCompartido();
|
||||
}
|
||||
|
||||
private async Task CargarHistorialLocalAsync()
|
||||
private async Task<bool> AsegurarHistorialLocalCargadoAsync()
|
||||
{
|
||||
if (_historialLocalCargado)
|
||||
return true;
|
||||
|
||||
_tareaCargaHistorialLocal ??= CargarHistorialLocalDesdeNavegadorAsync();
|
||||
var tareaCarga = _tareaCargaHistorialLocal;
|
||||
var cargado = await tareaCarga;
|
||||
|
||||
if (!cargado && ReferenceEquals(_tareaCargaHistorialLocal, tareaCarga))
|
||||
_tareaCargaHistorialLocal = null;
|
||||
|
||||
return cargado;
|
||||
}
|
||||
|
||||
private async Task<bool> CargarHistorialLocalDesdeNavegadorAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = await JS.InvokeAsync<string?>("rt.loadLocalValue", ClaveHistorialLocal);
|
||||
if (string.IsNullOrWhiteSpace(json))
|
||||
return;
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(json))
|
||||
{
|
||||
var items = JsonSerializer.Deserialize<List<RegistroHistorialRuta>>(json);
|
||||
if (items is null)
|
||||
return;
|
||||
return false;
|
||||
|
||||
_historialLocal.Clear();
|
||||
_historialLocal.AddRange(items
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x.Id))
|
||||
.OrderByDescending(x => x.FechaActualizacionUtc)
|
||||
.Take(MaxItemsHistorialLocal));
|
||||
}
|
||||
|
||||
_historialLocalSeleccionadoId = _historialLocal.FirstOrDefault()?.Id ?? string.Empty;
|
||||
_historialLocalCargado = true;
|
||||
return true;
|
||||
}
|
||||
catch (JSException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
catch (JsonException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task PersistirHistorialLocalAsync()
|
||||
{
|
||||
if (!_historialLocalCargado)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var json = JsonSerializer.Serialize(_historialLocal);
|
||||
@@ -157,6 +187,9 @@ public partial class PlanificadorRutas
|
||||
|
||||
private async Task RegistrarEscenarioActualEnHistorialLocalAsync()
|
||||
{
|
||||
if (!await AsegurarHistorialLocalCargadoAsync())
|
||||
return;
|
||||
|
||||
var actual = ConstruirRegistroHistorialActual();
|
||||
if (actual is null)
|
||||
return;
|
||||
@@ -287,6 +320,9 @@ public partial class PlanificadorRutas
|
||||
|
||||
private async Task EliminarHistorialLocalAsync(string id)
|
||||
{
|
||||
if (!await AsegurarHistorialLocalCargadoAsync())
|
||||
return;
|
||||
|
||||
var item = _historialLocal.FirstOrDefault(x => x.Id == id);
|
||||
if (item is null)
|
||||
return;
|
||||
|
||||
@@ -1261,7 +1261,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _elevPopupMinimizado = false;
|
||||
|
||||
|
||||
private const string _versionApp = "(v-20260710a)";
|
||||
private const string _versionApp = "(v-20260713a)";
|
||||
|
||||
|
||||
private double? _paradaElevG;
|
||||
|
||||
@@ -244,6 +244,33 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
Math.Max(16, Math.Max(1, parametros.MaxAlternativasBusPorPar) * 10),
|
||||
parametros);
|
||||
}
|
||||
|
||||
var iniciosCircularesDirectos = BuscarIniciosDirectosTrasCircularConHorario(
|
||||
candidatasBus,
|
||||
candidatasOrigen,
|
||||
instanteSalida,
|
||||
parametros);
|
||||
|
||||
if (iniciosCircularesDirectos.Count > 0)
|
||||
{
|
||||
var porFirma = candidatasBus.ToDictionary(
|
||||
candidata => UtilidadesTransitoDbus.FirmaRutaBus(candidata.RutaBus),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var inicioDirecto in iniciosCircularesDirectos)
|
||||
{
|
||||
var firma = UtilidadesTransitoDbus.FirmaRutaBus(inicioDirecto.RutaBus);
|
||||
if (!porFirma.TryGetValue(firma, out var actual) ||
|
||||
ResultadoBusquedaEsMejor(inicioDirecto, actual))
|
||||
{
|
||||
porFirma[firma] = inicioDirecto;
|
||||
}
|
||||
}
|
||||
|
||||
// Estos recortes ya proceden del pool seleccionado. No se vuelve a podar para que
|
||||
// una familia con muchos itinerarios equivalentes no los elimine otra vez.
|
||||
candidatasBus = porFirma.Values.ToList();
|
||||
}
|
||||
}
|
||||
|
||||
candidatasBus = candidatasBus
|
||||
@@ -2326,6 +2353,156 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
return total;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Conserva una variante que entra directamente en el segundo sentido de una circular cuando
|
||||
/// una parada inmediata permite coger el mismo servicio con mejor score equilibrado.
|
||||
/// </summary>
|
||||
private List<ResultadoBusquedaRutaBusDbus> BuscarIniciosDirectosTrasCircularConHorario(
|
||||
IReadOnlyList<ResultadoBusquedaRutaBusDbus> candidatasBase,
|
||||
IReadOnlyList<ParadaCandidata> candidatasOrigen,
|
||||
DateTime instanteSalida,
|
||||
ParametrosPlanificadorRuta parametros)
|
||||
{
|
||||
if (candidatasBase.Count == 0 || candidatasOrigen.Count == 0)
|
||||
return new();
|
||||
|
||||
var origenesPorCodigo = candidatasOrigen
|
||||
.GroupBy(x => x.Parada.Codigo, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(
|
||||
grupo => grupo.Key,
|
||||
grupo => grupo.OrderBy(x => x.TiempoAPie).ThenBy(x => x.DistanciaAPie).First(),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
var mejores = new Dictionary<string, ResultadoBusquedaRutaBusDbus>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var candidataBase in candidatasBase)
|
||||
{
|
||||
var tramosBase = candidataBase.RutaBus.Tramos;
|
||||
if (tramosBase.Count < 2)
|
||||
continue;
|
||||
|
||||
var primero = tramosBase[0];
|
||||
var segundo = tramosBase[1];
|
||||
if (!primero.EsValido ||
|
||||
!segundo.EsValido ||
|
||||
!EsContinuidadCircular(primero.Variante, segundo.Variante) ||
|
||||
!string.Equals(primero.CodigoParadaFin, segundo.CodigoParadaInicio, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var llegadaPrimeraParada = UtilidadesTransitoDbus.AddSecondsSafe(instanteSalida, candidataBase.TiempoPieInicio);
|
||||
if (!ResolverSegmentoHorario(
|
||||
primero.CodigoParadaInicio,
|
||||
primero.CodigoParadaFin,
|
||||
UtilidadesTransitoDbus.LineaCodigoStr(primero.Variante),
|
||||
primero.Variante.CodigoItinerario,
|
||||
UtilidadesTransitoDbus.NormalizarSentidoCodigo(primero.Variante.Sentido?.Codigo),
|
||||
llegadaPrimeraParada,
|
||||
out _,
|
||||
out var llegadaIntermedia) ||
|
||||
!ResolverSegmentoHorario(
|
||||
segundo.CodigoParadaInicio,
|
||||
segundo.CodigoParadaFin,
|
||||
UtilidadesTransitoDbus.LineaCodigoStr(segundo.Variante),
|
||||
segundo.Variante.CodigoItinerario,
|
||||
UtilidadesTransitoDbus.NormalizarSentidoCodigo(segundo.Variante.Sentido?.Codigo),
|
||||
llegadaIntermedia,
|
||||
out _,
|
||||
out var llegadaOriginalSegundo))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
ResultadoBusquedaRutaBusDbus? mejorRecorte = null;
|
||||
var indiceMaximo = Math.Min(
|
||||
segundo.IndiceFin - 1,
|
||||
segundo.IndiceInicio + VentanaRefinamientoParadaInicio);
|
||||
|
||||
for (var indiceSubida = segundo.IndiceInicio + 1; indiceSubida <= indiceMaximo; indiceSubida++)
|
||||
{
|
||||
var codigoSubida = segundo.Variante.CodigosParadas[indiceSubida];
|
||||
if (!origenesPorCodigo.TryGetValue(codigoSubida, out var origenDirecto) ||
|
||||
origenDirecto.DistanciaAPie > candidataBase.DistanciaPieInicio + MaxExcesoAccesoDirectoCircularMetros)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var llegadaDirectaParada = UtilidadesTransitoDbus.AddSecondsSafe(instanteSalida, origenDirecto.TiempoAPie);
|
||||
if (!ResolverSegmentoHorario(
|
||||
codigoSubida,
|
||||
segundo.CodigoParadaFin,
|
||||
UtilidadesTransitoDbus.LineaCodigoStr(segundo.Variante),
|
||||
segundo.Variante.CodigoItinerario,
|
||||
UtilidadesTransitoDbus.NormalizarSentidoCodigo(segundo.Variante.Sentido?.Codigo),
|
||||
llegadaDirectaParada,
|
||||
out var salidaDirecta,
|
||||
out var llegadaDirectaSegundo) ||
|
||||
llegadaDirectaSegundo > llegadaOriginalSegundo.AddSeconds(ToleranciaMismoBusCircularSegundos))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var rutaDirecta = ClonarRutaBus(candidataBase.RutaBus);
|
||||
rutaDirecta.Tramos.RemoveAt(0);
|
||||
rutaDirecta.Tramos[0].IndiceInicio = indiceSubida;
|
||||
rutaDirecta.NumeroTransbordos = UtilidadesTransitoDbus.ContarTransbordosReales(rutaDirecta.Tramos);
|
||||
rutaDirecta.CodigosParadasTransbordo = ConstruirCodigosTransbordo(rutaDirecta.Tramos);
|
||||
rutaDirecta.DistanciaTotalBus = 0;
|
||||
|
||||
var diferenciaLlegada = (llegadaDirectaSegundo - llegadaOriginalSegundo).TotalSeconds;
|
||||
var duracionEstimada = Math.Max(0, candidataBase.DuracionEstimadaSegundos + diferenciaLlegada);
|
||||
var esperaInicial = Math.Max(0, (salidaDirecta - llegadaDirectaParada).TotalSeconds);
|
||||
var recorte = new ResultadoBusquedaRutaBusDbus
|
||||
{
|
||||
ParadaOrigen = origenDirecto.Parada,
|
||||
ParadaDestino = candidataBase.ParadaDestino,
|
||||
DistanciaPieInicio = origenDirecto.DistanciaAPie,
|
||||
TiempoPieInicio = origenDirecto.TiempoAPie,
|
||||
DistanciaPieFin = candidataBase.DistanciaPieFin,
|
||||
TiempoPieFin = candidataBase.TiempoPieFin,
|
||||
DistanciaPieTransbordos = candidataBase.DistanciaPieTransbordos,
|
||||
TiempoEsperaInicialSegundos = esperaInicial,
|
||||
TiempoEsperaTransbordosSegundos = candidataBase.TiempoEsperaTransbordosSegundos,
|
||||
DuracionEstimadaSegundos = duracionEstimada,
|
||||
PuntuacionCoste = UtilidadesTransitoDbus.CalcularCosteConPenalizacionesRankingSegundos(
|
||||
duracionEstimada,
|
||||
parametros,
|
||||
rutaDirecta.NumeroTransbordos,
|
||||
origenDirecto.DistanciaAPie,
|
||||
candidataBase.DistanciaPieTransbordos,
|
||||
candidataBase.DistanciaPieFin),
|
||||
RutaBus = rutaDirecta
|
||||
};
|
||||
|
||||
if (CalcularScoreBusquedaEquilibrado(recorte, parametros) + 1e-6 >=
|
||||
CalcularScoreBusquedaEquilibrado(candidataBase, parametros))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (mejorRecorte is null ||
|
||||
CalcularScoreBusquedaEquilibrado(recorte, parametros) + 1e-6 <
|
||||
CalcularScoreBusquedaEquilibrado(mejorRecorte, parametros))
|
||||
{
|
||||
mejorRecorte = recorte;
|
||||
}
|
||||
}
|
||||
|
||||
if (mejorRecorte is null)
|
||||
continue;
|
||||
|
||||
var firma = UtilidadesTransitoDbus.FirmaRutaBus(mejorRecorte.RutaBus);
|
||||
if (!mejores.TryGetValue(firma, out var actual) ||
|
||||
CalcularScoreBusquedaEquilibrado(mejorRecorte, parametros) + 1e-6 <
|
||||
CalcularScoreBusquedaEquilibrado(actual, parametros))
|
||||
{
|
||||
mejores[firma] = mejorRecorte;
|
||||
}
|
||||
}
|
||||
|
||||
return mejores.Values.ToList();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evita empezar una circular yendo hacia atras si se puede coger directamente el tramo siguiente.
|
||||
/// </summary>
|
||||
|
||||
@@ -13,6 +13,8 @@ namespace RutasDBUS.Servicios.RedBus;
|
||||
|
||||
public class RedBusServicio : IRedBusServicio
|
||||
{
|
||||
private const string ColorVisualLinea40Argb = "FF9A7B00";
|
||||
|
||||
private readonly IWebHostEnvironment _entorno;
|
||||
private readonly OpcionesRedBus _opciones;
|
||||
private readonly ILogger<RedBusServicio> _logger;
|
||||
@@ -98,7 +100,11 @@ public class RedBusServicio : IRedBusServicio
|
||||
_paradas.AddRange(network.Paradas);
|
||||
foreach (var kvp in network.Trayectos)
|
||||
_trayectos[kvp.Key] = kvp.Value;
|
||||
_lineas.AddRange(network.Lineas);
|
||||
foreach (var linea in network.Lineas)
|
||||
{
|
||||
AjustarColorVisualLinea(linea);
|
||||
_lineas.Add(linea);
|
||||
}
|
||||
|
||||
if (_paradas.Count == 0 || _lineas.Count == 0)
|
||||
{
|
||||
@@ -174,6 +180,12 @@ public class RedBusServicio : IRedBusServicio
|
||||
_gridParadas.Clear();
|
||||
}
|
||||
|
||||
private static void AjustarColorVisualLinea(LineaBus linea)
|
||||
{
|
||||
if (string.Equals(linea.Codigo, "40", StringComparison.OrdinalIgnoreCase))
|
||||
linea.ColorArgb = ColorVisualLinea40Argb;
|
||||
}
|
||||
|
||||
// =========================================================
|
||||
// ZIP
|
||||
// =========================================================
|
||||
|
||||
Reference in New Issue
Block a user