v-20260916a
- restaurar derivaciones - añadido la pedida de anchura de calles y cariiles - correcion paradas bus - desglose del score - historial no cambia la hora - resuarar no cambia modo de historial
This commit is contained in:
@@ -1455,31 +1455,135 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
|
|
||||||
private double CalcularScoreEquilibradoFactor(AlternativaRuta? alternativa)
|
private double CalcularScoreEquilibradoFactor(AlternativaRuta? alternativa)
|
||||||
{
|
{
|
||||||
if (alternativa is null)
|
var desglose = CrearDesgloseScoreEquilibrado(alternativa);
|
||||||
return double.PositiveInfinity;
|
return desglose?.ScoreTotal ?? double.PositiveInfinity;
|
||||||
|
}
|
||||||
|
|
||||||
var distanciaBus = 0.0;
|
private DesgloseScoreEquilibrado? CrearDesgloseScoreEquilibrado(AlternativaRuta? alternativa)
|
||||||
if (!alternativa.EsSoloAPie)
|
{
|
||||||
{
|
if (alternativa is null || (!alternativa.EsSoloAPie && alternativa.AlternativaBus is null))
|
||||||
if (alternativa.AlternativaBus is null)
|
return null;
|
||||||
return double.PositiveInfinity;
|
|
||||||
|
|
||||||
distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
|
var parametros = CrearParametrosPenalizacionActuales();
|
||||||
}
|
var distanciasPie = alternativa.EsSoloAPie
|
||||||
|
? new[] { alternativa.DistanciaSoloAPie }
|
||||||
|
: alternativa.DistanciasPiePorTramoAprox.Count > 0
|
||||||
|
? alternativa.DistanciasPiePorTramoAprox.ToArray()
|
||||||
|
: new[]
|
||||||
|
{
|
||||||
|
alternativa.DistanciaPieInicioAprox,
|
||||||
|
alternativa.DistanciaPieTransbordosAprox,
|
||||||
|
alternativa.DistanciaPieFinAprox
|
||||||
|
};
|
||||||
|
|
||||||
var distanciaAndando = CalcularDistanciaCorregidaAlternativa(alternativa);
|
var tramosAndando = distanciasPie
|
||||||
var factor = double.IsFinite(_factorEquilibradoAndarBus) && _factorEquilibradoAndarBus > 0
|
.Where(distancia => double.IsFinite(distancia) && distancia > 0)
|
||||||
|
.Select((distancia, indice) => new TramoAndandoScore(
|
||||||
|
indice + 1,
|
||||||
|
distancia,
|
||||||
|
UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(distancia, parametros)))
|
||||||
|
.ToList();
|
||||||
|
var distanciaAndandoCorregidaBase = tramosAndando.Sum(x => x.DistanciaCorregidaMetros);
|
||||||
|
var esfuerzoExtraPendiente = _mostrarElevacionesRuta &&
|
||||||
|
double.IsFinite(alternativa.EsfuerzoExtraPendienteMetros)
|
||||||
|
? Math.Max(0, alternativa.EsfuerzoExtraPendienteMetros)
|
||||||
|
: 0;
|
||||||
|
var aportacionAndando = distanciaAndandoCorregidaBase + esfuerzoExtraPendiente;
|
||||||
|
|
||||||
|
var distanciaBus = alternativa.EsSoloAPie
|
||||||
|
? 0
|
||||||
|
: Math.Max(0, alternativa.AlternativaBus!.DistanciaTotalBus);
|
||||||
|
var factorEquilibrado = double.IsFinite(_factorEquilibradoAndarBus) &&
|
||||||
|
_factorEquilibradoAndarBus > 0
|
||||||
? _factorEquilibradoAndarBus
|
? _factorEquilibradoAndarBus
|
||||||
: FactorEquilibradoAndarBusPorDefecto;
|
: FactorEquilibradoAndarBusPorDefecto;
|
||||||
|
var aportacionBus = distanciaBus / factorEquilibrado;
|
||||||
|
|
||||||
|
var penalizacionSegundos = UtilidadesTransitoDbus.CalcularPenalizacionTransbordosRankingSegundos(
|
||||||
|
alternativa.NumeroTransbordosReales,
|
||||||
|
parametros);
|
||||||
var penalizacionTransbordos = UtilidadesTransitoDbus.CalcularPenalizacionTransbordosRankingMetros(
|
var penalizacionTransbordos = UtilidadesTransitoDbus.CalcularPenalizacionTransbordosRankingMetros(
|
||||||
alternativa.NumeroTransbordosReales,
|
alternativa.NumeroTransbordosReales,
|
||||||
CrearParametrosPenalizacionActuales());
|
parametros);
|
||||||
var score = distanciaAndando + (distanciaBus / factor) + penalizacionTransbordos;
|
var pesoTransbordoMinutos = double.IsFinite(_pesoTransbordoRankingMinutos) &&
|
||||||
return double.IsFinite(score) && score >= 0
|
_pesoTransbordoRankingMinutos > 0
|
||||||
? score
|
? _pesoTransbordoRankingMinutos
|
||||||
: double.PositiveInfinity;
|
: 0;
|
||||||
|
var velocidadBusKmH = double.IsFinite(_velocidadBusKmH) && _velocidadBusKmH > 0
|
||||||
|
? _velocidadBusKmH
|
||||||
|
: 16.0;
|
||||||
|
var factorAndando = double.IsFinite(_factorPenalizacionDistanciaPie)
|
||||||
|
? _factorPenalizacionDistanciaPie
|
||||||
|
: 1.8;
|
||||||
|
var baseAndandoMetros = double.IsFinite(_parametroPenalizacionDistanciaPieMetros)
|
||||||
|
? Math.Abs(_parametroPenalizacionDistanciaPieMetros)
|
||||||
|
: 200.0;
|
||||||
|
if (baseAndandoMetros <= 0)
|
||||||
|
baseAndandoMetros = 1.0;
|
||||||
|
|
||||||
|
var score = aportacionAndando + aportacionBus + penalizacionTransbordos;
|
||||||
|
if (!double.IsFinite(score) || score < 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return new DesgloseScoreEquilibrado(
|
||||||
|
tramosAndando,
|
||||||
|
factorAndando,
|
||||||
|
baseAndandoMetros,
|
||||||
|
distanciaAndandoCorregidaBase,
|
||||||
|
esfuerzoExtraPendiente,
|
||||||
|
_mostrarElevacionesRuta,
|
||||||
|
aportacionAndando,
|
||||||
|
distanciaBus,
|
||||||
|
factorEquilibrado,
|
||||||
|
aportacionBus,
|
||||||
|
alternativa.NumeroTransbordosReales,
|
||||||
|
pesoTransbordoMinutos,
|
||||||
|
penalizacionSegundos,
|
||||||
|
velocidadBusKmH,
|
||||||
|
penalizacionTransbordos,
|
||||||
|
score);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AbrirDesgloseScore(AlternativaRuta alternativa)
|
||||||
|
=> _desgloseScoreVisible = CrearDesgloseScoreEquilibrado(alternativa);
|
||||||
|
|
||||||
|
private void CerrarDesgloseScore()
|
||||||
|
=> _desgloseScoreVisible = null;
|
||||||
|
|
||||||
|
private static string FormatearMetrosDesgloseScore(double valor)
|
||||||
|
=> $"{valor.ToString("0.00", CultureInfo.InvariantCulture)} m";
|
||||||
|
|
||||||
|
private static string FormatearNumeroDesgloseScore(double valor)
|
||||||
|
=> valor.ToString("0.##", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
private static string FormatearScoreFinalDesglose(double valor)
|
||||||
|
=> valor.ToString("0", CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
private sealed record TramoAndandoScore(
|
||||||
|
int Numero,
|
||||||
|
double DistanciaRealMetros,
|
||||||
|
double DistanciaCorregidaMetros);
|
||||||
|
|
||||||
|
private sealed record DesgloseScoreEquilibrado(
|
||||||
|
IReadOnlyList<TramoAndandoScore> TramosAndando,
|
||||||
|
double FactorAndando,
|
||||||
|
double BaseAndandoMetros,
|
||||||
|
double DistanciaAndandoCorregidaBaseMetros,
|
||||||
|
double EsfuerzoExtraPendienteMetros,
|
||||||
|
bool PendientesAplicadas,
|
||||||
|
double AportacionAndandoMetros,
|
||||||
|
double DistanciaBusMetros,
|
||||||
|
double FactorEquilibrado,
|
||||||
|
double AportacionBusMetros,
|
||||||
|
int NumeroTransbordos,
|
||||||
|
double PesoTransbordoMinutos,
|
||||||
|
double PenalizacionTransbordosSegundos,
|
||||||
|
double VelocidadBusKmH,
|
||||||
|
double PenalizacionTransbordosMetros,
|
||||||
|
double ScoreTotal);
|
||||||
|
|
||||||
|
private DesgloseScoreEquilibrado? _desgloseScoreVisible;
|
||||||
|
|
||||||
private string FormatearScoreEquilibradoAlternativa(AlternativaRuta? alternativa)
|
private string FormatearScoreEquilibradoAlternativa(AlternativaRuta? alternativa)
|
||||||
{
|
{
|
||||||
var score = CalcularScoreEquilibradoFactor(alternativa);
|
var score = CalcularScoreEquilibradoFactor(alternativa);
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ public partial class PlanificadorRutas
|
|||||||
private RegistroHistorialRuta? _escenarioBaseDerivacionHistorial;
|
private RegistroHistorialRuta? _escenarioBaseDerivacionHistorial;
|
||||||
private bool _mostrarInformacionDerivacionHistorial;
|
private bool _mostrarInformacionDerivacionHistorial;
|
||||||
private int _indicePasoDerivacionSeleccionado = -1;
|
private int _indicePasoDerivacionSeleccionado = -1;
|
||||||
|
private int _indicePasoDerivacionActivo = -1;
|
||||||
|
private bool _cargandoPasoDerivacionHistorial;
|
||||||
private string _mensajeHistorial = string.Empty;
|
private string _mensajeHistorial = string.Empty;
|
||||||
private bool _mensajeHistorialEsError = false;
|
private bool _mensajeHistorialEsError = false;
|
||||||
private bool _mostrarHistorialRutas = false;
|
private bool _mostrarHistorialRutas = false;
|
||||||
@@ -76,7 +78,8 @@ public partial class PlanificadorRutas
|
|||||||
private sealed record PasoDerivacionHistorial(
|
private sealed record PasoDerivacionHistorial(
|
||||||
int Numero,
|
int Numero,
|
||||||
DateTime FechaUtc,
|
DateTime FechaUtc,
|
||||||
RegistroHistorialRuta Escenario);
|
RegistroHistorialRuta Escenario,
|
||||||
|
int? NumeroAnterior);
|
||||||
|
|
||||||
private sealed record CambioDerivacionHistorial(
|
private sealed record CambioDerivacionHistorial(
|
||||||
string Campo,
|
string Campo,
|
||||||
@@ -1084,8 +1087,9 @@ public partial class PlanificadorRutas
|
|||||||
return string.Empty;
|
return string.Empty;
|
||||||
|
|
||||||
var numeroDerivacion = _tipoReferenciaHistorial == TipoReferenciaHistorial.Compartido &&
|
var numeroDerivacion = _tipoReferenciaHistorial == TipoReferenciaHistorial.Compartido &&
|
||||||
_pasosDerivacionHistorial.Count > 0
|
_indicePasoDerivacionActivo >= 0 &&
|
||||||
? $" · Derivación n.º {_pasosDerivacionHistorial.Count}"
|
_indicePasoDerivacionActivo < _pasosDerivacionHistorial.Count
|
||||||
|
? $" · Derivación n.º {_pasosDerivacionHistorial[_indicePasoDerivacionActivo].Numero}"
|
||||||
: string.Empty;
|
: string.Empty;
|
||||||
return $"Ruta actual derivada del Historial {nombre} n.º {indice + 1}{numeroDerivacion}.";
|
return $"Ruta actual derivada del Historial {nombre} n.º {indice + 1}{numeroDerivacion}.";
|
||||||
}
|
}
|
||||||
@@ -1113,7 +1117,9 @@ public partial class PlanificadorRutas
|
|||||||
if (!PuedeMostrarInformacionDerivacionHistorial)
|
if (!PuedeMostrarInformacionDerivacionHistorial)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_indicePasoDerivacionSeleccionado = _pasosDerivacionHistorial.Count - 1;
|
_indicePasoDerivacionSeleccionado = _indicePasoDerivacionActivo >= 0
|
||||||
|
? _indicePasoDerivacionActivo
|
||||||
|
: _pasosDerivacionHistorial.Count - 1;
|
||||||
_mostrarInformacionDerivacionHistorial = true;
|
_mostrarInformacionDerivacionHistorial = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1130,11 +1136,46 @@ public partial class PlanificadorRutas
|
|||||||
_indicePasoDerivacionSeleccionado = indice;
|
_indicePasoDerivacionSeleccionado = indice;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CargarPasoDerivacionHistorialAsync()
|
||||||
|
{
|
||||||
|
if (_cargandoPasoDerivacionHistorial ||
|
||||||
|
_indicePasoDerivacionSeleccionado < 0 ||
|
||||||
|
_indicePasoDerivacionSeleccionado >= _pasosDerivacionHistorial.Count)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var indice = _indicePasoDerivacionSeleccionado;
|
||||||
|
var paso = _pasosDerivacionHistorial[indice];
|
||||||
|
_cargandoPasoDerivacionHistorial = true;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await AplicarRegistroHistorialAsync(ClonarEscenarioHistorial(paso.Escenario));
|
||||||
|
_indicePasoDerivacionActivo = indice;
|
||||||
|
_indicePasoDerivacionSeleccionado = indice;
|
||||||
|
_rutaActualDerivadaDeHistorial = ExisteReferenciaHistorialActual();
|
||||||
|
_mostrarInformacionDerivacionHistorial = false;
|
||||||
|
MostrarMensajeHistorial($"Derivación n.º {paso.Numero} cargada.");
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
MostrarMensajeHistorial(
|
||||||
|
$"No he podido cargar la Derivación n.º {paso.Numero}.",
|
||||||
|
esError: true);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_cargandoPasoDerivacionHistorial = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ReiniciarSeguimientoDerivacionHistorial()
|
private void ReiniciarSeguimientoDerivacionHistorial()
|
||||||
{
|
{
|
||||||
_pasosDerivacionHistorial.Clear();
|
_pasosDerivacionHistorial.Clear();
|
||||||
_escenarioBaseDerivacionHistorial = null;
|
_escenarioBaseDerivacionHistorial = null;
|
||||||
_indicePasoDerivacionSeleccionado = -1;
|
_indicePasoDerivacionSeleccionado = -1;
|
||||||
|
_indicePasoDerivacionActivo = -1;
|
||||||
|
_cargandoPasoDerivacionHistorial = false;
|
||||||
_mostrarInformacionDerivacionHistorial = false;
|
_mostrarInformacionDerivacionHistorial = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1155,9 +1196,11 @@ public partial class PlanificadorRutas
|
|||||||
if (actual is null)
|
if (actual is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var anterior = _pasosDerivacionHistorial.Count > 0
|
var pasoAnterior = _indicePasoDerivacionActivo >= 0 &&
|
||||||
? _pasosDerivacionHistorial[^1].Escenario
|
_indicePasoDerivacionActivo < _pasosDerivacionHistorial.Count
|
||||||
: _escenarioBaseDerivacionHistorial;
|
? _pasosDerivacionHistorial[_indicePasoDerivacionActivo]
|
||||||
|
: null;
|
||||||
|
var anterior = pasoAnterior?.Escenario ?? _escenarioBaseDerivacionHistorial;
|
||||||
if (string.Equals(
|
if (string.Equals(
|
||||||
CalcularClaveEscenario(anterior),
|
CalcularClaveEscenario(anterior),
|
||||||
CalcularClaveEscenario(actual),
|
CalcularClaveEscenario(actual),
|
||||||
@@ -1169,20 +1212,21 @@ public partial class PlanificadorRutas
|
|||||||
_pasosDerivacionHistorial.Add(new PasoDerivacionHistorial(
|
_pasosDerivacionHistorial.Add(new PasoDerivacionHistorial(
|
||||||
_pasosDerivacionHistorial.Count + 1,
|
_pasosDerivacionHistorial.Count + 1,
|
||||||
DateTime.UtcNow,
|
DateTime.UtcNow,
|
||||||
ClonarEscenarioHistorial(actual)));
|
ClonarEscenarioHistorial(actual),
|
||||||
|
pasoAnterior?.Numero));
|
||||||
_indicePasoDerivacionSeleccionado = _pasosDerivacionHistorial.Count - 1;
|
_indicePasoDerivacionSeleccionado = _pasosDerivacionHistorial.Count - 1;
|
||||||
|
_indicePasoDerivacionActivo = _indicePasoDerivacionSeleccionado;
|
||||||
}
|
}
|
||||||
|
|
||||||
private RegistroHistorialRuta? ObtenerEscenarioAnteriorDerivacion(
|
private RegistroHistorialRuta? ObtenerEscenarioAnteriorDerivacion(
|
||||||
PasoDerivacionHistorial paso)
|
PasoDerivacionHistorial paso)
|
||||||
{
|
{
|
||||||
var indice = paso.Numero - 1;
|
if (paso.NumeroAnterior is null)
|
||||||
if (indice <= 0)
|
|
||||||
return _escenarioBaseDerivacionHistorial;
|
return _escenarioBaseDerivacionHistorial;
|
||||||
|
|
||||||
return indice - 1 < _pasosDerivacionHistorial.Count
|
return _pasosDerivacionHistorial
|
||||||
? _pasosDerivacionHistorial[indice - 1].Escenario
|
.FirstOrDefault(item => item.Numero == paso.NumeroAnterior.Value)
|
||||||
: null;
|
?.Escenario;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IReadOnlyList<CambioDerivacionHistorial> ObtenerCambiosDerivacion(
|
private IReadOnlyList<CambioDerivacionHistorial> ObtenerCambiosDerivacion(
|
||||||
@@ -1527,7 +1571,8 @@ public partial class PlanificadorRutas
|
|||||||
var configuracionHistorial = _modoConfiguracionHistorial switch
|
var configuracionHistorial = _modoConfiguracionHistorial switch
|
||||||
{
|
{
|
||||||
ModoConfiguracionHistorial.Guardada => CrearConfiguracionGuardadaHistorial(item),
|
ModoConfiguracionHistorial.Guardada => CrearConfiguracionGuardadaHistorial(item),
|
||||||
ModoConfiguracionHistorial.Predeterminada => CrearConfiguracionPredeterminadaHistorial(),
|
ModoConfiguracionHistorial.Predeterminada =>
|
||||||
|
CrearConfiguracionPredeterminadaParaHistorial(item),
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1609,6 +1654,30 @@ public partial class PlanificadorRutas
|
|||||||
return configuracion;
|
return configuracion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaParaHistorial(
|
||||||
|
RegistroHistorialRuta item)
|
||||||
|
{
|
||||||
|
var predeterminada = CrearConfiguracionPredeterminadaHistorial();
|
||||||
|
var guardada = CrearConfiguracionGuardadaHistorial(item);
|
||||||
|
predeterminada.UsarHoraSimulada = guardada.UsarHoraSimulada;
|
||||||
|
|
||||||
|
if (guardada.UsarHoraSimulada)
|
||||||
|
{
|
||||||
|
predeterminada.FechaSimulada = guardada.FechaSimulada;
|
||||||
|
predeterminada.HoraSimulada = guardada.HoraSimulada;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var ahora = DateTime.Now;
|
||||||
|
predeterminada.FechaSimulada = DateOnly.FromDateTime(ahora)
|
||||||
|
.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
|
||||||
|
predeterminada.HoraSimulada = TimeOnly.FromDateTime(ahora)
|
||||||
|
.ToString("HH:mm:ss", CultureInfo.InvariantCulture);
|
||||||
|
}
|
||||||
|
|
||||||
|
return predeterminada;
|
||||||
|
}
|
||||||
|
|
||||||
private void CapturarConfiguracionTemporalAntesDeHistorial()
|
private void CapturarConfiguracionTemporalAntesDeHistorial()
|
||||||
{
|
{
|
||||||
if (_configuracionTemporalPreviaHistorial is not null)
|
if (_configuracionTemporalPreviaHistorial is not null)
|
||||||
|
|||||||
@@ -652,17 +652,23 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
var fecha = DateOnly.FromDateTime(now);
|
var fecha = DateOnly.FromDateTime(now);
|
||||||
var hora = TimeOnly.FromDateTime(now);
|
var hora = TimeOnly.FromDateTime(now);
|
||||||
|
|
||||||
var deps = Horarios.GetNextDeparturesByCodigo(
|
var horarios = await Task.Run(() =>
|
||||||
paradaDetectada.Codigo,
|
{
|
||||||
fecha,
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
hora,
|
var deps = Horarios.GetNextDeparturesByCodigo(
|
||||||
take: null,
|
paradaDetectada.Codigo,
|
||||||
includePast: true);
|
fecha,
|
||||||
|
hora,
|
||||||
|
take: null,
|
||||||
|
includePast: true);
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
return ConstruirListaHorariosConActual(deps, hora);
|
||||||
|
}, cancellationToken);
|
||||||
|
|
||||||
if (!EsPopupParadaVigente(paradaDetectada, token, cancellationToken))
|
if (!EsPopupParadaVigente(paradaDetectada, token, cancellationToken))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_nextDeps = ConstruirListaHorariosConActual(deps, hora);
|
_nextDeps = horarios;
|
||||||
_scrollHorariosPendiente = true;
|
_scrollHorariosPendiente = true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -686,7 +692,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
token == Volatile.Read(ref _paradaPopupToken) &&
|
token == Volatile.Read(ref _paradaPopupToken) &&
|
||||||
string.Equals(_paradaClicada?.Codigo, parada.Codigo, StringComparison.OrdinalIgnoreCase);
|
string.Equals(_paradaClicada?.Codigo, parada.Codigo, StringComparison.OrdinalIgnoreCase);
|
||||||
|
|
||||||
private async Task CerrarPopupParadaAsync()
|
private Task CerrarPopupParadaAsync()
|
||||||
{
|
{
|
||||||
Interlocked.Increment(ref _paradaPopupToken);
|
Interlocked.Increment(ref _paradaPopupToken);
|
||||||
_ctsParadaPopup?.Cancel();
|
_ctsParadaPopup?.Cancel();
|
||||||
@@ -699,8 +705,14 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
_paradaElevLocal1 = null;
|
_paradaElevLocal1 = null;
|
||||||
_nextDeps.Clear();
|
_nextDeps.Clear();
|
||||||
_scrollHorariosPendiente = false;
|
_scrollHorariosPendiente = false;
|
||||||
try { await JS.InvokeVoidAsync("rt.clearSelectedStopMarker"); } catch { }
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
|
_ = LimpiarMarcadorParadaSeleccionadaAsync();
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LimpiarMarcadorParadaSeleccionadaAsync()
|
||||||
|
{
|
||||||
|
try { await JS.InvokeVoidAsync("rt.clearSelectedStopMarker"); } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1494,7 +1506,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private bool _elevPopupMinimizado = false;
|
private bool _elevPopupMinimizado = false;
|
||||||
|
|
||||||
|
|
||||||
private const string _versionApp = "(v-20260914a)";
|
private const string _versionApp = "(v-20260916a)";
|
||||||
|
|
||||||
|
|
||||||
private double? _paradaElevIdee;
|
private double? _paradaElevIdee;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Net;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using RutasDBUS.Modelos.Planificacion;
|
using RutasDBUS.Modelos.Planificacion;
|
||||||
|
|
||||||
@@ -46,7 +47,7 @@ public partial class PlanificadorRutas
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_inspectorSuperficieTexto = "No disponible";
|
_inspectorSuperficieTexto = FormatearSuperficieNoDisponible(" · ");
|
||||||
_inspectorSuperficieCargando = false;
|
_inspectorSuperficieCargando = false;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
}
|
}
|
||||||
@@ -81,7 +82,7 @@ public partial class PlanificadorRutas
|
|||||||
private static string FormatearSuperficiePuntoInspector(InformacionSueloTramo? informacion)
|
private static string FormatearSuperficiePuntoInspector(InformacionSueloTramo? informacion)
|
||||||
{
|
{
|
||||||
if (informacion is null || !informacion.Disponible || informacion.Segmentos.Count == 0)
|
if (informacion is null || !informacion.Disponible || informacion.Segmentos.Count == 0)
|
||||||
return "No disponible";
|
return FormatearSuperficieNoDisponible(" · ");
|
||||||
|
|
||||||
var superficies = informacion.Segmentos
|
var superficies = informacion.Segmentos
|
||||||
.Select(segmento =>
|
.Select(segmento =>
|
||||||
@@ -89,7 +90,8 @@ public partial class PlanificadorRutas
|
|||||||
var uso = string.IsNullOrWhiteSpace(segmento.Uso)
|
var uso = string.IsNullOrWhiteSpace(segmento.Uso)
|
||||||
? ""
|
? ""
|
||||||
: $" ({segmento.Uso})";
|
: $" ({segmento.Uso})";
|
||||||
return $"{segmento.Superficie}{uso}";
|
var atributos = FormatearAtributosVia(segmento, " · ");
|
||||||
|
return $"{segmento.Superficie}{uso}{atributos}";
|
||||||
})
|
})
|
||||||
.Distinct(StringComparer.OrdinalIgnoreCase)
|
.Distinct(StringComparer.OrdinalIgnoreCase)
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -136,7 +138,7 @@ public partial class PlanificadorRutas
|
|||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
superficie = "No disponible";
|
superficie = FormatearSuperficieNoDisponible("<br>");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version != _versionConsultaSuperficieSegmento)
|
if (version != _versionConsultaSuperficieSegmento)
|
||||||
@@ -159,7 +161,7 @@ public partial class PlanificadorRutas
|
|||||||
private static string FormatearSuperficieSegmento(InformacionSueloTramo? informacion)
|
private static string FormatearSuperficieSegmento(InformacionSueloTramo? informacion)
|
||||||
{
|
{
|
||||||
if (informacion is null || !informacion.Disponible || informacion.Segmentos.Count == 0)
|
if (informacion is null || !informacion.Disponible || informacion.Segmentos.Count == 0)
|
||||||
return "No disponible";
|
return FormatearSuperficieNoDisponible("<br>");
|
||||||
|
|
||||||
return string.Join(
|
return string.Join(
|
||||||
"<br>",
|
"<br>",
|
||||||
@@ -171,7 +173,40 @@ public partial class PlanificadorRutas
|
|||||||
var distancia = segmento.DistanciaMetros < 1000
|
var distancia = segmento.DistanciaMetros < 1000
|
||||||
? segmento.DistanciaMetros.ToString("0.##", CultureInfo.InvariantCulture) + " m"
|
? segmento.DistanciaMetros.ToString("0.##", CultureInfo.InvariantCulture) + " m"
|
||||||
: (segmento.DistanciaMetros / 1000.0).ToString("0.###", CultureInfo.InvariantCulture) + " km";
|
: (segmento.DistanciaMetros / 1000.0).ToString("0.###", CultureInfo.InvariantCulture) + " km";
|
||||||
return $"{segmento.Superficie}{uso}, {distancia}";
|
var atributos = FormatearAtributosVia(segmento, "<br>");
|
||||||
|
return $"{segmento.Superficie}{uso}, {distancia}{atributos}";
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static string FormatearAtributosVia(SegmentoSueloTramo segmento, string separador)
|
||||||
|
{
|
||||||
|
var atributos = new List<string>(2);
|
||||||
|
var carriles = segmento.NumeroCarriles is > 0
|
||||||
|
? segmento.NumeroCarriles.Value.ToString(CultureInfo.InvariantCulture)
|
||||||
|
: "-";
|
||||||
|
atributos.Add($"Carriles: {carriles}");
|
||||||
|
if (!string.IsNullOrWhiteSpace(segmento.AnchuraVia))
|
||||||
|
{
|
||||||
|
var etiqueta = segmento.AnchuraViaEstimada ? "Anchura estimada" : "Anchura";
|
||||||
|
atributos.Add($"{etiqueta}: {FormatearAnchuraVia(segmento.AnchuraVia)}");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
atributos.Add("Anchura: -");
|
||||||
|
}
|
||||||
|
|
||||||
|
return separador + string.Join(" · ", atributos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatearSuperficieNoDisponible(string separador)
|
||||||
|
=> "No disponible" + FormatearAtributosVia(new SegmentoSueloTramo(), separador);
|
||||||
|
|
||||||
|
private static string FormatearAnchuraVia(string anchura)
|
||||||
|
{
|
||||||
|
var valor = anchura.Trim();
|
||||||
|
var texto = double.TryParse(valor, NumberStyles.Float, CultureInfo.InvariantCulture, out _)
|
||||||
|
? $"{valor} m"
|
||||||
|
: valor;
|
||||||
|
return WebUtility.HtmlEncode(texto);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ public partial class PlanificadorRutas
|
|||||||
predeterminada.CriterioPrioritarioRuta = actual.CriterioPrioritarioRuta;
|
predeterminada.CriterioPrioritarioRuta = actual.CriterioPrioritarioRuta;
|
||||||
predeterminada.PreferenciaEtiquetaRuta = actual.PreferenciaEtiquetaRuta;
|
predeterminada.PreferenciaEtiquetaRuta = actual.PreferenciaEtiquetaRuta;
|
||||||
AplicarConfiguracionLocal(predeterminada);
|
AplicarConfiguracionLocal(predeterminada);
|
||||||
_modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[JSInvokable]
|
[JSInvokable]
|
||||||
|
|||||||
@@ -48,7 +48,6 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<select class="config-input header-center-input"
|
<select class="config-input header-center-input"
|
||||||
@key="_selectItKey"
|
@key="_selectItKey"
|
||||||
@bind="_itSeleccionadoId">
|
@bind="_itSeleccionadoId">
|
||||||
@@ -1075,7 +1074,14 @@
|
|||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(scoreEquilibradoSeleccionado))
|
@if (!string.IsNullOrWhiteSpace(scoreEquilibradoSeleccionado))
|
||||||
{
|
{
|
||||||
<div>Score equilibrado A: @scoreEquilibradoSeleccionado</div>
|
<div class="route-score-detail-line">
|
||||||
|
<span>Score equilibrado A: @scoreEquilibradoSeleccionado</span>
|
||||||
|
<button type="button"
|
||||||
|
class="route-score-info"
|
||||||
|
title="Ver desglose del score"
|
||||||
|
aria-label="Ver desglose del score equilibrado A"
|
||||||
|
@onclick="() => AbrirDesgloseScore(altSeleccionada)">i</button>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(xyzOrigen))
|
@if (!string.IsNullOrWhiteSpace(xyzOrigen))
|
||||||
{
|
{
|
||||||
@@ -1381,9 +1387,9 @@
|
|||||||
var cambiosRespectoAnterior = ObtenerCambiosDerivacion(
|
var cambiosRespectoAnterior = ObtenerCambiosDerivacion(
|
||||||
escenarioAnterior,
|
escenarioAnterior,
|
||||||
pasoDerivacion.Escenario);
|
pasoDerivacion.Escenario);
|
||||||
var textoComparacionAnterior = pasoDerivacion.Numero <= 1
|
var textoComparacionAnterior = pasoDerivacion.NumeroAnterior is null
|
||||||
? "Respecto al Historial Compartido original"
|
? "Respecto al Historial Compartido original"
|
||||||
: $"Respecto a la Derivación n.º {pasoDerivacion.Numero - 1}";
|
: $"Respecto a la Derivación n.º {pasoDerivacion.NumeroAnterior}";
|
||||||
|
|
||||||
<div class="custom-modal-backdrop history-derivation-backdrop"
|
<div class="custom-modal-backdrop history-derivation-backdrop"
|
||||||
@onclick="CerrarInformacionDerivacionHistorial">
|
@onclick="CerrarInformacionDerivacionHistorial">
|
||||||
@@ -1410,7 +1416,8 @@
|
|||||||
{
|
{
|
||||||
var indicePaso = paso.Numero - 1;
|
var indicePaso = paso.Numero - 1;
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="history-derivation-step @(indicePaso == _indicePasoDerivacionSeleccionado ? "history-derivation-step--selected" : "")"
|
class="history-derivation-step @(indicePaso == _indicePasoDerivacionSeleccionado ? "history-derivation-step--selected" : "") @(indicePaso == _indicePasoDerivacionActivo ? "history-derivation-step--active" : "")"
|
||||||
|
title="@(indicePaso == _indicePasoDerivacionActivo ? "Derivación cargada actualmente" : $"Ver Derivación n.º {paso.Numero}")"
|
||||||
@onclick="() => SeleccionarPasoDerivacionHistorial(indicePaso)">
|
@onclick="() => SeleccionarPasoDerivacionHistorial(indicePaso)">
|
||||||
Der. @paso.Numero
|
Der. @paso.Numero
|
||||||
</button>
|
</button>
|
||||||
@@ -1418,8 +1425,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="history-derivation-selected">
|
<div class="history-derivation-selected">
|
||||||
<strong>Derivación n.º @pasoDerivacion.Numero</strong>
|
<div class="history-derivation-selected__summary">
|
||||||
<span>@pasoDerivacion.FechaUtc.ToLocalTime().ToString("dd/MM/yyyy HH:mm:ss")</span>
|
<strong>Derivación n.º @pasoDerivacion.Numero</strong>
|
||||||
|
<span>@pasoDerivacion.FechaUtc.ToLocalTime().ToString("dd/MM/yyyy HH:mm:ss")</span>
|
||||||
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
class="btn-ghost history-derivation-load"
|
||||||
|
disabled="@(_cargandoPasoDerivacionHistorial || _indicePasoDerivacionSeleccionado == _indicePasoDerivacionActivo)"
|
||||||
|
@onclick="CargarPasoDerivacionHistorialAsync">
|
||||||
|
@(_cargandoPasoDerivacionHistorial
|
||||||
|
? "Cargando..."
|
||||||
|
: _indicePasoDerivacionSeleccionado == _indicePasoDerivacionActivo
|
||||||
|
? "Cargada"
|
||||||
|
: "Cargar derivación")
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="history-derivation-comparison">
|
<section class="history-derivation-comparison">
|
||||||
@@ -1453,7 +1472,7 @@
|
|||||||
}
|
}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
@if (pasoDerivacion.Numero > 1)
|
@if (pasoDerivacion.NumeroAnterior is not null)
|
||||||
{
|
{
|
||||||
<section class="history-derivation-comparison">
|
<section class="history-derivation-comparison">
|
||||||
<h6>@textoComparacionAnterior</h6>
|
<h6>@textoComparacionAnterior</h6>
|
||||||
@@ -2256,6 +2275,99 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if (_desgloseScoreVisible is { } desgloseScore)
|
||||||
|
{
|
||||||
|
<div class="custom-modal-backdrop score-breakdown-backdrop" @onclick="CerrarDesgloseScore">
|
||||||
|
<div class="custom-modal-card score-breakdown-modal"
|
||||||
|
@onclick:stopPropagation="true"
|
||||||
|
role="dialog"
|
||||||
|
aria-modal="true"
|
||||||
|
aria-label="Desglose del score equilibrado A">
|
||||||
|
<div class="score-breakdown-header">
|
||||||
|
<div>
|
||||||
|
<div class="custom-modal-title">Desglose del score equilibrado A</div>
|
||||||
|
<div class="score-breakdown-subtitle">
|
||||||
|
Andando corregido + BUS ponderado + penalización por transbordos
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button type="button"
|
||||||
|
class="score-breakdown-close"
|
||||||
|
title="Cerrar"
|
||||||
|
aria-label="Cerrar desglose"
|
||||||
|
@onclick="CerrarDesgloseScore">×</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="score-breakdown-formula">
|
||||||
|
Score = @FormatearMetrosDesgloseScore(desgloseScore.AportacionAndandoMetros)
|
||||||
|
+ @FormatearMetrosDesgloseScore(desgloseScore.AportacionBusMetros)
|
||||||
|
+ @FormatearMetrosDesgloseScore(desgloseScore.PenalizacionTransbordosMetros)
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section class="score-breakdown-section">
|
||||||
|
<h3>Distancia andando corregida</h3>
|
||||||
|
<div class="score-breakdown-note">
|
||||||
|
Por tramo: máx(distancia, (distancia / @FormatearNumeroDesgloseScore(desgloseScore.BaseAndandoMetros))<sup>@FormatearNumeroDesgloseScore(desgloseScore.FactorAndando)</sup> × @FormatearNumeroDesgloseScore(desgloseScore.BaseAndandoMetros))
|
||||||
|
</div>
|
||||||
|
@if (desgloseScore.TramosAndando.Count == 0)
|
||||||
|
{
|
||||||
|
<div class="score-breakdown-row"><span>Sin tramos andando</span><strong>0.00 m</strong></div>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
@foreach (var tramo in desgloseScore.TramosAndando)
|
||||||
|
{
|
||||||
|
<div class="score-breakdown-row">
|
||||||
|
<span>Tramo @tramo.Numero: @FormatearMetrosDesgloseScore(tramo.DistanciaRealMetros)</span>
|
||||||
|
<strong>@FormatearMetrosDesgloseScore(tramo.DistanciaCorregidaMetros)</strong>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<div class="score-breakdown-row score-breakdown-row--subtotal">
|
||||||
|
<span>Corrección base</span>
|
||||||
|
<strong>@FormatearMetrosDesgloseScore(desgloseScore.DistanciaAndandoCorregidaBaseMetros)</strong>
|
||||||
|
</div>
|
||||||
|
<div class="score-breakdown-row">
|
||||||
|
<span>Esfuerzo extra por pendientes @(!desgloseScore.PendientesAplicadas ? "(no aplicado)" : "")</span>
|
||||||
|
<strong>+ @FormatearMetrosDesgloseScore(desgloseScore.EsfuerzoExtraPendienteMetros)</strong>
|
||||||
|
</div>
|
||||||
|
<div class="score-breakdown-row score-breakdown-row--total">
|
||||||
|
<span>Aportación andando</span>
|
||||||
|
<strong>@FormatearMetrosDesgloseScore(desgloseScore.AportacionAndandoMetros)</strong>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="score-breakdown-section">
|
||||||
|
<h3>Distancia en BUS</h3>
|
||||||
|
<div class="score-breakdown-row">
|
||||||
|
<span>@FormatearMetrosDesgloseScore(desgloseScore.DistanciaBusMetros) ÷ @FormatearNumeroDesgloseScore(desgloseScore.FactorEquilibrado)</span>
|
||||||
|
<strong>@FormatearMetrosDesgloseScore(desgloseScore.AportacionBusMetros)</strong>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="score-breakdown-section">
|
||||||
|
<h3>Penalización por transbordos</h3>
|
||||||
|
<div class="score-breakdown-row">
|
||||||
|
<span>@desgloseScore.NumeroTransbordos transb. × @FormatearNumeroDesgloseScore(desgloseScore.PesoTransbordoMinutos) min</span>
|
||||||
|
<strong>@FormatearNumeroDesgloseScore(desgloseScore.PenalizacionTransbordosSegundos) s</strong>
|
||||||
|
</div>
|
||||||
|
<div class="score-breakdown-note">
|
||||||
|
Conversión: @FormatearNumeroDesgloseScore(desgloseScore.PenalizacionTransbordosSegundos) s
|
||||||
|
× (@FormatearNumeroDesgloseScore(desgloseScore.VelocidadBusKmH) km/h × 1000 / 3600)
|
||||||
|
</div>
|
||||||
|
<div class="score-breakdown-row score-breakdown-row--total">
|
||||||
|
<span>Aportación transbordos</span>
|
||||||
|
<strong>@FormatearMetrosDesgloseScore(desgloseScore.PenalizacionTransbordosMetros)</strong>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="score-breakdown-result">
|
||||||
|
<span>Score final</span>
|
||||||
|
<strong>@FormatearScoreFinalDesglose(desgloseScore.ScoreTotal)</strong>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@if (_mostrarConfirmacionRestablecer)
|
@if (_mostrarConfirmacionRestablecer)
|
||||||
{
|
{
|
||||||
<div class="custom-modal-backdrop" @onclick="CancelarConfirmacionRestablecer">
|
<div class="custom-modal-backdrop" @onclick="CancelarConfirmacionRestablecer">
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ public sealed class SegmentoSueloTramo
|
|||||||
public double DistanciaMetros { get; init; }
|
public double DistanciaMetros { get; init; }
|
||||||
public string Superficie { get; init; } = "Sin datos";
|
public string Superficie { get; init; } = "Sin datos";
|
||||||
public string? Uso { get; init; }
|
public string? Uso { get; init; }
|
||||||
|
public int? NumeroCarriles { get; init; }
|
||||||
|
public string? AnchuraVia { get; init; }
|
||||||
|
public bool AnchuraViaEstimada { get; init; }
|
||||||
public bool? SinPavimentar { get; init; }
|
public bool? SinPavimentar { get; init; }
|
||||||
public long? IdCamino { get; init; }
|
public long? IdCamino { get; init; }
|
||||||
public int? IndiceInicioForma { get; init; }
|
public int? IndiceInicioForma { get; init; }
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ builder.Services.AddSingleton<ServicioIndiceTransitoDbus>();
|
|||||||
builder.Services.AddSingleton<BuscadorRutasTemporalesDbus>();
|
builder.Services.AddSingleton<BuscadorRutasTemporalesDbus>();
|
||||||
builder.Services.AddSingleton<IPlanificadorRutas, PlanificadorRutasServicio>();
|
builder.Services.AddSingleton<IPlanificadorRutas, PlanificadorRutasServicio>();
|
||||||
builder.Services.AddSingleton<RutasDBUS.Servicios.Configuracion.ServicioValoresFabrica>();
|
builder.Services.AddSingleton<RutasDBUS.Servicios.Configuracion.ServicioValoresFabrica>();
|
||||||
|
builder.Services.AddMemoryCache();
|
||||||
|
|
||||||
// HttpClient genérico (lo usas en el Razor: @inject HttpClient Http)
|
// HttpClient genérico (lo usas en el Razor: @inject HttpClient Http)
|
||||||
builder.Services.AddHttpClient();
|
builder.Services.AddHttpClient();
|
||||||
@@ -59,6 +60,7 @@ builder.Services.AddHttpClient();
|
|||||||
// Clientes HTTP tipados
|
// Clientes HTTP tipados
|
||||||
builder.Services.AddHttpClient<IClienteOsrm, ClienteOsrm>();
|
builder.Services.AddHttpClient<IClienteOsrm, ClienteOsrm>();
|
||||||
builder.Services.AddHttpClient<IClienteValhalla, ClienteValhalla>();
|
builder.Services.AddHttpClient<IClienteValhalla, ClienteValhalla>();
|
||||||
|
builder.Services.AddHttpClient<IClienteAtributosOsm, ClienteAtributosOsm>();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
|||||||
136
RutasDBUS/Servicios/Caminata/ClienteAtributosOsm.cs
Normal file
136
RutasDBUS/Servicios/Caminata/ClienteAtributosOsm.cs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
using System.Net.Http.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
|
|
||||||
|
namespace RutasDBUS.Servicios.Caminata;
|
||||||
|
|
||||||
|
public sealed class ClienteAtributosOsm : IClienteAtributosOsm
|
||||||
|
{
|
||||||
|
private const int MaximoCaminosPorConsulta = 100;
|
||||||
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly IMemoryCache _cache;
|
||||||
|
private readonly string _baseUrl;
|
||||||
|
|
||||||
|
public ClienteAtributosOsm(
|
||||||
|
HttpClient httpClient,
|
||||||
|
IMemoryCache cache,
|
||||||
|
IConfiguration configuracion)
|
||||||
|
{
|
||||||
|
_httpClient = httpClient;
|
||||||
|
_cache = cache;
|
||||||
|
_baseUrl = (configuracion["Caminata:Osm:BaseUrl"]
|
||||||
|
?? "https://api.openstreetmap.org/api/0.6").Trim().TrimEnd('/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyDictionary<long, AnchuraViaOsm>> ObtenerAnchurasAsync(
|
||||||
|
IEnumerable<long> idsCamino,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
var ids = idsCamino
|
||||||
|
.Where(id => id > 0)
|
||||||
|
.Distinct()
|
||||||
|
.Take(MaximoCaminosPorConsulta)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var resultado = new Dictionary<long, AnchuraViaOsm>();
|
||||||
|
var pendientes = new List<long>();
|
||||||
|
foreach (var id in ids)
|
||||||
|
{
|
||||||
|
if (_cache.TryGetValue<ResultadoAnchuraCache>(ClaveCache(id), out var cacheado))
|
||||||
|
{
|
||||||
|
if (cacheado?.Dato is not null)
|
||||||
|
resultado[id] = cacheado.Dato;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
pendientes.Add(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendientes.Count == 0)
|
||||||
|
return resultado;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||||
|
timeout.CancelAfter(TimeSpan.FromSeconds(4));
|
||||||
|
|
||||||
|
var idsConsulta = string.Join(',', pendientes);
|
||||||
|
using var peticion = new HttpRequestMessage(
|
||||||
|
HttpMethod.Get,
|
||||||
|
$"{_baseUrl}/ways.json?ways={Uri.EscapeDataString(idsConsulta)}");
|
||||||
|
peticion.Headers.UserAgent.ParseAdd("RutasDBUS/1.0");
|
||||||
|
|
||||||
|
using var respuesta = await _httpClient.SendAsync(peticion, timeout.Token);
|
||||||
|
if (!respuesta.IsSuccessStatusCode)
|
||||||
|
return resultado;
|
||||||
|
|
||||||
|
var datos = await respuesta.Content.ReadFromJsonAsync<RespuestaOsm>(timeout.Token);
|
||||||
|
var encontrados = new HashSet<long>();
|
||||||
|
foreach (var elemento in datos?.Elementos ?? [])
|
||||||
|
{
|
||||||
|
encontrados.Add(elemento.Id);
|
||||||
|
var anchura = ObtenerAnchura(elemento.Etiquetas);
|
||||||
|
GuardarCache(elemento.Id, anchura);
|
||||||
|
if (anchura is not null)
|
||||||
|
resultado[elemento.Id] = anchura;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var id in pendientes.Where(id => !encontrados.Contains(id)))
|
||||||
|
GuardarCache(id, null);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// La anchura es informativa: un timeout no debe bloquear el tooltip.
|
||||||
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
// La información principal de v2 sigue siendo válida si OSM no responde.
|
||||||
|
}
|
||||||
|
catch (System.Text.Json.JsonException)
|
||||||
|
{
|
||||||
|
// Una respuesta no válida tampoco debe ocultar la información principal de v2.
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultado;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void GuardarCache(long id, AnchuraViaOsm? dato)
|
||||||
|
=> _cache.Set(
|
||||||
|
ClaveCache(id),
|
||||||
|
new ResultadoAnchuraCache(dato),
|
||||||
|
dato is null ? TimeSpan.FromHours(6) : TimeSpan.FromDays(1));
|
||||||
|
|
||||||
|
private static AnchuraViaOsm? ObtenerAnchura(Dictionary<string, string>? etiquetas)
|
||||||
|
{
|
||||||
|
if (etiquetas is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (etiquetas.TryGetValue("width", out var anchura) && !string.IsNullOrWhiteSpace(anchura))
|
||||||
|
return new AnchuraViaOsm(anchura.Trim(), false);
|
||||||
|
|
||||||
|
if (etiquetas.TryGetValue("est_width", out var estimada) && !string.IsNullOrWhiteSpace(estimada))
|
||||||
|
return new AnchuraViaOsm(estimada.Trim(), true);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ClaveCache(long id) => $"osm:anchura:{id}";
|
||||||
|
|
||||||
|
private sealed record ResultadoAnchuraCache(AnchuraViaOsm? Dato);
|
||||||
|
|
||||||
|
private sealed class RespuestaOsm
|
||||||
|
{
|
||||||
|
[JsonPropertyName("elements")]
|
||||||
|
public List<ElementoOsm>? Elementos { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ElementoOsm
|
||||||
|
{
|
||||||
|
[JsonPropertyName("id")]
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("tags")]
|
||||||
|
public Dictionary<string, string>? Etiquetas { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,14 +19,19 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
private const double PasoComprobacionExclusionMetros = 2.0;
|
private const double PasoComprobacionExclusionMetros = 2.0;
|
||||||
private const double ToleranciaBordeExclusionMetros = 1.5;
|
private const double ToleranciaBordeExclusionMetros = 1.5;
|
||||||
private readonly HttpClient _httpClient;
|
private readonly HttpClient _httpClient;
|
||||||
|
private readonly IClienteAtributosOsm? _clienteAtributosOsm;
|
||||||
private readonly string _baseUrl;
|
private readonly string _baseUrl;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Inicializa una nueva instancia de la clase ClienteValhalla.
|
/// Inicializa una nueva instancia de la clase ClienteValhalla.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ClienteValhalla(HttpClient httpClient, IConfiguration configuracion)
|
public ClienteValhalla(
|
||||||
|
HttpClient httpClient,
|
||||||
|
IConfiguration configuracion,
|
||||||
|
IClienteAtributosOsm? clienteAtributosOsm = null)
|
||||||
{
|
{
|
||||||
_httpClient = httpClient;
|
_httpClient = httpClient;
|
||||||
|
_clienteAtributosOsm = clienteAtributosOsm;
|
||||||
|
|
||||||
_baseUrl = configuracion["Caminata:Valhalla:BaseUrl"]
|
_baseUrl = configuracion["Caminata:Valhalla:BaseUrl"]
|
||||||
?? throw new InvalidOperationException(
|
?? throw new InvalidOperationException(
|
||||||
@@ -501,6 +506,7 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
"edge.surface",
|
"edge.surface",
|
||||||
"edge.unpaved",
|
"edge.unpaved",
|
||||||
"edge.use",
|
"edge.use",
|
||||||
|
"edge.lane_count",
|
||||||
"edge.way_id",
|
"edge.way_id",
|
||||||
"edge.begin_shape_index",
|
"edge.begin_shape_index",
|
||||||
"edge.end_shape_index"
|
"edge.end_shape_index"
|
||||||
@@ -527,7 +533,32 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
return CrearInformacionSueloNoDisponible("El servicio de superficies no ha respondido correctamente.");
|
return CrearInformacionSueloNoDisponible("El servicio de superficies no ha respondido correctamente.");
|
||||||
|
|
||||||
var data = await respuesta.Content.ReadFromJsonAsync<RespuestaAtributosSuperficieValhalla>(timeout.Token);
|
var data = await respuesta.Content.ReadFromJsonAsync<RespuestaAtributosSuperficieValhalla>(timeout.Token);
|
||||||
return ConvertirInformacionSuelo(data);
|
IReadOnlyDictionary<long, AnchuraViaOsm>? anchuras = null;
|
||||||
|
if (_clienteAtributosOsm is not null && data?.Aristas is not null)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
anchuras = await _clienteAtributosOsm.ObtenerAnchurasAsync(
|
||||||
|
data.Aristas
|
||||||
|
.Where(arista => arista.IdCamino is > 0)
|
||||||
|
.Select(arista => arista.IdCamino!.Value),
|
||||||
|
cancellationToken);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
|
||||||
|
{
|
||||||
|
// La anchura es opcional y no debe ocultar los datos de superficie de v2.
|
||||||
|
}
|
||||||
|
catch (HttpRequestException)
|
||||||
|
{
|
||||||
|
// La anchura es opcional y no debe ocultar los datos de superficie de v2.
|
||||||
|
}
|
||||||
|
catch (JsonException)
|
||||||
|
{
|
||||||
|
// La anchura es opcional y no debe ocultar los datos de superficie de v2.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConvertirInformacionSuelo(data, anchuras);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -559,7 +590,9 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
return forma;
|
return forma;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InformacionSueloTramo ConvertirInformacionSuelo(RespuestaAtributosSuperficieValhalla? data)
|
private static InformacionSueloTramo ConvertirInformacionSuelo(
|
||||||
|
RespuestaAtributosSuperficieValhalla? data,
|
||||||
|
IReadOnlyDictionary<long, AnchuraViaOsm>? anchuras = null)
|
||||||
{
|
{
|
||||||
if (data?.Aristas is null || data.Aristas.Count == 0)
|
if (data?.Aristas is null || data.Aristas.Count == 0)
|
||||||
return CrearInformacionSueloNoDisponible("No hay datos de superficie para este tramo.");
|
return CrearInformacionSueloNoDisponible("No hay datos de superficie para este tramo.");
|
||||||
@@ -573,8 +606,17 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
|
|
||||||
var superficie = arista.Superficie ?? "No disponible";
|
var superficie = arista.Superficie ?? "No disponible";
|
||||||
var uso = arista.Uso;
|
var uso = arista.Uso;
|
||||||
|
AnchuraViaOsm? anchura = null;
|
||||||
|
if (arista.IdCamino is long idCamino && anchuras is not null)
|
||||||
|
anchuras.TryGetValue(idCamino, out anchura);
|
||||||
|
|
||||||
if (segmentos.Count > 0 && EsMismaSuperficie(segmentos[^1], superficie, uso, arista.SinPavimentar))
|
if (segmentos.Count > 0 && EsMismaSuperficie(
|
||||||
|
segmentos[^1],
|
||||||
|
superficie,
|
||||||
|
uso,
|
||||||
|
arista.NumeroCarriles,
|
||||||
|
anchura,
|
||||||
|
arista.SinPavimentar))
|
||||||
{
|
{
|
||||||
var anterior = segmentos[^1];
|
var anterior = segmentos[^1];
|
||||||
segmentos[^1] = new SegmentoSueloTramo
|
segmentos[^1] = new SegmentoSueloTramo
|
||||||
@@ -582,6 +624,9 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
DistanciaMetros = anterior.DistanciaMetros + distanciaMetros,
|
DistanciaMetros = anterior.DistanciaMetros + distanciaMetros,
|
||||||
Superficie = anterior.Superficie,
|
Superficie = anterior.Superficie,
|
||||||
Uso = anterior.Uso,
|
Uso = anterior.Uso,
|
||||||
|
NumeroCarriles = anterior.NumeroCarriles,
|
||||||
|
AnchuraVia = anterior.AnchuraVia,
|
||||||
|
AnchuraViaEstimada = anterior.AnchuraViaEstimada,
|
||||||
SinPavimentar = anterior.SinPavimentar,
|
SinPavimentar = anterior.SinPavimentar,
|
||||||
IdCamino = anterior.IdCamino == arista.IdCamino ? anterior.IdCamino : null,
|
IdCamino = anterior.IdCamino == arista.IdCamino ? anterior.IdCamino : null,
|
||||||
IndiceInicioForma = anterior.IndiceInicioForma,
|
IndiceInicioForma = anterior.IndiceInicioForma,
|
||||||
@@ -595,6 +640,9 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
DistanciaMetros = distanciaMetros,
|
DistanciaMetros = distanciaMetros,
|
||||||
Superficie = superficie,
|
Superficie = superficie,
|
||||||
Uso = uso,
|
Uso = uso,
|
||||||
|
NumeroCarriles = arista.NumeroCarriles,
|
||||||
|
AnchuraVia = anchura?.Valor,
|
||||||
|
AnchuraViaEstimada = anchura?.EsEstimada ?? false,
|
||||||
SinPavimentar = arista.SinPavimentar,
|
SinPavimentar = arista.SinPavimentar,
|
||||||
IdCamino = arista.IdCamino,
|
IdCamino = arista.IdCamino,
|
||||||
IndiceInicioForma = arista.IndiceInicioForma,
|
IndiceInicioForma = arista.IndiceInicioForma,
|
||||||
@@ -617,9 +665,14 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
SegmentoSueloTramo anterior,
|
SegmentoSueloTramo anterior,
|
||||||
string superficie,
|
string superficie,
|
||||||
string? uso,
|
string? uso,
|
||||||
|
int? numeroCarriles,
|
||||||
|
AnchuraViaOsm? anchura,
|
||||||
bool? sinPavimentar)
|
bool? sinPavimentar)
|
||||||
=> string.Equals(anterior.Superficie, superficie, StringComparison.Ordinal)
|
=> string.Equals(anterior.Superficie, superficie, StringComparison.Ordinal)
|
||||||
&& string.Equals(anterior.Uso, uso, StringComparison.Ordinal)
|
&& string.Equals(anterior.Uso, uso, StringComparison.Ordinal)
|
||||||
|
&& anterior.NumeroCarriles == numeroCarriles
|
||||||
|
&& string.Equals(anterior.AnchuraVia, anchura?.Valor, StringComparison.Ordinal)
|
||||||
|
&& anterior.AnchuraViaEstimada == (anchura?.EsEstimada ?? false)
|
||||||
&& anterior.SinPavimentar == sinPavimentar;
|
&& anterior.SinPavimentar == sinPavimentar;
|
||||||
|
|
||||||
private static double ConvertirLongitudMetros(double? longitud, string? unidades)
|
private static double ConvertirLongitudMetros(double? longitud, string? unidades)
|
||||||
@@ -647,21 +700,6 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
_ => "Sin datos"
|
_ => "Sin datos"
|
||||||
};
|
};
|
||||||
|
|
||||||
private static string? EtiquetaUso(string? uso)
|
|
||||||
=> (uso ?? "").Trim().ToLowerInvariant() switch
|
|
||||||
{
|
|
||||||
"sidewalk" => "Acera",
|
|
||||||
"footway" => "Camino peatonal",
|
|
||||||
"steps" => "Escaleras",
|
|
||||||
"path" => "Senda",
|
|
||||||
"track" => "Pista",
|
|
||||||
"road" => "Calzada",
|
|
||||||
"service_other" => "Vía de servicio",
|
|
||||||
"other" => "Otro",
|
|
||||||
"" => null,
|
|
||||||
var valor => valor
|
|
||||||
};
|
|
||||||
|
|
||||||
private static InformacionSueloTramo CrearInformacionSueloNoDisponible(string mensaje)
|
private static InformacionSueloTramo CrearInformacionSueloNoDisponible(string mensaje)
|
||||||
=> new()
|
=> new()
|
||||||
{
|
{
|
||||||
@@ -754,6 +792,9 @@ public class ClienteValhalla : IClienteValhalla
|
|||||||
[JsonPropertyName("use")]
|
[JsonPropertyName("use")]
|
||||||
public string? Uso { get; set; }
|
public string? Uso { get; set; }
|
||||||
|
|
||||||
|
[JsonPropertyName("lane_count")]
|
||||||
|
public int? NumeroCarriles { get; set; }
|
||||||
|
|
||||||
[JsonPropertyName("way_id")]
|
[JsonPropertyName("way_id")]
|
||||||
public long? IdCamino { get; set; }
|
public long? IdCamino { get; set; }
|
||||||
|
|
||||||
|
|||||||
10
RutasDBUS/Servicios/Caminata/IClienteAtributosOsm.cs
Normal file
10
RutasDBUS/Servicios/Caminata/IClienteAtributosOsm.cs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
namespace RutasDBUS.Servicios.Caminata;
|
||||||
|
|
||||||
|
public sealed record AnchuraViaOsm(string Valor, bool EsEstimada);
|
||||||
|
|
||||||
|
public interface IClienteAtributosOsm
|
||||||
|
{
|
||||||
|
Task<IReadOnlyDictionary<long, AnchuraViaOsm>> ObtenerAnchurasAsync(
|
||||||
|
IEnumerable<long> idsCamino,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
}
|
||||||
@@ -3398,13 +3398,6 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
|||||||
finales.Add(alternativaPie);
|
finales.Add(alternativaPie);
|
||||||
}
|
}
|
||||||
|
|
||||||
var globalMasRapida = finales
|
|
||||||
.OrderBy(x => DuracionVisibleOrdenacion(x))
|
|
||||||
.ThenBy(x => x.PuntuacionCoste)
|
|
||||||
.FirstOrDefault();
|
|
||||||
if (globalMasRapida is not null && globalMasRapida.EsSoloAPie)
|
|
||||||
globalMasRapida.Etiqueta = "M\u00e1s r\u00e1pida";
|
|
||||||
|
|
||||||
return finales;
|
return finales;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,9 @@
|
|||||||
},
|
},
|
||||||
"Valhalla": {
|
"Valhalla": {
|
||||||
"BaseUrl": "http://192.168.41.12:8002"
|
"BaseUrl": "http://192.168.41.12:8002"
|
||||||
|
},
|
||||||
|
"Osm": {
|
||||||
|
"BaseUrl": "https://api.openstreetmap.org/api/0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1417,6 +1417,147 @@ html, body {
|
|||||||
.fabrica-cambios { max-height: 45dvh; overflow-y: auto; font-size: .8rem; overflow-wrap: anywhere; }
|
.fabrica-cambios { max-height: 45dvh; overflow-y: auto; font-size: .8rem; overflow-wrap: anywhere; }
|
||||||
.fabrica-cambios > div { padding: 5px 0; border-bottom: 1px solid #374151; }
|
.fabrica-cambios > div { padding: 5px 0; border-bottom: 1px solid #374151; }
|
||||||
|
|
||||||
|
.route-score-detail-line {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-score-info,
|
||||||
|
.score-breakdown-close {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
color: #cbd5e1;
|
||||||
|
border: 1px solid rgba(148, 163, 184, .55);
|
||||||
|
background: rgba(15, 23, 42, .7);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-score-info {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: .68rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-score-info:hover,
|
||||||
|
.route-score-info:focus-visible,
|
||||||
|
.score-breakdown-close:hover,
|
||||||
|
.score-breakdown-close:focus-visible {
|
||||||
|
color: #fff;
|
||||||
|
border-color: #38bdf8;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-backdrop {
|
||||||
|
z-index: 5750;
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-modal-card.score-breakdown-modal {
|
||||||
|
width: min(590px, calc(100vw - 2rem));
|
||||||
|
max-height: min(780px, calc(100dvh - 2rem));
|
||||||
|
padding: 1rem;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-header .custom-modal-title {
|
||||||
|
margin-bottom: .15rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-subtitle,
|
||||||
|
.score-breakdown-note {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-close {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
border-radius: 50%;
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-formula {
|
||||||
|
margin-top: .85rem;
|
||||||
|
padding: .7rem 0;
|
||||||
|
color: #e2e8f0;
|
||||||
|
border-top: 1px solid rgba(148, 163, 184, .22);
|
||||||
|
border-bottom: 1px solid rgba(148, 163, 184, .22);
|
||||||
|
font-size: .82rem;
|
||||||
|
font-weight: 650;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-section {
|
||||||
|
padding: .8rem 0;
|
||||||
|
border-bottom: 1px solid rgba(148, 163, 184, .18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-section h3 {
|
||||||
|
margin: 0 0 .45rem;
|
||||||
|
color: #f8fafc;
|
||||||
|
font-size: .84rem;
|
||||||
|
letter-spacing: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-section .score-breakdown-note {
|
||||||
|
margin-bottom: .45rem;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) auto;
|
||||||
|
align-items: baseline;
|
||||||
|
gap: .75rem;
|
||||||
|
padding: .25rem 0;
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-size: .79rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-row strong {
|
||||||
|
color: #f8fafc;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-row--subtotal {
|
||||||
|
margin-top: .25rem;
|
||||||
|
padding-top: .45rem;
|
||||||
|
border-top: 1px dashed rgba(148, 163, 184, .2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-row--total {
|
||||||
|
color: #e2e8f0;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-result {
|
||||||
|
display: flex;
|
||||||
|
align-items: baseline;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 1rem;
|
||||||
|
padding-top: .85rem;
|
||||||
|
color: #f8fafc;
|
||||||
|
font-size: .95rem;
|
||||||
|
font-weight: 750;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score-breakdown-result strong {
|
||||||
|
color: #facc15;
|
||||||
|
font-size: 1.12rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* LISTA DE TRAMOS TIPO “DIRECCIONES” CON NÚMEROS */
|
/* LISTA DE TRAMOS TIPO “DIRECCIONES” CON NÚMEROS */
|
||||||
|
|
||||||
.lista-tramos-ruta {
|
.lista-tramos-ruta {
|
||||||
@@ -1727,9 +1868,13 @@ html, body {
|
|||||||
color: #fef3c7;
|
color: #fef3c7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.history-derivation-step--active {
|
||||||
|
box-shadow: inset 0 -2px #22c55e;
|
||||||
|
}
|
||||||
|
|
||||||
.history-derivation-selected {
|
.history-derivation-selected {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
gap: .75rem;
|
gap: .75rem;
|
||||||
padding: .5rem 0;
|
padding: .5rem 0;
|
||||||
@@ -1738,12 +1883,31 @@ html, body {
|
|||||||
color: #f8fafc;
|
color: #f8fafc;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-derivation-selected span {
|
.history-derivation-selected__summary {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: .1rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.history-derivation-selected__summary span {
|
||||||
color: #94a3b8;
|
color: #94a3b8;
|
||||||
font-size: .72rem;
|
font-size: .72rem;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-ghost.history-derivation-load {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
min-height: 30px;
|
||||||
|
padding: .3rem .75rem;
|
||||||
|
border-color: rgba(59, 130, 246, .72);
|
||||||
|
background: rgba(29, 78, 216, .34);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-ghost.history-derivation-load:hover {
|
||||||
|
background: #1d4ed8;
|
||||||
|
}
|
||||||
|
|
||||||
.history-derivation-comparison {
|
.history-derivation-comparison {
|
||||||
padding-top: .7rem;
|
padding-top: .7rem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using Microsoft.Extensions.Configuration;
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using Microsoft.Extensions.Logging.Abstractions;
|
using Microsoft.Extensions.Logging.Abstractions;
|
||||||
using RutasDBUS.Components.Pages;
|
using RutasDBUS.Components.Pages;
|
||||||
using RutasDBUS.Modelos.Historial;
|
using RutasDBUS.Modelos.Historial;
|
||||||
@@ -180,6 +181,55 @@ Check((bool)puntoDentro.Invoke(null, new object?[] { 43.3155, -1.9845, parametro
|
|||||||
"Las paradas interiores a una zona se detectan antes de calcular");
|
"Las paradas interiores a una zona se detectan antes de calcular");
|
||||||
var page = new PlanificadorRutas();
|
var page = new PlanificadorRutas();
|
||||||
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
var flags = BindingFlags.Instance | BindingFlags.NonPublic;
|
||||||
|
var fabricaHistorial = (ConfiguracionHistorialRuta)baseMethod.Invoke(null, null)!;
|
||||||
|
fabricaHistorial.FactorEquilibradoAndarBus = 6;
|
||||||
|
typeof(PlanificadorRutas).GetField("_configuracionFabrica", flags)!.SetValue(page, fabricaHistorial);
|
||||||
|
var crearPredeterminadaHistorial = typeof(PlanificadorRutas).GetMethod(
|
||||||
|
"CrearConfiguracionPredeterminadaParaHistorial", flags)!;
|
||||||
|
var configuracionGuardada = fabricaHistorial.Clonar();
|
||||||
|
configuracionGuardada.UsarHoraSimulada = true;
|
||||||
|
configuracionGuardada.FechaSimulada = "2026-04-08";
|
||||||
|
configuracionGuardada.HoraSimulada = "08:46:17";
|
||||||
|
configuracionGuardada.FactorEquilibradoAndarBus = 99;
|
||||||
|
var registroConHora = new RegistroHistorialRuta { Configuracion = configuracionGuardada };
|
||||||
|
var predeterminadaConHora = (ConfiguracionHistorialRuta)crearPredeterminadaHistorial.Invoke(
|
||||||
|
page, new object[] { registroConHora })!;
|
||||||
|
Check(predeterminadaConHora.UsarHoraSimulada &&
|
||||||
|
predeterminadaConHora.FechaSimulada == "2026-04-08" &&
|
||||||
|
predeterminadaConHora.HoraSimulada == "08:46:17" &&
|
||||||
|
predeterminadaConHora.FactorEquilibradoAndarBus == 6,
|
||||||
|
"Predeterminada conserva solo la fecha y hora simuladas del historial");
|
||||||
|
configuracionGuardada.UsarHoraSimulada = false;
|
||||||
|
configuracionGuardada.FechaSimulada = "2020-01-01";
|
||||||
|
configuracionGuardada.HoraSimulada = "01:02:03";
|
||||||
|
var predeterminadaSinHora = (ConfiguracionHistorialRuta)crearPredeterminadaHistorial.Invoke(
|
||||||
|
page, new object[] { registroConHora })!;
|
||||||
|
Check(!predeterminadaSinHora.UsarHoraSimulada &&
|
||||||
|
predeterminadaSinHora.FechaSimulada == DateOnly.FromDateTime(DateTime.Now)
|
||||||
|
.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture) &&
|
||||||
|
predeterminadaSinHora.HoraSimulada != "01:02:03",
|
||||||
|
"Predeterminada usa la hora actual cuando el historial no tenia simulacion");
|
||||||
|
typeof(PlanificadorRutas).GetField("_factorEquilibradoAndarBus", flags)!.SetValue(page, 6.0);
|
||||||
|
typeof(PlanificadorRutas).GetField("_factorPenalizacionDistanciaPie", flags)!.SetValue(page, 1.8);
|
||||||
|
typeof(PlanificadorRutas).GetField("_parametroPenalizacionDistanciaPieMetros", flags)!.SetValue(page, 200.0);
|
||||||
|
typeof(PlanificadorRutas).GetField("_pesoTransbordoRankingMinutos", flags)!.SetValue(page, 6.0);
|
||||||
|
typeof(PlanificadorRutas).GetField("_velocidadBusKmH", flags)!.SetValue(page, 16.0);
|
||||||
|
typeof(PlanificadorRutas).GetField("_mostrarElevacionesRuta", flags)!.SetValue(page, true);
|
||||||
|
var alternativaDesglose = new AlternativaRuta
|
||||||
|
{
|
||||||
|
AlternativaBus = new RutaBusAlternativa { DistanciaTotalBus = 6000 },
|
||||||
|
DistanciasPiePorTramoAprox = new List<double> { 100, 300 },
|
||||||
|
EsfuerzoExtraPendienteMetros = 25,
|
||||||
|
NumeroTransbordosReales = 1
|
||||||
|
};
|
||||||
|
var calcularScore = typeof(PlanificadorRutas).GetMethod("CalcularScoreEquilibradoFactor", flags)!;
|
||||||
|
var crearDesglose = typeof(PlanificadorRutas).GetMethod("CrearDesgloseScoreEquilibrado", flags)!;
|
||||||
|
var scoreCalculado = (double)calcularScore.Invoke(page, new object?[] { alternativaDesglose })!;
|
||||||
|
var desgloseCalculado = crearDesglose.Invoke(page, new object?[] { alternativaDesglose })!;
|
||||||
|
var scoreDesglosado = (double)desgloseCalculado.GetType().GetProperty("ScoreTotal")!
|
||||||
|
.GetValue(desgloseCalculado)!;
|
||||||
|
Check(Math.Abs(scoreCalculado - scoreDesglosado) < 0.0001,
|
||||||
|
"El desglose y el ranking usan exactamente el mismo score");
|
||||||
var formatearEtiqueta = typeof(PlanificadorRutas).GetMethod("FormatearEtiquetaAlternativaTarjeta", flags)!;
|
var formatearEtiqueta = typeof(PlanificadorRutas).GetMethod("FormatearEtiquetaAlternativaTarjeta", flags)!;
|
||||||
Check((string)formatearEtiqueta.Invoke(page, new object[]
|
Check((string)formatearEtiqueta.Invoke(page, new object[]
|
||||||
{ new AlternativaRuta { Etiqueta = "Equilibrada" }, 0 })! == "Opción 1",
|
{ new AlternativaRuta { Etiqueta = "Equilibrada" }, 0 })! == "Opción 1",
|
||||||
@@ -206,6 +256,12 @@ Check(!(bool)typeof(PlanificadorRutas).GetField("_activarRangoLineas", flags)!.G
|
|||||||
(int)typeof(PlanificadorRutas).GetField("_itinerarioMin", flags)!.GetValue(page)! == defaults.ItinerarioMin &&
|
(int)typeof(PlanificadorRutas).GetField("_itinerarioMin", flags)!.GetValue(page)! == defaults.ItinerarioMin &&
|
||||||
(int)typeof(PlanificadorRutas).GetField("_itinerarioMax", flags)!.GetValue(page)! == defaults.ItinerarioMax,
|
(int)typeof(PlanificadorRutas).GetField("_itinerarioMax", flags)!.GetValue(page)! == defaults.ItinerarioMax,
|
||||||
"Restaurar aplica tambien los valores de lineas e itinerarios");
|
"Restaurar aplica tambien los valores de lineas e itinerarios");
|
||||||
|
var campoModoHistorial = typeof(PlanificadorRutas).GetField("_modoConfiguracionHistorial", flags)!;
|
||||||
|
var modoGuardada = Enum.Parse(campoModoHistorial.FieldType, "Guardada");
|
||||||
|
campoModoHistorial.SetValue(page, modoGuardada);
|
||||||
|
typeof(PlanificadorRutas).GetMethod("AplicarValoresFabricaConservandoSeleccion", flags)!.Invoke(page, null);
|
||||||
|
Check(Equals(campoModoHistorial.GetValue(page), modoGuardada),
|
||||||
|
"Restaurar valores conserva el modo de configuracion del historial");
|
||||||
var options = (List<AlternativaRuta>)typeof(PlanificadorRutas).GetField("_opcionesRuta", flags)!.GetValue(page)!;
|
var options = (List<AlternativaRuta>)typeof(PlanificadorRutas).GetField("_opcionesRuta", flags)!.GetValue(page)!;
|
||||||
var selected = typeof(PlanificadorRutas).GetField("_indiceAlternativaSeleccionada", flags)!;
|
var selected = typeof(PlanificadorRutas).GetField("_indiceAlternativaSeleccionada", flags)!;
|
||||||
var remember = typeof(PlanificadorRutas).GetMethod("ObtenerProveedorSoloAPieParaRecalculo", flags)!;
|
var remember = typeof(PlanificadorRutas).GetMethod("ObtenerProveedorSoloAPieParaRecalculo", flags)!;
|
||||||
@@ -221,6 +277,26 @@ selected.SetValue(page, 0);
|
|||||||
Check(Remember(true) is null, "Seleccionar bus libera la preferencia andando");
|
Check(Remember(true) is null, "Seleccionar bus libera la preferencia andando");
|
||||||
options.Clear();
|
options.Clear();
|
||||||
Check(Remember(false) is null, "Nueva seleccion de criterio no hereda la preferencia andando");
|
Check(Remember(false) is null, "Nueva seleccion de criterio no hereda la preferencia andando");
|
||||||
|
var osmHandler = new OsmWidthHandler();
|
||||||
|
using var osmCache = new MemoryCache(new MemoryCacheOptions());
|
||||||
|
var osmConfig = new ConfigurationBuilder().AddInMemoryCollection(new Dictionary<string, string?>
|
||||||
|
{
|
||||||
|
["Caminata:Osm:BaseUrl"] = "https://osm.test/api/0.6"
|
||||||
|
}).Build();
|
||||||
|
var osmClient = new ClienteAtributosOsm(new HttpClient(osmHandler), osmCache, osmConfig);
|
||||||
|
var anchos = await osmClient.ObtenerAnchurasAsync([101, 102, 103]);
|
||||||
|
Check(anchos[101] == new AnchuraViaOsm("4.5", false) &&
|
||||||
|
anchos[102] == new AnchuraViaOsm("2", true) &&
|
||||||
|
!anchos.ContainsKey(103),
|
||||||
|
"La anchura exacta y la estimada se leen de las etiquetas OSM");
|
||||||
|
await osmClient.ObtenerAnchurasAsync([101, 102, 103]);
|
||||||
|
Check(osmHandler.Calls == 1, "Las anchuras OSM se reutilizan desde cache");
|
||||||
|
var formatearSuperficieSinDatos = typeof(PlanificadorRutas).GetMethod(
|
||||||
|
"FormatearSuperficieSegmento", BindingFlags.Static | BindingFlags.NonPublic)!;
|
||||||
|
var superficieSinDatos = (string)formatearSuperficieSinDatos.Invoke(null, [null])!;
|
||||||
|
Check(superficieSinDatos.Contains("Carriles: -") &&
|
||||||
|
superficieSinDatos.Contains("Anchura: -"),
|
||||||
|
"El tooltip mantiene todos los atributos cuando no hay datos de via");
|
||||||
Console.WriteLine("Pruebas completadas.");
|
Console.WriteLine("Pruebas completadas.");
|
||||||
|
|
||||||
sealed class FakeValhalla : IClienteValhalla
|
sealed class FakeValhalla : IClienteValhalla
|
||||||
@@ -246,6 +322,31 @@ sealed class FakeValhalla : IClienteValhalla
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sealed class OsmWidthHandler : HttpMessageHandler
|
||||||
|
{
|
||||||
|
public int Calls { get; private set; }
|
||||||
|
|
||||||
|
protected override Task<HttpResponseMessage> SendAsync(
|
||||||
|
HttpRequestMessage request,
|
||||||
|
CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
Calls++;
|
||||||
|
const string contenido = """
|
||||||
|
{
|
||||||
|
"elements": [
|
||||||
|
{ "type": "way", "id": 101, "tags": { "width": "4.5" } },
|
||||||
|
{ "type": "way", "id": 102, "tags": { "est_width": "2" } },
|
||||||
|
{ "type": "way", "id": 103, "tags": { "highway": "residential" } }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
return Task.FromResult(new HttpResponseMessage(System.Net.HttpStatusCode.OK)
|
||||||
|
{
|
||||||
|
Content = new StringContent(contenido, System.Text.Encoding.UTF8, "application/json")
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sealed class RetryValhallaHandler : HttpMessageHandler
|
sealed class RetryValhallaHandler : HttpMessageHandler
|
||||||
{
|
{
|
||||||
public List<string> Bodies { get; } = new();
|
public List<string> Bodies { get; } = new();
|
||||||
|
|||||||
Reference in New Issue
Block a user