Añadido horario simualdo con advertencia a lostaxibus
añadido desseleccion de segmentos y iluminado del mismo añadido limites nuevos en lugares y ordenes alfabeticos quitado acalarion b5m defectos de L5 y maximos al 80% formula cambiada a 5.2 y division por distancia de bus dsitancia con z anteriores
This commit is contained in:
@@ -49,7 +49,8 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
// CONFIG
|
||||
private const double FactorPenalizacionDistanciaPiePorDefecto = 1.35;
|
||||
private const double ParametroPenalizacionDistanciaPieMetrosPorDefecto = 200.0;
|
||||
private const double CoeficienteEsfuerzoAlturaPorDefecto = 8.2;
|
||||
private const double CoeficienteEsfuerzoAlturaPorDefecto = 5.2;
|
||||
private const double PendienteMaximaEsfuerzoPorcentaje = 80.0;
|
||||
private const double FactorEquilibradoAndarBusPorDefecto = 6.0;
|
||||
private const double UmbralPendienteSubidaRutaPorDefecto = 2.0;
|
||||
private const double UmbralPendienteBajadaRutaPorDefecto = 5.0;
|
||||
@@ -64,7 +65,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private double _factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.35";
|
||||
private string _parametroPenalizacionDistanciaPieTextoEdicion = "200";
|
||||
private string _coeficienteEsfuerzoAlturaTextoEdicion = "8.20";
|
||||
private string _coeficienteEsfuerzoAlturaTextoEdicion = "5.20";
|
||||
private string _pesoTransbordoRankingTextoEdicion = "4,0";
|
||||
private string _factorEquilibradoAndarBusTextoEdicion = "6";
|
||||
private double _velocidadBusKmH = 16.0;
|
||||
@@ -234,7 +235,10 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
if (pendientePorcentaje.Value < umbralSubida)
|
||||
return distanciaMetros.Value;
|
||||
|
||||
var pendiente = pendientePorcentaje.Value / 100.0;
|
||||
var pendienteLimitadaPorcentaje = Math.Min(
|
||||
pendientePorcentaje.Value,
|
||||
PendienteMaximaEsfuerzoPorcentaje);
|
||||
var pendiente = pendienteLimitadaPorcentaje / 100.0;
|
||||
var factorConPotencia = Math.Pow(1.0 + pendiente, coeficiente);
|
||||
var esfuerzo = distanciaMetros.Value * factorConPotencia;
|
||||
return double.IsFinite(esfuerzo)
|
||||
|
||||
@@ -76,7 +76,7 @@ public partial class PlanificadorRutas
|
||||
try
|
||||
{
|
||||
await Task.Delay(120, ct);
|
||||
var sugerencias = await CrearSugerenciasBusquedaAsync(texto, 20, ct);
|
||||
var sugerencias = await CrearSugerenciasBusquedaAsync(texto, 50, ct);
|
||||
|
||||
if (ct.IsCancellationRequested || version != _versionSugerenciasBusqueda)
|
||||
return;
|
||||
@@ -160,7 +160,9 @@ public partial class PlanificadorRutas
|
||||
.Concat(lugares.Select(CrearSugerenciaLugar))
|
||||
.GroupBy(sugerencia => NormalizarClaveSugerencia(sugerencia), StringComparer.Ordinal)
|
||||
.Select(grupo => grupo.First())
|
||||
.Take(Math.Clamp(maxResultados, 1, 20))
|
||||
.OrderBy(sugerencia => sugerencia.Titulo, StringComparer.CurrentCultureIgnoreCase)
|
||||
.ThenBy(sugerencia => sugerencia.Subtitulo, StringComparer.CurrentCultureIgnoreCase)
|
||||
.Take(Math.Clamp(maxResultados, 1, 50))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
@@ -247,8 +249,8 @@ public partial class PlanificadorRutas
|
||||
TextoEntrada = lugar.Nombre,
|
||||
Titulo = lugar.Nombre,
|
||||
Subtitulo = string.IsNullOrWhiteSpace(lugar.Subtitulo)
|
||||
? $"{lugar.Tipo} · {lugar.Fuente}"
|
||||
: $"{lugar.Subtitulo} · {lugar.Fuente}",
|
||||
? lugar.Tipo
|
||||
: lugar.Subtitulo,
|
||||
Tipo = lugar.Tipo,
|
||||
Fuente = lugar.Fuente,
|
||||
Latitud = lugar.Latitud,
|
||||
|
||||
@@ -30,6 +30,10 @@ public partial class PlanificadorRutas
|
||||
private TipoReferenciaHistorial _tipoReferenciaHistorial;
|
||||
private string _historialReferenciaId = string.Empty;
|
||||
private bool _rutaActualDerivadaDeHistorial;
|
||||
private readonly List<PasoDerivacionHistorial> _pasosDerivacionHistorial = new();
|
||||
private RegistroHistorialRuta? _escenarioBaseDerivacionHistorial;
|
||||
private bool _mostrarInformacionDerivacionHistorial;
|
||||
private int _indicePasoDerivacionSeleccionado = -1;
|
||||
private string _mensajeHistorial = string.Empty;
|
||||
private bool _mensajeHistorialEsError = false;
|
||||
private bool _mostrarHistorialRutas = false;
|
||||
@@ -53,6 +57,17 @@ public partial class PlanificadorRutas
|
||||
Compartido
|
||||
}
|
||||
|
||||
private sealed record PasoDerivacionHistorial(
|
||||
int Numero,
|
||||
DateTime FechaUtc,
|
||||
RegistroHistorialRuta Escenario);
|
||||
|
||||
private sealed record CambioDerivacionHistorial(
|
||||
string Campo,
|
||||
string ValorAnterior,
|
||||
string ValorActual,
|
||||
string Detalle);
|
||||
|
||||
private sealed class ResultadoStorageNavegador
|
||||
{
|
||||
[JsonPropertyName("ok")]
|
||||
@@ -113,6 +128,7 @@ public partial class PlanificadorRutas
|
||||
{
|
||||
_versionAperturaHistorial++;
|
||||
_mostrarHistorialRutas = false;
|
||||
_mostrarInformacionDerivacionHistorial = false;
|
||||
_historialLocalCargando = false;
|
||||
StopRefrescoHistorialCompartido();
|
||||
await RestaurarConfiguracionTemporalHistorialAsync();
|
||||
@@ -834,13 +850,27 @@ public partial class PlanificadorRutas
|
||||
private void ConfirmarDerivacionHistorial()
|
||||
{
|
||||
_rutaActualDerivadaDeHistorial = ExisteReferenciaHistorialActual();
|
||||
if (_rutaActualDerivadaDeHistorial &&
|
||||
_tipoReferenciaHistorial == TipoReferenciaHistorial.Compartido)
|
||||
{
|
||||
RegistrarPasoDerivacionHistorialActual();
|
||||
}
|
||||
}
|
||||
|
||||
private void EstablecerReferenciaHistorial(TipoReferenciaHistorial tipo, string id)
|
||||
{
|
||||
ReiniciarSeguimientoDerivacionHistorial();
|
||||
_tipoReferenciaHistorial = tipo;
|
||||
_historialReferenciaId = id;
|
||||
_rutaActualDerivadaDeHistorial = false;
|
||||
|
||||
if (tipo == TipoReferenciaHistorial.Compartido)
|
||||
{
|
||||
var compartida = _historialCompartido.FirstOrDefault(item =>
|
||||
string.Equals(item.Id, id, StringComparison.OrdinalIgnoreCase));
|
||||
if (compartida is not null)
|
||||
_escenarioBaseDerivacionHistorial = ClonarEscenarioHistorial(compartida);
|
||||
}
|
||||
}
|
||||
|
||||
private void LimpiarReferenciaHistorial()
|
||||
@@ -848,6 +878,7 @@ public partial class PlanificadorRutas
|
||||
_tipoReferenciaHistorial = TipoReferenciaHistorial.Ninguna;
|
||||
_historialReferenciaId = string.Empty;
|
||||
_rutaActualDerivadaDeHistorial = false;
|
||||
ReiniciarSeguimientoDerivacionHistorial();
|
||||
}
|
||||
|
||||
private bool ExisteReferenciaHistorialActual()
|
||||
@@ -884,11 +915,265 @@ public partial class PlanificadorRutas
|
||||
_ => (string.Empty, -1)
|
||||
};
|
||||
|
||||
return indice >= 0
|
||||
? $"Ruta actual derivada del Historial {nombre} n.º {indice + 1}."
|
||||
if (indice < 0)
|
||||
return string.Empty;
|
||||
|
||||
var numeroDerivacion = _tipoReferenciaHistorial == TipoReferenciaHistorial.Compartido &&
|
||||
_pasosDerivacionHistorial.Count > 0
|
||||
? $" · Derivación n.º {_pasosDerivacionHistorial.Count}"
|
||||
: string.Empty;
|
||||
return $"Ruta actual derivada del Historial {nombre} n.º {indice + 1}{numeroDerivacion}.";
|
||||
}
|
||||
|
||||
private bool PuedeMostrarInformacionDerivacionHistorial =>
|
||||
_rutaActualDerivadaDeHistorial &&
|
||||
_tipoReferenciaHistorial == TipoReferenciaHistorial.Compartido &&
|
||||
_escenarioBaseDerivacionHistorial is not null &&
|
||||
_pasosDerivacionHistorial.Count > 0;
|
||||
|
||||
private int IndiceHistorialCompartidoReferencia =>
|
||||
_historialCompartido.FindIndex(item => string.Equals(
|
||||
item.Id,
|
||||
_historialReferenciaId,
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
|
||||
private PasoDerivacionHistorial? PasoDerivacionSeleccionado =>
|
||||
_indicePasoDerivacionSeleccionado >= 0 &&
|
||||
_indicePasoDerivacionSeleccionado < _pasosDerivacionHistorial.Count
|
||||
? _pasosDerivacionHistorial[_indicePasoDerivacionSeleccionado]
|
||||
: null;
|
||||
|
||||
private void AbrirInformacionDerivacionHistorial()
|
||||
{
|
||||
if (!PuedeMostrarInformacionDerivacionHistorial)
|
||||
return;
|
||||
|
||||
_indicePasoDerivacionSeleccionado = _pasosDerivacionHistorial.Count - 1;
|
||||
_mostrarInformacionDerivacionHistorial = true;
|
||||
}
|
||||
|
||||
private void CerrarInformacionDerivacionHistorial()
|
||||
{
|
||||
_mostrarInformacionDerivacionHistorial = false;
|
||||
}
|
||||
|
||||
private void SeleccionarPasoDerivacionHistorial(int indice)
|
||||
{
|
||||
if (indice < 0 || indice >= _pasosDerivacionHistorial.Count)
|
||||
return;
|
||||
|
||||
_indicePasoDerivacionSeleccionado = indice;
|
||||
}
|
||||
|
||||
private void ReiniciarSeguimientoDerivacionHistorial()
|
||||
{
|
||||
_pasosDerivacionHistorial.Clear();
|
||||
_escenarioBaseDerivacionHistorial = null;
|
||||
_indicePasoDerivacionSeleccionado = -1;
|
||||
_mostrarInformacionDerivacionHistorial = false;
|
||||
}
|
||||
|
||||
private void RegistrarPasoDerivacionHistorialActual()
|
||||
{
|
||||
_escenarioBaseDerivacionHistorial ??= _historialCompartido
|
||||
.FirstOrDefault(item => string.Equals(
|
||||
item.Id,
|
||||
_historialReferenciaId,
|
||||
StringComparison.OrdinalIgnoreCase)) is { } compartida
|
||||
? ClonarEscenarioHistorial(compartida)
|
||||
: null;
|
||||
|
||||
if (_escenarioBaseDerivacionHistorial is null)
|
||||
return;
|
||||
|
||||
var actual = ConstruirRegistroHistorialActual();
|
||||
if (actual is null)
|
||||
return;
|
||||
|
||||
var anterior = _pasosDerivacionHistorial.Count > 0
|
||||
? _pasosDerivacionHistorial[^1].Escenario
|
||||
: _escenarioBaseDerivacionHistorial;
|
||||
if (string.Equals(
|
||||
CalcularClaveEscenario(anterior),
|
||||
CalcularClaveEscenario(actual),
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_pasosDerivacionHistorial.Add(new PasoDerivacionHistorial(
|
||||
_pasosDerivacionHistorial.Count + 1,
|
||||
DateTime.UtcNow,
|
||||
ClonarEscenarioHistorial(actual)));
|
||||
_indicePasoDerivacionSeleccionado = _pasosDerivacionHistorial.Count - 1;
|
||||
}
|
||||
|
||||
private RegistroHistorialRuta? ObtenerEscenarioAnteriorDerivacion(
|
||||
PasoDerivacionHistorial paso)
|
||||
{
|
||||
var indice = paso.Numero - 1;
|
||||
if (indice <= 0)
|
||||
return _escenarioBaseDerivacionHistorial;
|
||||
|
||||
return indice - 1 < _pasosDerivacionHistorial.Count
|
||||
? _pasosDerivacionHistorial[indice - 1].Escenario
|
||||
: null;
|
||||
}
|
||||
|
||||
private IReadOnlyList<CambioDerivacionHistorial> ObtenerCambiosDerivacion(
|
||||
RegistroHistorialRuta? anterior,
|
||||
RegistroHistorialRuta actual)
|
||||
{
|
||||
if (anterior is null)
|
||||
return Array.Empty<CambioDerivacionHistorial>();
|
||||
|
||||
var cambios = new List<CambioDerivacionHistorial>();
|
||||
AgregarCambioPuntoDerivacion(
|
||||
cambios,
|
||||
"Origen",
|
||||
anterior.OrigenTexto,
|
||||
anterior.OrigenLatitud,
|
||||
anterior.OrigenLongitud,
|
||||
actual.OrigenTexto,
|
||||
actual.OrigenLatitud,
|
||||
actual.OrigenLongitud);
|
||||
AgregarCambioPuntoDerivacion(
|
||||
cambios,
|
||||
"Destino",
|
||||
anterior.DestinoTexto,
|
||||
anterior.DestinoLatitud,
|
||||
anterior.DestinoLongitud,
|
||||
actual.DestinoTexto,
|
||||
actual.DestinoLatitud,
|
||||
actual.DestinoLongitud);
|
||||
|
||||
AgregarCambioTextoDerivacion(
|
||||
cambios,
|
||||
"Fecha y hora",
|
||||
FormatearFechaHoraComparacionDerivacion(anterior),
|
||||
FormatearFechaHoraComparacionDerivacion(actual));
|
||||
AgregarCambioTextoDerivacion(
|
||||
cambios,
|
||||
"Modo",
|
||||
FormatearModoHistorial(anterior),
|
||||
FormatearModoHistorial(actual));
|
||||
AgregarCambioTextoDerivacion(
|
||||
cambios,
|
||||
"Factor Equilib. A",
|
||||
FormatearFactorDerivacion(anterior),
|
||||
FormatearFactorDerivacion(actual));
|
||||
AgregarCambioTextoDerivacion(
|
||||
cambios,
|
||||
"Horarios",
|
||||
anterior.UsarHorariosTeoricos ? "Activados" : "Desactivados",
|
||||
actual.UsarHorariosTeoricos ? "Activados" : "Desactivados");
|
||||
AgregarCambioTextoDerivacion(
|
||||
cambios,
|
||||
"Líneas utilizables",
|
||||
FormatearLineasDerivacion(anterior),
|
||||
FormatearLineasDerivacion(actual));
|
||||
|
||||
return cambios;
|
||||
}
|
||||
|
||||
private static void AgregarCambioPuntoDerivacion(
|
||||
ICollection<CambioDerivacionHistorial> cambios,
|
||||
string campo,
|
||||
string textoAnterior,
|
||||
double latitudAnterior,
|
||||
double longitudAnterior,
|
||||
string textoActual,
|
||||
double latitudActual,
|
||||
double longitudActual)
|
||||
{
|
||||
var distancia = CalcularDistanciaMetros(
|
||||
latitudAnterior,
|
||||
longitudAnterior,
|
||||
latitudActual,
|
||||
longitudActual);
|
||||
if (!double.IsFinite(distancia) || distancia < 0.5)
|
||||
return;
|
||||
|
||||
cambios.Add(new CambioDerivacionHistorial(
|
||||
campo,
|
||||
FormatearPuntoDerivacion(textoAnterior, latitudAnterior, longitudAnterior),
|
||||
FormatearPuntoDerivacion(textoActual, latitudActual, longitudActual),
|
||||
$"Desplazado {FormatearDistanciaDerivacion(distancia)}"));
|
||||
}
|
||||
|
||||
private static void AgregarCambioTextoDerivacion(
|
||||
ICollection<CambioDerivacionHistorial> cambios,
|
||||
string campo,
|
||||
string? valorAnterior,
|
||||
string? valorActual)
|
||||
{
|
||||
var anterior = string.IsNullOrWhiteSpace(valorAnterior) ? "-" : valorAnterior.Trim();
|
||||
var actual = string.IsNullOrWhiteSpace(valorActual) ? "-" : valorActual.Trim();
|
||||
if (string.Equals(anterior, actual, StringComparison.OrdinalIgnoreCase))
|
||||
return;
|
||||
|
||||
cambios.Add(new CambioDerivacionHistorial(campo, anterior, actual, string.Empty));
|
||||
}
|
||||
|
||||
private static string FormatearPuntoDerivacion(
|
||||
string texto,
|
||||
double latitud,
|
||||
double longitud)
|
||||
{
|
||||
var nombre = string.IsNullOrWhiteSpace(texto) ? "Punto del mapa" : texto.Trim();
|
||||
return $"{nombre} · {latitud.ToString("0.000000", CultureInfo.InvariantCulture)}, " +
|
||||
longitud.ToString("0.000000", CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
private static string FormatearDistanciaDerivacion(double distanciaMetros)
|
||||
=> distanciaMetros >= 1000
|
||||
? $"{(distanciaMetros / 1000.0).ToString("0.000", CultureInfo.InvariantCulture)} km"
|
||||
: $"{Math.Round(distanciaMetros).ToString("0", CultureInfo.InvariantCulture)} m";
|
||||
|
||||
private static string FormatearFechaHoraComparacionDerivacion(RegistroHistorialRuta item)
|
||||
=> item.UsarHoraSimulada
|
||||
? FormatearHoraReferenciaHistorial(item)
|
||||
: "Fecha y hora actuales";
|
||||
|
||||
private static string FormatearFactorDerivacion(RegistroHistorialRuta item)
|
||||
=> item.FactorEquilibradoAndarBus is { } factor && double.IsFinite(factor) && factor > 0
|
||||
? factor.ToString("0.##", CultureInfo.InvariantCulture)
|
||||
: "-";
|
||||
|
||||
private static string FormatearLineasDerivacion(RegistroHistorialRuta item)
|
||||
{
|
||||
if (!item.ActivarFiltroLineas)
|
||||
return "Todas";
|
||||
|
||||
var lineas = NormalizarLineasHistorial(item.LineasPermitidas);
|
||||
return lineas.Count == 0
|
||||
? "Ninguna"
|
||||
: string.Join(", ", lineas);
|
||||
}
|
||||
|
||||
private static RegistroHistorialRuta ClonarEscenarioHistorial(RegistroHistorialRuta item)
|
||||
=> new()
|
||||
{
|
||||
Id = item.Id,
|
||||
OrigenTexto = item.OrigenTexto,
|
||||
DestinoTexto = item.DestinoTexto,
|
||||
OrigenLatitud = item.OrigenLatitud,
|
||||
OrigenLongitud = item.OrigenLongitud,
|
||||
DestinoLatitud = item.DestinoLatitud,
|
||||
DestinoLongitud = item.DestinoLongitud,
|
||||
UsarHorariosTeoricos = item.UsarHorariosTeoricos,
|
||||
UsarHoraSimulada = item.UsarHoraSimulada,
|
||||
FechaSimulada = item.FechaSimulada,
|
||||
HoraSimulada = item.HoraSimulada,
|
||||
CriterioPrioritarioRuta = item.CriterioPrioritarioRuta,
|
||||
PreferenciaEtiquetaRuta = item.PreferenciaEtiquetaRuta,
|
||||
FactorEquilibradoAndarBus = item.FactorEquilibradoAndarBus,
|
||||
ActivarFiltroLineas = item.ActivarFiltroLineas,
|
||||
LineasPermitidas = item.LineasPermitidas.ToList(),
|
||||
FechaCreacionUtc = item.FechaCreacionUtc,
|
||||
FechaActualizacionUtc = item.FechaActualizacionUtc
|
||||
};
|
||||
|
||||
private async Task CompartirEscenarioActualAsync()
|
||||
{
|
||||
var actual = ConstruirRegistroHistorialActual()
|
||||
|
||||
35
RutasDBUS/Components/Pages/PlanificadorRutas.InspectorZ.cs
Normal file
35
RutasDBUS/Components/Pages/PlanificadorRutas.InspectorZ.cs
Normal file
@@ -0,0 +1,35 @@
|
||||
using System.Globalization;
|
||||
|
||||
namespace RutasDBUS.Components.Pages;
|
||||
|
||||
public partial class PlanificadorRutas
|
||||
{
|
||||
private PuntoInspectorAnterior? _inspectorPuntoAnterior;
|
||||
|
||||
private sealed record PuntoInspectorAnterior(
|
||||
double Latitud,
|
||||
double Longitud,
|
||||
double? ElevacionIdee,
|
||||
double? ElevacionLocal5,
|
||||
double? ElevacionLocal2,
|
||||
double? ElevacionLocal1);
|
||||
|
||||
private double? ObtenerElevacionPuntoInspectorAnterior(PuntoInspectorAnterior punto)
|
||||
=> UsarElevacionLocal1ParaCalculo
|
||||
? punto.ElevacionLocal1
|
||||
: UsarElevacionLocal2ParaCalculo
|
||||
? punto.ElevacionLocal2
|
||||
: UsarElevacionLocal5ParaCalculo
|
||||
? punto.ElevacionLocal5
|
||||
: punto.ElevacionIdee;
|
||||
|
||||
private static string FormatearDistanciaPuntoInspector(double distanciaMetros)
|
||||
=> distanciaMetros < 1000
|
||||
? distanciaMetros.ToString("0.0", CultureInfo.InvariantCulture) + " m"
|
||||
: (distanciaMetros / 1000.0).ToString("0.000", CultureInfo.InvariantCulture) + " km";
|
||||
|
||||
private static string FormatearAlturaPuntoInspector(double? altura)
|
||||
=> altura is { } valor
|
||||
? valor.ToString("0.00", CultureInfo.InvariantCulture) + " m"
|
||||
: "-";
|
||||
}
|
||||
@@ -1202,7 +1202,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _elevPopupMinimizado = false;
|
||||
|
||||
|
||||
private const string _versionApp = "(v-20260825b)";
|
||||
private const string _versionApp = "(v-20260827b)";
|
||||
|
||||
|
||||
private double? _paradaElevIdee;
|
||||
|
||||
@@ -10,6 +10,8 @@ public partial class PlanificadorRutas
|
||||
private const string ClaveConfiguracionLocal = "rutasdbus.planificador.config.v1";
|
||||
private const int VersionPersistenciaFactorEquilibrado = 2;
|
||||
private const int VersionPersistenciaPendientes = 2;
|
||||
private const int VersionPersistenciaCoeficienteEsfuerzo = 1;
|
||||
private const int VersionPersistenciaFuenteElevacion = 1;
|
||||
private bool _configuracionLocalCargada = false;
|
||||
private bool _mostrarConfirmacionRestablecer = false;
|
||||
|
||||
@@ -38,11 +40,13 @@ public partial class PlanificadorRutas
|
||||
public double? FactorPenalizacionDistanciaPie { get; set; }
|
||||
public double? ParametroPenalizacionDistanciaPieMetros { get; set; }
|
||||
public double? CoeficienteEsfuerzoAltura { get; set; }
|
||||
public int? VersionCoeficienteEsfuerzo { get; set; }
|
||||
public double? PesoTransbordoRankingMinutos { get; set; }
|
||||
public double? FactorEquilibradoAndarBus { get; set; }
|
||||
public int? VersionFactorEquilibrado { get; set; }
|
||||
public bool? MostrarElevacionesRuta { get; set; }
|
||||
public string? FuenteElevacionCalculo { get; set; }
|
||||
public int? VersionFuenteElevacion { get; set; }
|
||||
public int? VersionPendientes { get; set; }
|
||||
public double? UmbralPendienteSubidaRuta { get; set; }
|
||||
public double? UmbralPendienteBajadaRuta { get; set; }
|
||||
@@ -154,11 +158,13 @@ public partial class PlanificadorRutas
|
||||
FactorPenalizacionDistanciaPie = _factorPenalizacionDistanciaPie,
|
||||
ParametroPenalizacionDistanciaPieMetros = _parametroPenalizacionDistanciaPieMetros,
|
||||
CoeficienteEsfuerzoAltura = _coeficienteEsfuerzoAltura,
|
||||
VersionCoeficienteEsfuerzo = VersionPersistenciaCoeficienteEsfuerzo,
|
||||
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
||||
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
||||
VersionFactorEquilibrado = VersionPersistenciaFactorEquilibrado,
|
||||
MostrarElevacionesRuta = _mostrarElevacionesRutaConfiguracion,
|
||||
FuenteElevacionCalculo = _fuenteElevacionCalculo,
|
||||
VersionFuenteElevacion = VersionPersistenciaFuenteElevacion,
|
||||
VersionPendientes = VersionPersistenciaPendientes,
|
||||
UmbralPendienteSubidaRuta = _umbralPendienteSubidaRuta,
|
||||
UmbralPendienteBajadaRuta = _umbralPendienteBajadaRuta,
|
||||
@@ -218,11 +224,15 @@ public partial class PlanificadorRutas
|
||||
Math.Abs(parametro - 100.0) < 0.0001
|
||||
? ParametroPenalizacionDistanciaPieMetrosPorDefecto
|
||||
: parametro;
|
||||
var migrarCoeficienteEsfuerzo =
|
||||
(config.VersionCoeficienteEsfuerzo ?? 0) < VersionPersistenciaCoeficienteEsfuerzo;
|
||||
if (config.CoeficienteEsfuerzoAltura is { } coeficienteAltura && double.IsFinite(coeficienteAltura))
|
||||
_coeficienteEsfuerzoAltura =
|
||||
Math.Abs(coeficienteAltura - 1.6) < 0.0001 ||
|
||||
migrarCoeficienteEsfuerzo &&
|
||||
(Math.Abs(coeficienteAltura - 1.6) < 0.0001 ||
|
||||
Math.Abs(coeficienteAltura - 5.2) < 0.0001 ||
|
||||
Math.Abs(coeficienteAltura - 9.2) < 0.0001
|
||||
Math.Abs(coeficienteAltura - 8.2) < 0.0001 ||
|
||||
Math.Abs(coeficienteAltura - 9.2) < 0.0001)
|
||||
? CoeficienteEsfuerzoAlturaPorDefecto
|
||||
: coeficienteAltura;
|
||||
if (config.PesoTransbordoRankingMinutos is { } pesoTransbordo && double.IsFinite(pesoTransbordo))
|
||||
@@ -239,11 +249,14 @@ public partial class PlanificadorRutas
|
||||
? true
|
||||
: config.MostrarElevacionesRuta ?? true;
|
||||
_mostrarElevacionesRutaConfiguracion = _mostrarElevacionesRuta;
|
||||
_fuenteElevacionCalculo = migrarValoresPendientes &&
|
||||
(string.IsNullOrWhiteSpace(config.FuenteElevacionCalculo) ||
|
||||
string.Equals(config.FuenteElevacionCalculo, FuenteElevacionLocal5, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(config.FuenteElevacionCalculo, "local", StringComparison.OrdinalIgnoreCase))
|
||||
? FuenteElevacionLocal1
|
||||
var migrarFuenteElevacion =
|
||||
(config.VersionFuenteElevacion ?? 0) < VersionPersistenciaFuenteElevacion;
|
||||
_fuenteElevacionCalculo = migrarFuenteElevacion &&
|
||||
string.Equals(
|
||||
config.FuenteElevacionCalculo,
|
||||
FuenteElevacionLocal1,
|
||||
StringComparison.OrdinalIgnoreCase)
|
||||
? FuenteElevacionLocal5
|
||||
: NormalizarFuenteElevacion(config.FuenteElevacionCalculo);
|
||||
if (config.UmbralPendienteSubidaRuta is { } umbralSubida && double.IsFinite(umbralSubida))
|
||||
_umbralPendienteSubidaRuta = Math.Abs(umbralSubida - 3.0) < 0.0001
|
||||
@@ -376,7 +389,7 @@ public partial class PlanificadorRutas
|
||||
_pesoTransbordoRankingMinutos = 4.0;
|
||||
_factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||
_mostrarElevacionesRutaConfiguracion = true;
|
||||
_fuenteElevacionCalculo = FuenteElevacionLocal1;
|
||||
_fuenteElevacionCalculo = FuenteElevacionLocal5;
|
||||
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||
_umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||
@@ -429,7 +442,7 @@ public partial class PlanificadorRutas
|
||||
_pesoTransbordoRankingMinutos = 4.0;
|
||||
_factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
|
||||
_mostrarElevacionesRutaConfiguracion = true;
|
||||
_fuenteElevacionCalculo = FuenteElevacionLocal1;
|
||||
_fuenteElevacionCalculo = FuenteElevacionLocal5;
|
||||
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||
_umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
|
||||
|
||||
@@ -188,7 +188,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private const string FuenteElevacionLocal1 = "local1";
|
||||
private const string FuenteElevacionIdee = "idee";
|
||||
private const int TiempoMaximoIdeeSegundos = 10;
|
||||
private string _fuenteElevacionCalculo = FuenteElevacionLocal1;
|
||||
private string _fuenteElevacionCalculo = FuenteElevacionLocal5;
|
||||
|
||||
// IDEE: en el correo indican que 6 decimales es suficiente
|
||||
private const int ELEV_IDEE_DECIMALES_CACHE = 6;
|
||||
@@ -217,7 +217,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
return FuenteElevacionLocal5;
|
||||
}
|
||||
|
||||
return FuenteElevacionLocal1;
|
||||
return FuenteElevacionLocal5;
|
||||
}
|
||||
|
||||
private bool ElevacionLocal5Disponible
|
||||
@@ -1519,6 +1519,16 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
/// </summary>
|
||||
private async Task MostrarInspectorAsync(double lat, double lon)
|
||||
{
|
||||
_inspectorPuntoAnterior = _popupInspectorVisible
|
||||
? new PuntoInspectorAnterior(
|
||||
_inspectorLat,
|
||||
_inspectorLon,
|
||||
_inspectorElevIdee,
|
||||
_inspectorElevLocal5,
|
||||
_inspectorElevLocal2,
|
||||
_inspectorElevLocal1)
|
||||
: null;
|
||||
|
||||
var versionInspector = ++_versionInspector;
|
||||
_inspectorLat = lat;
|
||||
_inspectorLon = lon;
|
||||
@@ -1629,6 +1639,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
{
|
||||
_versionInspector++;
|
||||
_popupInspectorVisible = false;
|
||||
_inspectorPuntoAnterior = null;
|
||||
_inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
|
||||
|
||||
try { await JS.InvokeVoidAsync("rt.clearInspectorMarkers"); } catch { }
|
||||
|
||||
@@ -218,6 +218,26 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
string ItinerarioClave);
|
||||
|
||||
private List<ProximaSalidaVm> _nextDeps = new();
|
||||
|
||||
private bool HorariosTaxibusSimuladosVisibles
|
||||
=> _nextDeps.Any(salida => TaxibusDbus.EsExpedicionSimulada(salida.ExpId));
|
||||
|
||||
private static string ObtenerAvisoTaxibus(AlternativaRuta alternativa)
|
||||
{
|
||||
var lineasTaxibus = alternativa.AlternativaBus?.Tramos
|
||||
.Select(tramo => tramo.Variante.Linea)
|
||||
.Where(linea => TaxibusDbus.EsLinea(linea.Codigo))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (lineasTaxibus is not { Count: > 0 })
|
||||
return string.Empty;
|
||||
|
||||
return lineasTaxibus.Any(linea => linea.HorarioSimulado)
|
||||
? TaxibusDbus.MensajeHorarioSimulado
|
||||
: TaxibusDbus.MensajeReserva;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona toggle hora simulada.
|
||||
/// </summary>
|
||||
|
||||
@@ -299,6 +299,13 @@
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (HorariosTaxibusSimuladosVisibles)
|
||||
{
|
||||
<div class="taxibus-notice taxibus-notice--schedule" role="status">
|
||||
<strong>Taxibús:</strong> @TaxibusDbus.MensajeHorarioSimulado
|
||||
</div>
|
||||
}
|
||||
|
||||
<div style="max-height: 220px; overflow-y: auto; border: 1px solid rgba(255,255,255,.12); border-radius: 6px; padding: .35rem .5rem; scroll-behavior: smooth;">
|
||||
<ul style="margin:0; padding-left:1.1rem;">
|
||||
@foreach (var x in NextDepsOrdenados)
|
||||
@@ -401,6 +408,29 @@
|
||||
}
|
||||
</div>
|
||||
|
||||
@if (_inspectorPuntoAnterior is { } puntoAnterior)
|
||||
{
|
||||
var distanciaPuntoAnterior = CalcularDistanciaMetros(
|
||||
puntoAnterior.Latitud,
|
||||
puntoAnterior.Longitud,
|
||||
_inspectorLat,
|
||||
_inspectorLon);
|
||||
var elevacionPuntoAnterior = ObtenerElevacionPuntoInspectorAnterior(puntoAnterior);
|
||||
|
||||
<div class="inspector-previous-point">
|
||||
<div class="inspector-previous-point__distance">
|
||||
<b>Distancia al punto Z anterior:</b>
|
||||
@FormatearDistanciaPuntoInspector(distanciaPuntoAnterior)
|
||||
</div>
|
||||
<div class="inspector-previous-point__details">
|
||||
<span>Punto anterior</span>
|
||||
<span>Lat: @puntoAnterior.Latitud.ToString("0.000000", CultureInfo.InvariantCulture)</span>
|
||||
<span>Lon: @puntoAnterior.Longitud.ToString("0.000000", CultureInfo.InvariantCulture)</span>
|
||||
<span>@EtiquetaFuenteElevacionCalculo: @FormatearAlturaPuntoInspector(elevacionPuntoAnterior)</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (_inspectorLugaresCercanos.Count > 0)
|
||||
{
|
||||
<div class="inspector-address-list">
|
||||
@@ -741,6 +771,7 @@
|
||||
@if (_opcionesRuta.Count > 0 && _indiceAlternativaSeleccionada >= 0 && _indiceAlternativaSeleccionada < _opcionesRuta.Count)
|
||||
{
|
||||
var alt = _opcionesRuta[_indiceAlternativaSeleccionada];
|
||||
var avisoTaxibus = ObtenerAvisoTaxibus(alt);
|
||||
|
||||
@if (MostrarAlertaPendientesPositivasSignificativas)
|
||||
{
|
||||
@@ -753,6 +784,13 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (!string.IsNullOrWhiteSpace(avisoTaxibus))
|
||||
{
|
||||
<div class="taxibus-notice" role="status">
|
||||
<strong>Taxibús:</strong> @avisoTaxibus
|
||||
</div>
|
||||
}
|
||||
|
||||
@* ========= RESUMEN BONITO ARRIBA (único) ========= *@
|
||||
<p class="route-main-text">
|
||||
@if (alt.EsSoloAPie)
|
||||
@@ -1162,7 +1200,15 @@
|
||||
{
|
||||
<div class="history-derived-note">
|
||||
<span class="history-derived-marker" aria-hidden="true">*</span>
|
||||
<span>@textoReferenciaDerivada</span>
|
||||
<span class="history-derived-note__text">@textoReferenciaDerivada</span>
|
||||
@if (PuedeMostrarInformacionDerivacionHistorial)
|
||||
{
|
||||
<button type="button"
|
||||
class="history-derived-info-btn"
|
||||
@onclick="AbrirInformacionDerivacionHistorial">
|
||||
Info
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -1198,6 +1244,126 @@
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (_mostrarInformacionDerivacionHistorial &&
|
||||
_escenarioBaseDerivacionHistorial is not null &&
|
||||
PasoDerivacionSeleccionado is { } pasoDerivacion)
|
||||
{
|
||||
var cambiosRespectoOriginal = ObtenerCambiosDerivacion(
|
||||
_escenarioBaseDerivacionHistorial,
|
||||
pasoDerivacion.Escenario);
|
||||
var escenarioAnterior = ObtenerEscenarioAnteriorDerivacion(pasoDerivacion);
|
||||
var cambiosRespectoAnterior = ObtenerCambiosDerivacion(
|
||||
escenarioAnterior,
|
||||
pasoDerivacion.Escenario);
|
||||
var textoComparacionAnterior = pasoDerivacion.Numero <= 1
|
||||
? "Respecto al Historial Compartido original"
|
||||
: $"Respecto a la Derivación n.º {pasoDerivacion.Numero - 1}";
|
||||
|
||||
<div class="custom-modal-backdrop history-derivation-backdrop"
|
||||
@onclick="CerrarInformacionDerivacionHistorial">
|
||||
<div class="custom-modal-card history-derivation-modal"
|
||||
@onclick:stopPropagation="true">
|
||||
<div class="history-derivation-modal__header">
|
||||
<div>
|
||||
<div class="custom-modal-title">Cambios de la derivación</div>
|
||||
<div class="history-derivation-modal__source">
|
||||
Historial Compartido n.º @(IndiceHistorialCompartidoReferencia + 1)
|
||||
</div>
|
||||
</div>
|
||||
<button type="button"
|
||||
class="config-close-btn"
|
||||
title="Cerrar"
|
||||
aria-label="Cerrar"
|
||||
@onclick="CerrarInformacionDerivacionHistorial">
|
||||
×
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="history-derivation-steps" aria-label="Derivaciones registradas">
|
||||
@foreach (var paso in _pasosDerivacionHistorial)
|
||||
{
|
||||
var indicePaso = paso.Numero - 1;
|
||||
<button type="button"
|
||||
class="history-derivation-step @(indicePaso == _indicePasoDerivacionSeleccionado ? "history-derivation-step--selected" : "")"
|
||||
@onclick="() => SeleccionarPasoDerivacionHistorial(indicePaso)">
|
||||
Der. @paso.Numero
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="history-derivation-selected">
|
||||
<strong>Derivación n.º @pasoDerivacion.Numero</strong>
|
||||
<span>@pasoDerivacion.FechaUtc.ToLocalTime().ToString("dd/MM/yyyy HH:mm:ss")</span>
|
||||
</div>
|
||||
|
||||
<section class="history-derivation-comparison">
|
||||
<h6>Respecto al Historial Compartido original</h6>
|
||||
@if (cambiosRespectoOriginal.Count == 0)
|
||||
{
|
||||
<div class="history-derivation-no-changes">Sin cambios registrados.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var cambio in cambiosRespectoOriginal)
|
||||
{
|
||||
<div class="history-derivation-change">
|
||||
<div class="history-derivation-change__heading">
|
||||
<strong>@cambio.Campo</strong>
|
||||
@if (!string.IsNullOrWhiteSpace(cambio.Detalle))
|
||||
{
|
||||
<span>@cambio.Detalle</span>
|
||||
}
|
||||
</div>
|
||||
<div class="history-derivation-change__value">
|
||||
<span>Antes</span>
|
||||
<div>@cambio.ValorAnterior</div>
|
||||
</div>
|
||||
<div class="history-derivation-change__value">
|
||||
<span>Ahora</span>
|
||||
<div>@cambio.ValorActual</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
|
||||
@if (pasoDerivacion.Numero > 1)
|
||||
{
|
||||
<section class="history-derivation-comparison">
|
||||
<h6>@textoComparacionAnterior</h6>
|
||||
@if (cambiosRespectoAnterior.Count == 0)
|
||||
{
|
||||
<div class="history-derivation-no-changes">Sin cambios respecto a la anterior.</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
@foreach (var cambio in cambiosRespectoAnterior)
|
||||
{
|
||||
<div class="history-derivation-change">
|
||||
<div class="history-derivation-change__heading">
|
||||
<strong>@cambio.Campo</strong>
|
||||
@if (!string.IsNullOrWhiteSpace(cambio.Detalle))
|
||||
{
|
||||
<span>@cambio.Detalle</span>
|
||||
}
|
||||
</div>
|
||||
<div class="history-derivation-change__value">
|
||||
<span>Antes</span>
|
||||
<div>@cambio.ValorAnterior</div>
|
||||
</div>
|
||||
<div class="history-derivation-change__value">
|
||||
<span>Ahora</span>
|
||||
<div>@cambio.ValorActual</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</section>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
<!-- CONFIG -->
|
||||
@* <button class="config-toggle-btn" @onclick="ToggleConfigPanel">⚙</button>
|
||||
*@
|
||||
@@ -1309,7 +1475,7 @@
|
||||
<div class="line-filter">
|
||||
<label class="line-filter__toggle">
|
||||
<input type="checkbox" @bind="_activarRangoLineas" @bind:after="PersistirConfiguracionAsync" />
|
||||
<span>Aplicar lineas utilizables:</span>
|
||||
<span>Utiliza las siguientes líneas:</span>
|
||||
</label>
|
||||
|
||||
@if (_activarRangoLineas)
|
||||
@@ -1651,15 +1817,15 @@
|
||||
disabled="@_pintandoElevacionesRuta"
|
||||
@bind="_fuenteElevacionCalculo"
|
||||
@bind:after="OnFuenteElevacionChangedAsync">
|
||||
<option value="@FuenteElevacionIdee">Altura (G)</option>
|
||||
<option value="@FuenteElevacionIdee">G</option>
|
||||
<option value="@FuenteElevacionLocal5" disabled="@(!ElevacionLocal5Disponible)">
|
||||
Altura (L5)@(!ElevacionLocal5Disponible ? " - no disponible" : string.Empty)
|
||||
L5@(!ElevacionLocal5Disponible ? " - no disponible" : string.Empty)
|
||||
</option>
|
||||
<option value="@FuenteElevacionLocal2" disabled="@(!ElevacionLocal2Disponible)">
|
||||
Altura (L2)@(!ElevacionLocal2Disponible ? " - no disponible" : string.Empty)
|
||||
L2@(!ElevacionLocal2Disponible ? " - no disponible" : string.Empty)
|
||||
</option>
|
||||
<option value="@FuenteElevacionLocal1" disabled="@(!ElevacionLocal1Disponible)">
|
||||
Altura (L1)@(!ElevacionLocal1Disponible ? " - no disponible" : string.Empty)
|
||||
L1@(!ElevacionLocal1Disponible ? " - no disponible" : string.Empty)
|
||||
</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
@@ -16,6 +16,8 @@ public sealed class ArchivoImportacionDbus
|
||||
public IReadOnlyList<FilaTiempoRealCsv> TiemposReales { get; init; } = Array.Empty<FilaTiempoRealCsv>();
|
||||
public IReadOnlyList<FilaTransbordoCsv> Transbordos { get; init; } = Array.Empty<FilaTransbordoCsv>();
|
||||
public IReadOnlyList<FilaVehiculoCsv> Vehiculos { get; init; } = Array.Empty<FilaVehiculoCsv>();
|
||||
public IReadOnlySet<string> RutasConHorarioSimulado { get; init; } =
|
||||
new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public IReadOnlyDictionary<int, FilaParadaCsv> ParadasPorId { get; init; } = new Dictionary<int, FilaParadaCsv>();
|
||||
public IReadOnlyDictionary<string, FilaRutaCsv> RutasPorId { get; init; } =
|
||||
|
||||
@@ -17,6 +17,9 @@ public class LineaBus
|
||||
/// <summary>Color de fondo en formato ARGB (ej. FF0000FF).</summary>
|
||||
public string ColorArgb { get; set; } = "FF0000FF";
|
||||
|
||||
/// <summary>Indica si el horario activo se ha completado con una frecuencia provisional.</summary>
|
||||
public bool HorarioSimulado { get; set; }
|
||||
|
||||
/// <summary>Código del itinerario por defecto.</summary>
|
||||
public int CodigoItinerarioPorDefecto { get; set; }
|
||||
|
||||
|
||||
@@ -43,9 +43,10 @@ public sealed class GeocodificadorLocalServicio : IGeocodificadorLocalServicio
|
||||
if (tokens.Length == 0)
|
||||
return Array.Empty<LugarGeocodificado>();
|
||||
|
||||
maxResultados = Math.Clamp(maxResultados, 1, 30);
|
||||
maxResultados = Math.Clamp(maxResultados, 1, 50);
|
||||
|
||||
return indice
|
||||
.Where(entrada => EsDonostia(entrada.Municipio))
|
||||
.Select(entrada => new
|
||||
{
|
||||
Entrada = entrada,
|
||||
|
||||
@@ -534,17 +534,24 @@ namespace RutasDBUS.Servicios.Horarios
|
||||
/// </summary>
|
||||
private bool CoincideLineaBusqueda(FilaExpedicion exp, string idRutaPedido, string lineaPedida)
|
||||
{
|
||||
return CoincideLineaExpedicion(exp, idRutaPedido) ||
|
||||
CoincideLineaExpedicion(exp, lineaPedida);
|
||||
}
|
||||
|
||||
private bool CoincideLineaExpedicion(FilaExpedicion exp, string? lineaPedida)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(lineaPedida) ||
|
||||
string.Equals(lineaPedida, "?", StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var idRutaExp = NormalizarLinea(exp.IdRuta);
|
||||
var codigoVisibleExp = NormalizarLinea(ResolverCodigoLineaVisible(exp.IdRuta));
|
||||
var lineaNormalizada = NormalizarLinea(lineaPedida);
|
||||
|
||||
return (!string.IsNullOrWhiteSpace(idRutaPedido) &&
|
||||
!string.Equals(idRutaPedido, "?", StringComparison.Ordinal) &&
|
||||
(string.Equals(idRutaExp, idRutaPedido, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(codigoVisibleExp, idRutaPedido, StringComparison.OrdinalIgnoreCase))) ||
|
||||
(!string.IsNullOrWhiteSpace(lineaPedida) &&
|
||||
!string.Equals(lineaPedida, "?", StringComparison.Ordinal) &&
|
||||
(string.Equals(idRutaExp, lineaPedida, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(codigoVisibleExp, lineaPedida, StringComparison.OrdinalIgnoreCase)));
|
||||
return string.Equals(idRutaExp, lineaNormalizada, StringComparison.OrdinalIgnoreCase) ||
|
||||
string.Equals(codigoVisibleExp, lineaNormalizada, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -763,8 +770,7 @@ namespace RutasDBUS.Servicios.Horarios
|
||||
if (!activeCals.Contains(exp.IdCal))
|
||||
continue;
|
||||
|
||||
var lineaExp = NormalizarLinea(exp.IdRuta);
|
||||
if (!string.Equals(lineaExp, lineaPedida, StringComparison.OrdinalIgnoreCase))
|
||||
if (!CoincideLineaExpedicion(exp, lineaPedida))
|
||||
continue;
|
||||
|
||||
var sentidoExp = DireccionToSentido(exp.Direccion);
|
||||
|
||||
@@ -109,6 +109,11 @@ public sealed class ServicioArchivoImportacionDbus
|
||||
var tiemposReales = await ReadTiemposRealesAsync(FindEntry(zip, "tiempo_real.csv"), ct);
|
||||
var transbordos = await ReadTransbordosAsync(FindEntry(zip, "transbordos.csv"), ct);
|
||||
var vehiculos = await ReadVehiculosAsync(FindEntry(zip, "vehiculos.csv"), ct);
|
||||
var rutasConHorarioSimulado = SimuladorHorariosTaxibus.AplicarSiFaltanHorarios(
|
||||
calendarios,
|
||||
expediciones,
|
||||
rutas,
|
||||
tiemposParada);
|
||||
|
||||
return new ArchivoImportacionDbus
|
||||
{
|
||||
@@ -126,6 +131,7 @@ public sealed class ServicioArchivoImportacionDbus
|
||||
TiemposReales = tiemposReales,
|
||||
Transbordos = transbordos,
|
||||
Vehiculos = vehiculos,
|
||||
RutasConHorarioSimulado = rutasConHorarioSimulado,
|
||||
ParadasPorId = paradas.ToDictionary(x => x.Id),
|
||||
RutasPorId = rutas.ToDictionary(x => x.Id, StringComparer.OrdinalIgnoreCase),
|
||||
ExpedicionesPorId = expediciones.ToDictionary(x => x.Id, StringComparer.OrdinalIgnoreCase),
|
||||
|
||||
200
RutasDBUS/Servicios/Importacion/SimuladorHorariosTaxibus.cs
Normal file
200
RutasDBUS/Servicios/Importacion/SimuladorHorariosTaxibus.cs
Normal file
@@ -0,0 +1,200 @@
|
||||
using RutasDBUS.Modelos.Importacion;
|
||||
|
||||
namespace RutasDBUS.Servicios.Importacion;
|
||||
|
||||
/// <summary>
|
||||
/// Completa las plantillas sin horario de TB6 y TB7 con una frecuencia provisional.
|
||||
/// </summary>
|
||||
internal static class SimuladorHorariosTaxibus
|
||||
{
|
||||
private const string IdCalendario = "TAXIBUS_SIM_TODOS_LOS_DIAS";
|
||||
private const int FrecuenciaMinutos = 30;
|
||||
private const double VelocidadMetrosSegundo = 16_000.0 / 3_600.0;
|
||||
private const int SegundosMinimosEntreParadas = 30;
|
||||
|
||||
public static IReadOnlySet<string> AplicarSiFaltanHorarios(
|
||||
List<FilaCalendarioCsv> calendarios,
|
||||
List<FilaExpedicionCsv> expediciones,
|
||||
IReadOnlyList<FilaRutaCsv> rutas,
|
||||
List<FilaTiempoParadaCsv> tiemposParada)
|
||||
{
|
||||
var rutasSimuladas = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var tiemposOriginalesPorExpedicion = tiemposParada
|
||||
.GroupBy(tiempo => tiempo.IdExp, StringComparer.OrdinalIgnoreCase)
|
||||
.ToDictionary(
|
||||
grupo => grupo.Key,
|
||||
grupo => grupo.OrderBy(tiempo => tiempo.SecPar).ToList(),
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var ruta in rutas.Where(EsRutaTaxibus))
|
||||
{
|
||||
var expedicionesRuta = expediciones
|
||||
.Where(expedicion => string.Equals(
|
||||
expedicion.IdRuta,
|
||||
ruta.Id,
|
||||
StringComparison.OrdinalIgnoreCase))
|
||||
.OrderBy(expedicion => expedicion.Id, StringComparer.OrdinalIgnoreCase)
|
||||
.ToList();
|
||||
|
||||
if (expedicionesRuta.Count == 0 ||
|
||||
TieneHorarioOficial(expedicionesRuta, tiemposOriginalesPorExpedicion))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var plantillas = expedicionesRuta
|
||||
.Select(expedicion => new PlantillaTaxibus(
|
||||
expedicion,
|
||||
tiemposOriginalesPorExpedicion.TryGetValue(expedicion.Id, out var tiempos)
|
||||
? tiempos
|
||||
: new List<FilaTiempoParadaCsv>()))
|
||||
.Where(plantilla => plantilla.Tiempos.Count >= 2)
|
||||
.ToList();
|
||||
|
||||
if (plantillas.Count == 0)
|
||||
continue;
|
||||
|
||||
var idsPlantilla = expedicionesRuta
|
||||
.Select(expedicion => expedicion.Id)
|
||||
.ToHashSet(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
expediciones.RemoveAll(expedicion => idsPlantilla.Contains(expedicion.Id));
|
||||
tiemposParada.RemoveAll(tiempo => idsPlantilla.Contains(tiempo.IdExp));
|
||||
|
||||
for (var indicePlantilla = 0; indicePlantilla < plantillas.Count; indicePlantilla++)
|
||||
{
|
||||
CrearExpedicionesSimuladas(
|
||||
ruta,
|
||||
plantillas[indicePlantilla],
|
||||
indicePlantilla,
|
||||
expediciones,
|
||||
tiemposParada);
|
||||
}
|
||||
|
||||
rutasSimuladas.Add(ruta.Id);
|
||||
}
|
||||
|
||||
if (rutasSimuladas.Count > 0)
|
||||
{
|
||||
calendarios.RemoveAll(calendario =>
|
||||
calendario.Id.Equals(IdCalendario, StringComparison.OrdinalIgnoreCase));
|
||||
calendarios.Add(new FilaCalendarioCsv(
|
||||
IdCalendario,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
2,
|
||||
new DateOnly(2000, 1, 1),
|
||||
new DateOnly(2099, 12, 31)));
|
||||
}
|
||||
|
||||
return rutasSimuladas;
|
||||
}
|
||||
|
||||
private static bool EsRutaTaxibus(FilaRutaCsv ruta)
|
||||
=> TaxibusDbus.EsLinea(ruta.Abreviatura) || TaxibusDbus.EsLinea(ruta.Id);
|
||||
|
||||
private static bool TieneHorarioOficial(
|
||||
IReadOnlyList<FilaExpedicionCsv> expediciones,
|
||||
IReadOnlyDictionary<string, List<FilaTiempoParadaCsv>> tiemposPorExpedicion)
|
||||
{
|
||||
foreach (var expedicion in expediciones)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(expedicion.IdCal) &&
|
||||
!expedicion.IdCal.Equals("0000", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (expedicion.HSalida is { } salida && salida != TimeOnly.MinValue)
|
||||
return true;
|
||||
|
||||
if (!tiemposPorExpedicion.TryGetValue(expedicion.Id, out var tiempos))
|
||||
continue;
|
||||
|
||||
if (tiempos.Any(tiempo =>
|
||||
EsHoraReal(tiempo.HoraLlegada) || EsHoraReal(tiempo.HoraSalida)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static bool EsHoraReal(TimeOnly? hora)
|
||||
=> hora is { } valor && valor != TimeOnly.MinValue;
|
||||
|
||||
private static void CrearExpedicionesSimuladas(
|
||||
FilaRutaCsv ruta,
|
||||
PlantillaTaxibus plantilla,
|
||||
int indicePlantilla,
|
||||
ICollection<FilaExpedicionCsv> expediciones,
|
||||
ICollection<FilaTiempoParadaCsv> tiemposParada)
|
||||
{
|
||||
var cantidadSalidas = 24 * 60 / FrecuenciaMinutos;
|
||||
for (var indiceSalida = 0; indiceSalida < cantidadSalidas; indiceSalida++)
|
||||
{
|
||||
var segundosInicio = indiceSalida * FrecuenciaMinutos * 60;
|
||||
var horaInicio = HoraDesdeSegundos(segundosInicio);
|
||||
var idExpedicion =
|
||||
$"{TaxibusDbus.PrefijoExpedicionSimulada}{ruta.Id}_{indicePlantilla:00}_{indiceSalida:00}";
|
||||
|
||||
expediciones.Add(new FilaExpedicionCsv(
|
||||
idExpedicion,
|
||||
plantilla.Expedicion.IdRuta,
|
||||
plantilla.Expedicion.DirDestino,
|
||||
plantilla.Expedicion.Direccion,
|
||||
plantilla.Expedicion.CambioExp,
|
||||
plantilla.Expedicion.IdPatronExp,
|
||||
horaInicio,
|
||||
IdCalendario));
|
||||
|
||||
var segundosPaso = segundosInicio;
|
||||
for (var indiceParada = 0; indiceParada < plantilla.Tiempos.Count; indiceParada++)
|
||||
{
|
||||
var tiempoPlantilla = plantilla.Tiempos[indiceParada];
|
||||
var horaPaso = HoraDesdeSegundos(segundosPaso);
|
||||
|
||||
tiemposParada.Add(new FilaTiempoParadaCsv(
|
||||
idExpedicion,
|
||||
horaPaso,
|
||||
horaPaso,
|
||||
tiempoPlantilla.DistanciaSiguienteParada,
|
||||
tiempoPlantilla.IdTiempoPar,
|
||||
tiempoPlantilla.SecPar,
|
||||
tiempoPlantilla.TipoSubidaPar,
|
||||
tiempoPlantilla.TipoBajadaPar,
|
||||
tiempoPlantilla.NuevaDirDestinoPar));
|
||||
|
||||
if (indiceParada < plantilla.Tiempos.Count - 1)
|
||||
segundosPaso += CalcularSegundosEntreParadas(tiempoPlantilla.DistanciaSiguienteParada);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static int CalcularSegundosEntreParadas(double distanciaMetros)
|
||||
{
|
||||
if (!double.IsFinite(distanciaMetros) || distanciaMetros <= 0)
|
||||
return SegundosMinimosEntreParadas;
|
||||
|
||||
return Math.Max(
|
||||
SegundosMinimosEntreParadas,
|
||||
(int)Math.Round(
|
||||
distanciaMetros / VelocidadMetrosSegundo,
|
||||
MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
private static TimeOnly HoraDesdeSegundos(int segundos)
|
||||
{
|
||||
var segundosDia = ((segundos % (24 * 3600)) + (24 * 3600)) % (24 * 3600);
|
||||
return TimeOnly.FromTimeSpan(TimeSpan.FromSeconds(segundosDia));
|
||||
}
|
||||
|
||||
private sealed record PlantillaTaxibus(
|
||||
FilaExpedicionCsv Expedicion,
|
||||
IReadOnlyList<FilaTiempoParadaCsv> Tiempos);
|
||||
}
|
||||
@@ -3363,8 +3363,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
.Where(x => x.DistanciaPieTotalAprox <= limiteAhorroClaro)
|
||||
.OrderBy(x => x.DistanciaPieTotalAprox)
|
||||
.ThenBy(x => DuracionVisibleOrdenacion(x))
|
||||
.ThenBy(x => x.PuntuacionCoste)
|
||||
.Take(8));
|
||||
.ThenBy(x => x.PuntuacionCoste));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,7 @@ public sealed class CreadorRedTransporteDbus
|
||||
: CodigoSinDecimal(row.Id),
|
||||
Nombre = !string.IsNullOrWhiteSpace(row.Nombre) ? row.Nombre : (row.Descripcion ?? ""),
|
||||
ColorArgb = NormalizarColorArgb(row.Color),
|
||||
HorarioSimulado = archive.RutasConHorarioSimulado.Contains(row.Id ?? ""),
|
||||
CodigoItinerarioPorDefecto = 0
|
||||
};
|
||||
|
||||
|
||||
32
RutasDBUS/Servicios/TaxibusDbus.cs
Normal file
32
RutasDBUS/Servicios/TaxibusDbus.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace RutasDBUS.Servicios;
|
||||
|
||||
/// <summary>
|
||||
/// Identificacion y textos comunes de las lineas Taxibus.
|
||||
/// </summary>
|
||||
public static class TaxibusDbus
|
||||
{
|
||||
public const string PrefijoExpedicionSimulada = "TAXIBUS_SIM_";
|
||||
public const string MensajeReserva = "Esta l\u00ednea requiere solicitud de reserva.";
|
||||
public const string MensajeHorarioSimulado =
|
||||
"Horario provisional simulado cada 30 min. Esta l\u00ednea requiere solicitud de reserva.";
|
||||
|
||||
public static bool EsLinea(string? codigo)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(codigo))
|
||||
return false;
|
||||
|
||||
var normalizado = codigo.Trim();
|
||||
var separador = normalizado.IndexOf('.');
|
||||
if (separador > 0)
|
||||
normalizado = normalizado[..separador];
|
||||
|
||||
return normalizado.Equals("TB6", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizado.Equals("TB7", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizado.Equals("6", StringComparison.OrdinalIgnoreCase) ||
|
||||
normalizado.Equals("7", StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public static bool EsExpedicionSimulada(string? idExpedicion)
|
||||
=> !string.IsNullOrWhiteSpace(idExpedicion) &&
|
||||
idExpedicion.StartsWith(PrefijoExpedicionSimulada, StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
@@ -53,6 +53,33 @@
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.inspector-previous-point {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
margin: 7px 0 6px;
|
||||
padding: 7px 0 2px;
|
||||
border-top: 1px solid rgba(148, 163, 184, .25);
|
||||
}
|
||||
|
||||
.inspector-previous-point__distance {
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.inspector-previous-point__details {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px 8px;
|
||||
color: #94a3b8;
|
||||
font-size: .68rem;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.inspector-previous-point__details > span:first-child {
|
||||
flex-basis: 100%;
|
||||
color: #cbd5e1;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.rt-inspector-place-icon {
|
||||
border: 0 !important;
|
||||
background: transparent !important;
|
||||
@@ -373,6 +400,19 @@ html, body {
|
||||
cursor: default !important;
|
||||
}
|
||||
|
||||
/* Leaflet enfoca cada tramo SVG con una caja rectangular al pulsarlo. */
|
||||
.leaflet-interactive.rt-elev-segment:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.rt-elev-segment-halo {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.rt-elev-segment-halo--outer {
|
||||
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, .75));
|
||||
}
|
||||
|
||||
/* Evitar cursor de mano en puntos, polilíneas, polígonos de Leaflet */
|
||||
.leaflet-marker-icon,
|
||||
.leaflet-marker-shadow,
|
||||
@@ -946,6 +986,27 @@ html, body {
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.taxibus-notice {
|
||||
margin: 0 0 .45rem;
|
||||
padding: .38rem .5rem;
|
||||
border-left: 3px solid #38bdf8;
|
||||
border-radius: 4px;
|
||||
background: rgba(14, 116, 144, .2);
|
||||
color: #e0f2fe;
|
||||
font-size: .72rem;
|
||||
font-weight: 600;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.taxibus-notice strong {
|
||||
color: #7dd3fc;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.taxibus-notice--schedule {
|
||||
margin: .3rem 0 .4rem;
|
||||
}
|
||||
|
||||
/* Lista de alternativas de ruta */
|
||||
|
||||
.route-alt-list {
|
||||
@@ -1243,13 +1304,185 @@ html, body {
|
||||
}
|
||||
|
||||
.history-derived-note {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
display: grid;
|
||||
grid-template-columns: auto minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: .35rem;
|
||||
width: 100%;
|
||||
font-size: .76rem;
|
||||
color: #fcd34d;
|
||||
}
|
||||
|
||||
.history-derived-note__text {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.history-derived-info-btn {
|
||||
flex: 0 0 auto;
|
||||
min-height: 26px;
|
||||
padding: .2rem .5rem;
|
||||
border: 1px solid rgba(251, 191, 36, .5);
|
||||
border-radius: 5px;
|
||||
background: rgba(120, 53, 15, .24);
|
||||
color: #fde68a;
|
||||
font-size: .7rem;
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.history-derived-info-btn:hover,
|
||||
.history-derived-info-btn:focus-visible {
|
||||
border-color: rgba(251, 191, 36, .86);
|
||||
background: rgba(146, 64, 14, .38);
|
||||
color: #fef3c7;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.history-derivation-backdrop {
|
||||
z-index: 5700;
|
||||
}
|
||||
|
||||
.custom-modal-card.history-derivation-modal {
|
||||
width: min(620px, calc(100vw - 2rem));
|
||||
max-height: min(760px, calc(100dvh - 2rem));
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.history-derivation-modal__header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: .75rem;
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
|
||||
.history-derivation-modal__header .custom-modal-title {
|
||||
margin-bottom: .15rem;
|
||||
}
|
||||
|
||||
.history-derivation-modal__source {
|
||||
color: #94a3b8;
|
||||
font-size: .75rem;
|
||||
}
|
||||
|
||||
.history-derivation-steps {
|
||||
display: flex;
|
||||
gap: .35rem;
|
||||
padding: .15rem 0 .55rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.history-derivation-step {
|
||||
flex: 0 0 auto;
|
||||
min-height: 30px;
|
||||
padding: .25rem .55rem;
|
||||
border: 1px solid rgba(148, 163, 184, .32);
|
||||
border-radius: 5px;
|
||||
background: rgba(30, 41, 59, .52);
|
||||
color: #cbd5e1;
|
||||
font-size: .72rem;
|
||||
font-weight: 650;
|
||||
}
|
||||
|
||||
.history-derivation-step:hover,
|
||||
.history-derivation-step:focus-visible {
|
||||
border-color: rgba(56, 189, 248, .7);
|
||||
color: #e0f2fe;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.history-derivation-step--selected {
|
||||
border-color: rgba(251, 191, 36, .8);
|
||||
background: rgba(120, 53, 15, .3);
|
||||
color: #fef3c7;
|
||||
}
|
||||
|
||||
.history-derivation-selected {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: .75rem;
|
||||
padding: .5rem 0;
|
||||
border-top: 1px solid rgba(148, 163, 184, .2);
|
||||
border-bottom: 1px solid rgba(148, 163, 184, .2);
|
||||
color: #f8fafc;
|
||||
}
|
||||
|
||||
.history-derivation-selected span {
|
||||
color: #94a3b8;
|
||||
font-size: .72rem;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.history-derivation-comparison {
|
||||
padding-top: .7rem;
|
||||
}
|
||||
|
||||
.history-derivation-comparison + .history-derivation-comparison {
|
||||
margin-top: .25rem;
|
||||
border-top: 1px solid rgba(148, 163, 184, .2);
|
||||
}
|
||||
|
||||
.history-derivation-comparison h6 {
|
||||
margin: 0 0 .35rem;
|
||||
color: #cbd5e1;
|
||||
font-size: .78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.history-derivation-change {
|
||||
padding: .48rem 0;
|
||||
border-bottom: 1px solid rgba(148, 163, 184, .14);
|
||||
}
|
||||
|
||||
.history-derivation-change:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.history-derivation-change__heading {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: .75rem;
|
||||
margin-bottom: .28rem;
|
||||
color: #e2e8f0;
|
||||
font-size: .76rem;
|
||||
}
|
||||
|
||||
.history-derivation-change__heading span {
|
||||
color: #fbbf24;
|
||||
font-size: .7rem;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.history-derivation-change__value {
|
||||
display: grid;
|
||||
grid-template-columns: 54px minmax(0, 1fr);
|
||||
gap: .4rem;
|
||||
padding: .08rem 0;
|
||||
color: #cbd5e1;
|
||||
font-size: .72rem;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.history-derivation-change__value > span {
|
||||
color: #64748b;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.history-derivation-change__value > div {
|
||||
min-width: 0;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.history-derivation-no-changes {
|
||||
padding: .35rem 0;
|
||||
color: #94a3b8;
|
||||
font-size: .74rem;
|
||||
}
|
||||
|
||||
.history-item-action,
|
||||
.history-item-delete {
|
||||
flex: 0 0 32px;
|
||||
|
||||
@@ -100,8 +100,137 @@ window.rt.ensureRouteLayer = function () {
|
||||
return true;
|
||||
};
|
||||
|
||||
window.rt.clearElevationSegmentSelection = function () {
|
||||
const selected = window.rt._selectedElevationSegment;
|
||||
if (!selected) return;
|
||||
|
||||
try {
|
||||
if (selected.line && selected.originalStyle) {
|
||||
selected.line.setStyle(selected.originalStyle);
|
||||
}
|
||||
|
||||
if (selected.group && Array.isArray(selected.endpoints)) {
|
||||
selected.endpoints.forEach(function (endpoint) {
|
||||
selected.group.removeLayer(endpoint);
|
||||
});
|
||||
}
|
||||
|
||||
if (selected.group && Array.isArray(selected.halos)) {
|
||||
selected.halos.forEach(function (halo) {
|
||||
selected.group.removeLayer(halo);
|
||||
});
|
||||
}
|
||||
} catch { }
|
||||
|
||||
window.rt._selectedElevationSegment = null;
|
||||
};
|
||||
|
||||
window.rt.ensureElevationSegmentSelectionDismiss = function () {
|
||||
if (window.rt._elevationSelectionDismissHandler) return;
|
||||
|
||||
window.rt._elevationSelectionDismissHandler = function (event) {
|
||||
const selected = window.rt._selectedElevationSegment;
|
||||
if (!selected) return;
|
||||
|
||||
const selectedPath = selected.line && selected.line._path;
|
||||
const target = event && event.target;
|
||||
if (selectedPath && target &&
|
||||
(target === selectedPath ||
|
||||
(typeof selectedPath.contains === "function" && selectedPath.contains(target)))) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.rt.clearElevationSegmentSelection();
|
||||
};
|
||||
|
||||
document.addEventListener(
|
||||
"click",
|
||||
window.rt._elevationSelectionDismissHandler,
|
||||
true);
|
||||
};
|
||||
|
||||
window.rt.ensureElevationSegmentSelectionDismiss();
|
||||
|
||||
window.rt.selectElevationSegment = function (
|
||||
routeId,
|
||||
line,
|
||||
coords,
|
||||
group,
|
||||
originalStyle,
|
||||
baseWeight) {
|
||||
window.rt.clearElevationSegmentSelection();
|
||||
|
||||
if (!line || !Array.isArray(coords) || coords.length < 2 || !group) return;
|
||||
|
||||
const selectionColor = originalStyle && originalStyle.color
|
||||
? originalStyle.color
|
||||
: "#dc2626";
|
||||
const selectedWeight = Math.max(6, Number(baseWeight || 4) + 2);
|
||||
const outerHalo = window.L.polyline(coords, {
|
||||
color: "#111827",
|
||||
weight: selectedWeight + 7,
|
||||
opacity: 0.94,
|
||||
dashArray: null,
|
||||
lineCap: "round",
|
||||
interactive: false,
|
||||
className: "rt-elev-segment-halo rt-elev-segment-halo--outer"
|
||||
});
|
||||
const innerHalo = window.L.polyline(coords, {
|
||||
color: "#f8fafc",
|
||||
weight: selectedWeight + 4,
|
||||
opacity: 0.96,
|
||||
dashArray: null,
|
||||
lineCap: "round",
|
||||
interactive: false,
|
||||
className: "rt-elev-segment-halo rt-elev-segment-halo--inner"
|
||||
});
|
||||
outerHalo.addTo(group);
|
||||
innerHalo.addTo(group);
|
||||
|
||||
line.setStyle({
|
||||
color: selectionColor,
|
||||
weight: selectedWeight,
|
||||
opacity: 1,
|
||||
dashArray: null,
|
||||
lineCap: "round"
|
||||
});
|
||||
|
||||
const endpointOptions = {
|
||||
radius: 4,
|
||||
color: "#ffffff",
|
||||
weight: 2,
|
||||
opacity: 1,
|
||||
fillColor: selectionColor,
|
||||
fillOpacity: 1,
|
||||
interactive: false,
|
||||
className: "rt-elev-segment-endpoint"
|
||||
};
|
||||
const endpoints = [
|
||||
window.L.circleMarker(coords[0], endpointOptions),
|
||||
window.L.circleMarker(coords[coords.length - 1], endpointOptions)
|
||||
];
|
||||
|
||||
endpoints.forEach(function (endpoint) {
|
||||
endpoint.addTo(group);
|
||||
});
|
||||
try { line.bringToFront(); } catch { }
|
||||
endpoints.forEach(function (endpoint) {
|
||||
try { endpoint.bringToFront(); } catch { }
|
||||
});
|
||||
|
||||
window.rt._selectedElevationSegment = {
|
||||
routeId,
|
||||
line,
|
||||
group,
|
||||
halos: [outerHalo, innerHalo],
|
||||
endpoints,
|
||||
originalStyle
|
||||
};
|
||||
};
|
||||
|
||||
window.rt.clearRoutes = function () {
|
||||
if (!window.rt.ensureRouteLayer()) return;
|
||||
window.rt.clearElevationSegmentSelection();
|
||||
window.rt._routeLayer.clearLayers();
|
||||
window.rt._routeLines = {};
|
||||
window.rt._routeCoords = {};
|
||||
@@ -114,6 +243,11 @@ window.rt.clearRoutes = function () {
|
||||
window.rt.drawRoute = function (id, coords, options) {
|
||||
if (!window.rt.ensureRouteLayer()) return;
|
||||
|
||||
if (window.rt._selectedElevationSegment &&
|
||||
window.rt._selectedElevationSegment.routeId === id) {
|
||||
window.rt.clearElevationSegmentSelection();
|
||||
}
|
||||
|
||||
if (window.rt._routeLines[id]) {
|
||||
window.rt._routeLayer.removeLayer(window.rt._routeLines[id]);
|
||||
delete window.rt._routeLines[id];
|
||||
@@ -241,11 +375,19 @@ window.rt.drawRoute = function (id, coords, options) {
|
||||
weight,
|
||||
opacity,
|
||||
lineCap: lineCap || "round",
|
||||
className: "rt-elev-segment",
|
||||
bubblingMouseEvents: false
|
||||
};
|
||||
if (dashArray) segmentOpts.dashArray = dashArray;
|
||||
|
||||
const segmentLine = buildLine(segmentCoords, segmentOpts);
|
||||
const originalSegmentStyle = {
|
||||
color: segmentColor,
|
||||
weight,
|
||||
opacity,
|
||||
lineCap: lineCap || "round",
|
||||
dashArray: dashArray || null
|
||||
};
|
||||
const label = elevationTooltip(segment, 0.5);
|
||||
|
||||
if (label) {
|
||||
@@ -262,6 +404,21 @@ window.rt.drawRoute = function (id, coords, options) {
|
||||
segmentLine.on("mouseover", updateTooltipAtCursor);
|
||||
segmentLine.on("mousemove", updateTooltipAtCursor);
|
||||
segmentLine.on("click", function (e) {
|
||||
window.rt.selectElevationSegment(
|
||||
id,
|
||||
segmentLine,
|
||||
segmentCoords,
|
||||
group,
|
||||
originalSegmentStyle,
|
||||
weight);
|
||||
window.requestAnimationFrame(function () {
|
||||
try {
|
||||
if (segmentLine._path && typeof segmentLine._path.blur === "function") {
|
||||
segmentLine._path.blur();
|
||||
}
|
||||
} catch { }
|
||||
});
|
||||
|
||||
if (!window.rt.isMobileLayout || !window.rt.isMobileLayout()) return;
|
||||
updateTooltipAtCursor(e);
|
||||
try { segmentLine.openTooltip(e && e.latlng); } catch { }
|
||||
|
||||
Reference in New Issue
Block a user