- Rediseñada la configuración de pendientes con un panel compacto accesible desde la rueda.
- Corregida la posición del panel para que no salga fuera de la pantalla. - Renombrado el interruptor a Visualizar Pendientes. - Aplicado el porcentaje de subida como umbral para calcular el esfuerzo. - Mostrado el umbral de subida utilizado dentro de la tabla de elevación. - Añadido al aviso el total de distancia con pendiente y la altura subida en metros y pisos. - Evitada la duplicación de acumulados al repintar las pendientes. - Cambiados los valores por defecto a subida 2 %, bajada 5 % y coeficiente 8,2. - Añadida la distancia equilibrada andando al detalle técnico. - Mostradas las distancias en kilómetros con tres decimales. - Mostrada la fecha del ZIP activo en el acceso de gestión del modelo DBUS. - Añadidas migraciones para actualizar los antiguos valores predeterminados.
This commit is contained in:
@@ -7,14 +7,14 @@
|
|||||||
<base href="/" />
|
<base href="/" />
|
||||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
||||||
<link rel="stylesheet" href="RutasDBUS.styles.css" />
|
<link rel="stylesheet" href="RutasDBUS.styles.css" />
|
||||||
<link rel="stylesheet" href="app.css?v=20260716m3" />
|
<link rel="stylesheet" href="app.css?v=20260720h" />
|
||||||
<link rel="icon" type="image/png" href="favicon.png" />
|
<link rel="icon" type="image/png" href="favicon.png" />
|
||||||
<HeadOutlet />
|
<HeadOutlet />
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<Routes />
|
<Routes />
|
||||||
<script src="app.js?v=20260716m3" defer></script>
|
<script src="app.js?v=20260720h" defer></script>
|
||||||
<script src="_framework/blazor.web.js"></script>
|
<script src="_framework/blazor.web.js"></script>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
|
|||||||
using LeafletForBlazor;
|
using LeafletForBlazor;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
using RutasDBUS.Modelos.Geocodificacion;
|
||||||
using RutasDBUS.Modelos.Planificacion;
|
using RutasDBUS.Modelos.Planificacion;
|
||||||
using RutasDBUS.Modelos.RedBus;
|
using RutasDBUS.Modelos.RedBus;
|
||||||
using RutasDBUS.Servicios;
|
using RutasDBUS.Servicios;
|
||||||
@@ -174,6 +175,8 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
|
|||||||
if (it is null) return;
|
if (it is null) return;
|
||||||
|
|
||||||
await PintarItItemAsync(it);
|
await PintarItItemAsync(it);
|
||||||
|
var coordenadas = ConstruirCoordenadasItinerario(it);
|
||||||
|
await FitRoutesJsAsync(coordenadas);
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Quita it seleccionado.
|
/// Quita it seleccionado.
|
||||||
@@ -254,7 +257,8 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
|
|||||||
private double _inspectorLat, _inspectorLon;
|
private double _inspectorLat, _inspectorLon;
|
||||||
private double? _inspectorElev;
|
private double? _inspectorElev;
|
||||||
private double? _inspectorElevIdee;
|
private double? _inspectorElevIdee;
|
||||||
private string? _inspectorCallejero;
|
private IReadOnlyList<LugarGeocodificadoCercano> _inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
|
||||||
|
private int _versionInspector;
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -47,16 +47,22 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private int _nivelBrilloMapa = 0;
|
private int _nivelBrilloMapa = 0;
|
||||||
|
|
||||||
// CONFIG
|
// CONFIG
|
||||||
|
private const double FactorPenalizacionDistanciaPiePorDefecto = 1.35;
|
||||||
|
private const double CoeficienteEsfuerzoAlturaPorDefecto = 8.2;
|
||||||
|
private const double UmbralPendienteSubidaRutaPorDefecto = 2.0;
|
||||||
|
private const double UmbralPendienteBajadaRutaPorDefecto = 5.0;
|
||||||
|
private const double UmbralPisosPendienteRutaPorDefecto = 2.0;
|
||||||
|
private const double MetrosPorPiso = 3.0;
|
||||||
|
|
||||||
private double _velocidadPieKmH = 3.6;
|
private double _velocidadPieKmH = 3.6;
|
||||||
private double _factorPenalizacionDistanciaPie = 1.6;
|
private double _factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
|
||||||
private double _parametroPenalizacionDistanciaPieMetros = 100.0;
|
private double _parametroPenalizacionDistanciaPieMetros = 100.0;
|
||||||
private const double CoeficienteEsfuerzoAlturaPorDefecto = 5.2;
|
|
||||||
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
private double _pesoTransbordoRankingMinutos = 4.0;
|
private double _pesoTransbordoRankingMinutos = 4.0;
|
||||||
private double _factorEquilibradoAndarBus = 12.0;
|
private double _factorEquilibradoAndarBus = 12.0;
|
||||||
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.60";
|
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.35";
|
||||||
private string _parametroPenalizacionDistanciaPieTextoEdicion = "100";
|
private string _parametroPenalizacionDistanciaPieTextoEdicion = "100";
|
||||||
private string _coeficienteEsfuerzoAlturaTextoEdicion = "5.20";
|
private string _coeficienteEsfuerzoAlturaTextoEdicion = "8.20";
|
||||||
private string _pesoTransbordoRankingTextoEdicion = "4,0";
|
private string _pesoTransbordoRankingTextoEdicion = "4,0";
|
||||||
private string _factorEquilibradoAndarBusTextoEdicion = "12";
|
private string _factorEquilibradoAndarBusTextoEdicion = "12";
|
||||||
private double _velocidadBusKmH = 16.0;
|
private double _velocidadBusKmH = 16.0;
|
||||||
@@ -222,10 +228,11 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
? _coeficienteEsfuerzoAltura
|
? _coeficienteEsfuerzoAltura
|
||||||
: CoeficienteEsfuerzoAlturaPorDefecto;
|
: CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
|
|
||||||
var pendiente = pendientePorcentaje.Value / 100.0;
|
var umbralSubida = NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta);
|
||||||
if (pendiente <= 0)
|
if (pendientePorcentaje.Value < umbralSubida)
|
||||||
return distanciaMetros.Value;
|
return distanciaMetros.Value;
|
||||||
|
|
||||||
|
var pendiente = pendientePorcentaje.Value / 100.0;
|
||||||
var factorConPotencia = Math.Pow(1.0 + pendiente, coeficiente);
|
var factorConPotencia = Math.Pow(1.0 + pendiente, coeficiente);
|
||||||
var esfuerzo = distanciaMetros.Value * factorConPotencia;
|
var esfuerzo = distanciaMetros.Value * factorConPotencia;
|
||||||
return double.IsFinite(esfuerzo)
|
return double.IsFinite(esfuerzo)
|
||||||
@@ -351,9 +358,18 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private TimeSpan? _tiempoUltimoCalculoRuta = null;
|
private TimeSpan? _tiempoUltimoCalculoRuta = null;
|
||||||
private bool _mostrarElevacionesRuta = false;
|
private bool _mostrarElevacionesRuta = false;
|
||||||
private bool _pintandoElevacionesRuta = false;
|
private bool _pintandoElevacionesRuta = false;
|
||||||
|
private bool _mostrarConfiguracionPendientesRuta = false;
|
||||||
private bool _panelRutasMovilContraido = false;
|
private bool _panelRutasMovilContraido = false;
|
||||||
private double _umbralPendienteSubidaRuta = 3.0;
|
private double _umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||||
private double _umbralPendienteBajadaRuta = 3.0;
|
private double _umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||||
|
private double _umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
||||||
|
private double _alturaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||||
|
private double _distanciaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||||
|
|
||||||
|
private bool MostrarAvisoPendientesPositivasSignificativas =>
|
||||||
|
_mostrarElevacionesRuta &&
|
||||||
|
_alturaPendientePositivaSignificativaRutaMetros >
|
||||||
|
(NormalizarUmbralPisosPendienteRuta(_umbralPisosPendienteRuta) * MetrosPorPiso);
|
||||||
private readonly Dictionary<string, List<Parada>> _paradasItinerarioVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, List<Parada>> _paradasItinerarioVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private readonly Dictionary<string, List<Parada>> _paradasRutaVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
private readonly Dictionary<string, List<Parada>> _paradasRutaVisiblesPorId = new(StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
@@ -987,9 +1003,16 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ajusta rutas js.
|
/// Ajusta rutas js.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task FitRoutesJsAsync()
|
private async Task FitRoutesJsAsync(IReadOnlyList<double[]>? coordenadas = null)
|
||||||
{
|
{
|
||||||
try { await JS.InvokeVoidAsync("rt.fitRoutes"); } catch { }
|
try
|
||||||
|
{
|
||||||
|
if (coordenadas is null)
|
||||||
|
await JS.InvokeVoidAsync("rt.fitRoutes");
|
||||||
|
else
|
||||||
|
await JS.InvokeVoidAsync("rt.fitRoutes", coordenadas);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1043,7 +1066,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
{
|
{
|
||||||
if (d <= 0) return "-";
|
if (d <= 0) return "-";
|
||||||
if (d < 1000) return $"{Math.Truncate(d):0} m";
|
if (d < 1000) return $"{Math.Truncate(d):0} m";
|
||||||
return $"{(d / 1000.0):0.00} km";
|
return $"{(d / 1000.0):0.000} km";
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -1206,18 +1229,35 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
return EstimarDuracionPieSegundos(distanciaCorregida);
|
return EstimarDuracionPieSegundos(distanciaCorregida);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Calcula la aportacion de todos los tramos andando a la distancia equilibrada.
|
||||||
|
/// </summary>
|
||||||
|
private double CalcularDistanciaEquilibradaAndandoAlternativa(AlternativaRuta? alternativa)
|
||||||
|
{
|
||||||
|
if (alternativa is null)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
var distanciaAndando = alternativa.EsSoloAPie
|
||||||
|
? alternativa.DistanciaSoloAPie
|
||||||
|
: alternativa.DistanciaPieTotalAprox;
|
||||||
|
var factor = double.IsFinite(_factorEquilibradoAndarBus)
|
||||||
|
? Math.Max(0, _factorEquilibradoAndarBus)
|
||||||
|
: 12.0;
|
||||||
|
var distanciaEquilibrada = Math.Max(0, distanciaAndando) * factor;
|
||||||
|
|
||||||
|
return double.IsFinite(distanciaEquilibrada)
|
||||||
|
? distanciaEquilibrada
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
private double CalcularScoreEquilibradoFactor(AlternativaRuta? alternativa)
|
private double CalcularScoreEquilibradoFactor(AlternativaRuta? alternativa)
|
||||||
{
|
{
|
||||||
if (alternativa is null || alternativa.EsSoloAPie || alternativa.AlternativaBus is null)
|
if (alternativa is null || alternativa.EsSoloAPie || alternativa.AlternativaBus is null)
|
||||||
return double.PositiveInfinity;
|
return double.PositiveInfinity;
|
||||||
|
|
||||||
var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
|
var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
|
||||||
var distanciaAndando = Math.Max(0, alternativa.DistanciaPieTotalAprox);
|
var distanciaEquilibradaAndando = CalcularDistanciaEquilibradaAndandoAlternativa(alternativa);
|
||||||
var factor = double.IsFinite(_factorEquilibradoAndarBus)
|
var score = distanciaBus + distanciaEquilibradaAndando;
|
||||||
? Math.Max(0, _factorEquilibradoAndarBus)
|
|
||||||
: 12.0;
|
|
||||||
|
|
||||||
var score = distanciaBus + (distanciaAndando * factor);
|
|
||||||
return double.IsFinite(score) && score >= 0
|
return double.IsFinite(score) && score >= 0
|
||||||
? score
|
? score
|
||||||
: double.PositiveInfinity;
|
: double.PositiveInfinity;
|
||||||
@@ -1232,9 +1272,6 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
return $"Score {FormatearDistanciaTarjeta(score)} (x{_factorEquilibradoAndarBus.ToString("0.##", CultureInfo.InvariantCulture)})";
|
return $"Score {FormatearDistanciaTarjeta(score)} (x{_factorEquilibradoAndarBus.ToString("0.##", CultureInfo.InvariantCulture)})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private string FactorEquilibradoAndarBusResumen
|
|
||||||
=> _factorEquilibradoAndarBus.ToString("0.##", CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync()
|
private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync()
|
||||||
{
|
{
|
||||||
if (!CriterioPrioritarioEquilibradoActivo)
|
if (!CriterioPrioritarioEquilibradoActivo)
|
||||||
@@ -1572,6 +1609,8 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
_hayRutaValida = false;
|
_hayRutaValida = false;
|
||||||
_tramosRuta.Clear();
|
_tramosRuta.Clear();
|
||||||
_duracionTotalSegundos = 0;
|
_duracionTotalSegundos = 0;
|
||||||
|
_alturaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||||
|
_distanciaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||||
_altitudOrigenResumenRuta = null;
|
_altitudOrigenResumenRuta = null;
|
||||||
_altitudDestinoResumenRuta = null;
|
_altitudDestinoResumenRuta = null;
|
||||||
_callejeroOrigenResumenRuta = null;
|
_callejeroOrigenResumenRuta = null;
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ public partial class PlanificadorRutas
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
await Task.Delay(120, ct);
|
await Task.Delay(120, ct);
|
||||||
var sugerencias = await CrearSugerenciasBusquedaAsync(texto, 10, ct);
|
var sugerencias = await CrearSugerenciasBusquedaAsync(texto, 20, ct);
|
||||||
|
|
||||||
if (ct.IsCancellationRequested || version != _versionSugerenciasBusqueda)
|
if (ct.IsCancellationRequested || version != _versionSugerenciasBusqueda)
|
||||||
return;
|
return;
|
||||||
@@ -118,6 +118,10 @@ public partial class PlanificadorRutas
|
|||||||
_hayRutaValida = false;
|
_hayRutaValida = false;
|
||||||
|
|
||||||
await SincronizarMarcadoresOrigenDestinoJsAsync();
|
await SincronizarMarcadoresOrigenDestinoJsAsync();
|
||||||
|
await FitRoutesJsAsync(new List<double[]>
|
||||||
|
{
|
||||||
|
new[] { sugerencia.Latitud, sugerencia.Longitud }
|
||||||
|
});
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -232,6 +232,9 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task PintarItFiltro()
|
private async Task PintarItFiltro()
|
||||||
{
|
{
|
||||||
|
if (!PuedePintarItinerariosFiltrados)
|
||||||
|
return;
|
||||||
|
|
||||||
foreach (var it in _itItemsFiltrados)
|
foreach (var it in _itItemsFiltrados)
|
||||||
{
|
{
|
||||||
if (it is null) continue;
|
if (it is null) continue;
|
||||||
@@ -289,6 +292,23 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
RegistrarPreview(clave, subId);
|
RegistrarPreview(clave, subId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Reúne la geometría de todos los sentidos incluidos en un elemento de itinerario.
|
||||||
|
/// </summary>
|
||||||
|
private List<double[]> ConstruirCoordenadasItinerario(ElementoItinerario it)
|
||||||
|
{
|
||||||
|
var coordenadas = new List<double[]>();
|
||||||
|
|
||||||
|
foreach (var variante in it.Variantes)
|
||||||
|
{
|
||||||
|
var geometria = ConstruirGeometriaCompletaVariante(variante);
|
||||||
|
if (geometria is not null)
|
||||||
|
coordenadas.AddRange(LineaToLatLng(geometria));
|
||||||
|
}
|
||||||
|
|
||||||
|
return coordenadas;
|
||||||
|
}
|
||||||
|
|
||||||
private Parada? _paradaPrimerClickPendiente;
|
private Parada? _paradaPrimerClickPendiente;
|
||||||
|
|
||||||
@@ -609,6 +629,8 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
|
|
||||||
|
|
||||||
private string _itFiltro = "";
|
private string _itFiltro = "";
|
||||||
|
private bool PuedePintarItinerariosFiltrados => !string.IsNullOrWhiteSpace(_itFiltro);
|
||||||
|
|
||||||
private string ItFiltro
|
private string ItFiltro
|
||||||
{
|
{
|
||||||
get => _itFiltro;
|
get => _itFiltro;
|
||||||
@@ -1181,8 +1203,8 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
|
|
||||||
private bool _elevPopupMinimizado = false;
|
private bool _elevPopupMinimizado = false;
|
||||||
|
|
||||||
|
|
||||||
private const string _versionApp = "(v-20260716a)";
|
private const string _versionApp = "(v-20260720c)";
|
||||||
|
|
||||||
|
|
||||||
private double? _paradaElevG;
|
private double? _paradaElevG;
|
||||||
@@ -1200,7 +1222,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
_itinerarioClicado = null;
|
_itinerarioClicado = null;
|
||||||
|
|
||||||
_paradaClicada = null;
|
_paradaClicada = null;
|
||||||
_popupInspectorVisible = false;
|
await CerrarInspectorAsync();
|
||||||
|
|
||||||
if (_tramoElevDetalle is not null)
|
if (_tramoElevDetalle is not null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -18,6 +18,38 @@ public partial class PlanificadorRutas
|
|||||||
private ModeloHorarioDbusInfo? ModeloHorarioActivo =>
|
private ModeloHorarioDbusInfo? ModeloHorarioActivo =>
|
||||||
_modelosHorarios.FirstOrDefault(modelo => modelo.Activo);
|
_modelosHorarios.FirstOrDefault(modelo => modelo.Activo);
|
||||||
|
|
||||||
|
private string FechaModeloHorarioActivo =>
|
||||||
|
FormatearFechaModeloHorario(ModeloHorarioActivo?.NombreArchivo);
|
||||||
|
|
||||||
|
private static string FormatearFechaModeloHorario(string? nombreArchivo)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(nombreArchivo))
|
||||||
|
return "Sin modelo cargado";
|
||||||
|
|
||||||
|
for (var indice = nombreArchivo.Length - 8; indice >= 0; indice--)
|
||||||
|
{
|
||||||
|
if (indice > 0 && char.IsDigit(nombreArchivo[indice - 1]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var fin = indice + 8;
|
||||||
|
if (fin < nombreArchivo.Length && char.IsDigit(nombreArchivo[fin]))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var candidata = nombreArchivo.Substring(indice, 8);
|
||||||
|
if (DateTime.TryParseExact(
|
||||||
|
candidata,
|
||||||
|
"yyyyMMdd",
|
||||||
|
CultureInfo.InvariantCulture,
|
||||||
|
DateTimeStyles.None,
|
||||||
|
out _))
|
||||||
|
{
|
||||||
|
return candidata;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "Fecha no disponible";
|
||||||
|
}
|
||||||
|
|
||||||
private void AbrirModelosHorariosPanel()
|
private void AbrirModelosHorariosPanel()
|
||||||
{
|
{
|
||||||
_mostrarModelosHorariosPanel = true;
|
_mostrarModelosHorariosPanel = true;
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ public partial class PlanificadorRutas
|
|||||||
public double? FactorEquilibradoAndarBus { get; set; }
|
public double? FactorEquilibradoAndarBus { get; set; }
|
||||||
public double? UmbralPendienteSubidaRuta { get; set; }
|
public double? UmbralPendienteSubidaRuta { get; set; }
|
||||||
public double? UmbralPendienteBajadaRuta { get; set; }
|
public double? UmbralPendienteBajadaRuta { get; set; }
|
||||||
|
public double? UmbralPisosPendienteRuta { get; set; }
|
||||||
public double VelocidadPieKmH { get; set; }
|
public double VelocidadPieKmH { get; set; }
|
||||||
public double VelocidadBusKmH { get; set; }
|
public double VelocidadBusKmH { get; set; }
|
||||||
public int ElevMaxPuntosPorTramo { get; set; }
|
public int ElevMaxPuntosPorTramo { get; set; }
|
||||||
@@ -140,6 +141,7 @@ public partial class PlanificadorRutas
|
|||||||
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
||||||
UmbralPendienteSubidaRuta = _umbralPendienteSubidaRuta,
|
UmbralPendienteSubidaRuta = _umbralPendienteSubidaRuta,
|
||||||
UmbralPendienteBajadaRuta = _umbralPendienteBajadaRuta,
|
UmbralPendienteBajadaRuta = _umbralPendienteBajadaRuta,
|
||||||
|
UmbralPisosPendienteRuta = _umbralPisosPendienteRuta,
|
||||||
VelocidadPieKmH = _velocidadPieKmH,
|
VelocidadPieKmH = _velocidadPieKmH,
|
||||||
VelocidadBusKmH = _velocidadBusKmH,
|
VelocidadBusKmH = _velocidadBusKmH,
|
||||||
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
|
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
|
||||||
@@ -185,11 +187,17 @@ public partial class PlanificadorRutas
|
|||||||
_horaSimulada = horaSimulada;
|
_horaSimulada = horaSimulada;
|
||||||
|
|
||||||
if (config.FactorPenalizacionDistanciaPie is { } factor && double.IsFinite(factor))
|
if (config.FactorPenalizacionDistanciaPie is { } factor && double.IsFinite(factor))
|
||||||
_factorPenalizacionDistanciaPie = Math.Abs(factor - 1.3) < 0.0001 ? 1.6 : factor;
|
_factorPenalizacionDistanciaPie =
|
||||||
|
Math.Abs(factor - 1.3) < 0.0001 || Math.Abs(factor - 1.6) < 0.0001
|
||||||
|
? FactorPenalizacionDistanciaPiePorDefecto
|
||||||
|
: factor;
|
||||||
if (config.ParametroPenalizacionDistanciaPieMetros is { } parametro && double.IsFinite(parametro))
|
if (config.ParametroPenalizacionDistanciaPieMetros is { } parametro && double.IsFinite(parametro))
|
||||||
_parametroPenalizacionDistanciaPieMetros = parametro;
|
_parametroPenalizacionDistanciaPieMetros = parametro;
|
||||||
if (config.CoeficienteEsfuerzoAltura is { } coeficienteAltura && double.IsFinite(coeficienteAltura))
|
if (config.CoeficienteEsfuerzoAltura is { } coeficienteAltura && double.IsFinite(coeficienteAltura))
|
||||||
_coeficienteEsfuerzoAltura = Math.Abs(coeficienteAltura - 1.6) < 0.0001
|
_coeficienteEsfuerzoAltura =
|
||||||
|
Math.Abs(coeficienteAltura - 1.6) < 0.0001 ||
|
||||||
|
Math.Abs(coeficienteAltura - 5.2) < 0.0001 ||
|
||||||
|
Math.Abs(coeficienteAltura - 9.2) < 0.0001
|
||||||
? CoeficienteEsfuerzoAlturaPorDefecto
|
? CoeficienteEsfuerzoAlturaPorDefecto
|
||||||
: coeficienteAltura;
|
: coeficienteAltura;
|
||||||
if (config.PesoTransbordoRankingMinutos is { } pesoTransbordo && double.IsFinite(pesoTransbordo))
|
if (config.PesoTransbordoRankingMinutos is { } pesoTransbordo && double.IsFinite(pesoTransbordo))
|
||||||
@@ -197,9 +205,17 @@ public partial class PlanificadorRutas
|
|||||||
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
|
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
|
||||||
_factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
|
_factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
|
||||||
if (config.UmbralPendienteSubidaRuta is { } umbralSubida && double.IsFinite(umbralSubida))
|
if (config.UmbralPendienteSubidaRuta is { } umbralSubida && double.IsFinite(umbralSubida))
|
||||||
_umbralPendienteSubidaRuta = NormalizarUmbralPendienteRuta(umbralSubida);
|
_umbralPendienteSubidaRuta = Math.Abs(umbralSubida - 3.0) < 0.0001
|
||||||
|
? UmbralPendienteSubidaRutaPorDefecto
|
||||||
|
: NormalizarUmbralPendienteRuta(umbralSubida);
|
||||||
if (config.UmbralPendienteBajadaRuta is { } umbralBajada && double.IsFinite(umbralBajada))
|
if (config.UmbralPendienteBajadaRuta is { } umbralBajada && double.IsFinite(umbralBajada))
|
||||||
_umbralPendienteBajadaRuta = NormalizarUmbralPendienteRuta(umbralBajada);
|
_umbralPendienteBajadaRuta =
|
||||||
|
Math.Abs(umbralBajada - 3.0) < 0.0001 ||
|
||||||
|
Math.Abs(umbralBajada - 6.0) < 0.0001
|
||||||
|
? UmbralPendienteBajadaRutaPorDefecto
|
||||||
|
: NormalizarUmbralPendienteRuta(umbralBajada);
|
||||||
|
if (config.UmbralPisosPendienteRuta is { } umbralPisos && double.IsFinite(umbralPisos))
|
||||||
|
_umbralPisosPendienteRuta = NormalizarUmbralPisosPendienteRuta(umbralPisos);
|
||||||
_velocidadPieKmH = Math.Clamp(config.VelocidadPieKmH, 0.5, 25.0);
|
_velocidadPieKmH = Math.Clamp(config.VelocidadPieKmH, 0.5, 25.0);
|
||||||
_velocidadBusKmH = Math.Clamp(
|
_velocidadBusKmH = Math.Clamp(
|
||||||
Math.Abs(config.VelocidadBusKmH - 15.0) < 0.0001 ||
|
Math.Abs(config.VelocidadBusKmH - 15.0) < 0.0001 ||
|
||||||
@@ -298,13 +314,14 @@ public partial class PlanificadorRutas
|
|||||||
_minTransbordosPermitidos = 0;
|
_minTransbordosPermitidos = 0;
|
||||||
_maxTransbordosPermitidos = 3;
|
_maxTransbordosPermitidos = 3;
|
||||||
|
|
||||||
_factorPenalizacionDistanciaPie = 1.6;
|
_factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
|
||||||
_parametroPenalizacionDistanciaPieMetros = 100.0;
|
_parametroPenalizacionDistanciaPieMetros = 100.0;
|
||||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
_pesoTransbordoRankingMinutos = 4.0;
|
_pesoTransbordoRankingMinutos = 4.0;
|
||||||
_factorEquilibradoAndarBus = 12.0;
|
_factorEquilibradoAndarBus = 12.0;
|
||||||
_umbralPendienteSubidaRuta = 3.0;
|
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||||
_umbralPendienteBajadaRuta = 3.0;
|
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||||
|
_umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
||||||
_velocidadPieKmH = 3.6;
|
_velocidadPieKmH = 3.6;
|
||||||
_velocidadBusKmH = 16.0;
|
_velocidadBusKmH = 16.0;
|
||||||
_minutosTransbordo = 5;
|
_minutosTransbordo = 5;
|
||||||
@@ -347,13 +364,14 @@ public partial class PlanificadorRutas
|
|||||||
_minTransbordosPermitidos = 0;
|
_minTransbordosPermitidos = 0;
|
||||||
_maxTransbordosPermitidos = 3;
|
_maxTransbordosPermitidos = 3;
|
||||||
|
|
||||||
_factorPenalizacionDistanciaPie = 1.6;
|
_factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
|
||||||
_parametroPenalizacionDistanciaPieMetros = 100.0;
|
_parametroPenalizacionDistanciaPieMetros = 100.0;
|
||||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
_pesoTransbordoRankingMinutos = 4.0;
|
_pesoTransbordoRankingMinutos = 4.0;
|
||||||
_factorEquilibradoAndarBus = 12.0;
|
_factorEquilibradoAndarBus = 12.0;
|
||||||
_umbralPendienteSubidaRuta = 3.0;
|
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
|
||||||
_umbralPendienteBajadaRuta = 3.0;
|
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
|
||||||
|
_umbralPisosPendienteRuta = UmbralPisosPendienteRutaPorDefecto;
|
||||||
_velocidadPieKmH = 3.6;
|
_velocidadPieKmH = 3.6;
|
||||||
_velocidadBusKmH = 16.0;
|
_velocidadBusKmH = 16.0;
|
||||||
_minutosTransbordo = 5;
|
_minutosTransbordo = 5;
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using System.Text.RegularExpressions;
|
|||||||
using LeafletForBlazor;
|
using LeafletForBlazor;
|
||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
|
using RutasDBUS.Modelos.Geocodificacion;
|
||||||
using RutasDBUS.Modelos.Planificacion;
|
using RutasDBUS.Modelos.Planificacion;
|
||||||
using RutasDBUS.Modelos.RedBus;
|
using RutasDBUS.Modelos.RedBus;
|
||||||
using RutasDBUS.Servicios;
|
using RutasDBUS.Servicios;
|
||||||
@@ -1267,10 +1268,26 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task MostrarInspectorAsync(double lat, double lon)
|
private async Task MostrarInspectorAsync(double lat, double lon)
|
||||||
{
|
{
|
||||||
|
var versionInspector = ++_versionInspector;
|
||||||
_inspectorLat = lat;
|
_inspectorLat = lat;
|
||||||
_inspectorLon = lon;
|
_inspectorLon = lon;
|
||||||
_inspectorCallejero = null;
|
_inspectorElev = null;
|
||||||
|
_inspectorElevIdee = null;
|
||||||
|
_inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
|
||||||
|
_popupInspectorVisible = true;
|
||||||
|
|
||||||
|
await ActualizarMarcadoresInspectorJsAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
|
||||||
|
var tareaLugares = GeocodificadorLocal.BuscarCercanosAsync(
|
||||||
|
lat,
|
||||||
|
lon,
|
||||||
|
radioMetros: 20,
|
||||||
|
maxResultados: 3,
|
||||||
|
cancellationToken: CancellationToken.None);
|
||||||
|
|
||||||
|
double? elevacionOpen = null;
|
||||||
|
double? elevacionIdee = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// -------------------------
|
// -------------------------
|
||||||
@@ -1279,7 +1296,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
var kOpen = ElevKey(lat, lon);
|
var kOpen = ElevKey(lat, lon);
|
||||||
if (_elevCache.TryGetValue(kOpen, out var cachedOpen))
|
if (_elevCache.TryGetValue(kOpen, out var cachedOpen))
|
||||||
{
|
{
|
||||||
_inspectorElev = cachedOpen;
|
elevacionOpen = cachedOpen;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1287,7 +1304,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
new List<(double lat, double lon)> { (lat, lon) },
|
new List<(double lat, double lon)> { (lat, lon) },
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
_inspectorElev = _elevCache.TryGetValue(ElevKey(lat, lon), out var elevOpen)
|
elevacionOpen = _elevCache.TryGetValue(ElevKey(lat, lon), out var elevOpen)
|
||||||
? elevOpen
|
? elevOpen
|
||||||
: (double?)null;
|
: (double?)null;
|
||||||
}
|
}
|
||||||
@@ -1298,7 +1315,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
var kIdee = ClaveElevacionIdee(lat, lon);
|
var kIdee = ClaveElevacionIdee(lat, lon);
|
||||||
if (_elevIdeeCache.TryGetValue(kIdee, out var cachedIdee))
|
if (_elevIdeeCache.TryGetValue(kIdee, out var cachedIdee))
|
||||||
{
|
{
|
||||||
_inspectorElevIdee = cachedIdee;
|
elevacionIdee = cachedIdee;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1306,33 +1323,72 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
new List<(double lat, double lon)> { (lat, lon) },
|
new List<(double lat, double lon)> { (lat, lon) },
|
||||||
CancellationToken.None);
|
CancellationToken.None);
|
||||||
|
|
||||||
_inspectorElevIdee = _elevIdeeCache.TryGetValue(ClaveElevacionIdee(lat, lon), out var elevIdee)
|
elevacionIdee = _elevIdeeCache.TryGetValue(ClaveElevacionIdee(lat, lon), out var elevIdee)
|
||||||
? elevIdee
|
? elevIdee
|
||||||
: (double?)null;
|
: (double?)null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_inspectorElev = null;
|
|
||||||
_inspectorElevIdee = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var lugar = await GeocodificadorLocal.BuscarMasCercanoAsync(
|
var lugares = await tareaLugares;
|
||||||
lat,
|
if (versionInspector != _versionInspector)
|
||||||
lon,
|
return;
|
||||||
CancellationToken.None);
|
|
||||||
_inspectorCallejero = FormatearLugarCallejeroResumen(lugar);
|
_inspectorLugaresCercanos = lugares;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
_inspectorCallejero = null;
|
if (versionInspector != _versionInspector)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
|
||||||
}
|
}
|
||||||
|
|
||||||
_popupInspectorVisible = true;
|
if (versionInspector != _versionInspector)
|
||||||
|
return;
|
||||||
|
|
||||||
|
_inspectorElev = elevacionOpen;
|
||||||
|
_inspectorElevIdee = elevacionIdee;
|
||||||
|
await ActualizarMarcadoresInspectorJsAsync();
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ActualizarMarcadoresInspectorJsAsync()
|
||||||
|
{
|
||||||
|
var lugares = _inspectorLugaresCercanos
|
||||||
|
.Select(cercano => new
|
||||||
|
{
|
||||||
|
latitud = cercano.Lugar.Latitud,
|
||||||
|
longitud = cercano.Lugar.Longitud,
|
||||||
|
texto = FormatearLugarCallejeroResumen(cercano)
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await JS.InvokeVoidAsync(
|
||||||
|
"rt.setInspectorMarkers",
|
||||||
|
_inspectorLat,
|
||||||
|
_inspectorLon,
|
||||||
|
lugares);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CerrarInspectorAsync()
|
||||||
|
{
|
||||||
|
_versionInspector++;
|
||||||
|
_popupInspectorVisible = false;
|
||||||
|
_inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
|
||||||
|
|
||||||
|
try { await JS.InvokeVoidAsync("rt.clearInspectorMarkers"); } catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
};
|
};
|
||||||
|
|
||||||
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
||||||
? "Aplicando elevaciones..."
|
? "Aplicando pendientes..."
|
||||||
: "Representando ruta seleccionada...");
|
: "Representando ruta seleccionada...");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,11 +96,21 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
await PersistirConfiguracionAsync();
|
await PersistirConfiguracionAsync();
|
||||||
|
|
||||||
if (_mostrarElevacionesRuta)
|
if (_mostrarElevacionesRuta)
|
||||||
await RepintarRutaSeleccionadaPorElevacionesAsync("Aplicando elevaciones...");
|
await RepintarRutaSeleccionadaPorElevacionesAsync("Aplicando pendientes...");
|
||||||
else
|
else
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CambiarUmbralPisosPendienteRutaAsync(ChangeEventArgs e)
|
||||||
|
{
|
||||||
|
_umbralPisosPendienteRuta = ParseUmbralPisosPendienteRuta(
|
||||||
|
e.Value,
|
||||||
|
_umbralPisosPendienteRuta);
|
||||||
|
|
||||||
|
await PersistirConfiguracionAsync();
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
private async Task RepintarRutaSeleccionadaPorElevacionesAsync(string mensaje)
|
private async Task RepintarRutaSeleccionadaPorElevacionesAsync(string mensaje)
|
||||||
{
|
{
|
||||||
if (_opcionesRuta.Count == 0 ||
|
if (_opcionesRuta.Count == 0 ||
|
||||||
@@ -111,6 +121,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ReiniciarResumenPendientesRuta();
|
||||||
_pintandoElevacionesRuta = true;
|
_pintandoElevacionesRuta = true;
|
||||||
_mensajeRuta = mensaje;
|
_mensajeRuta = mensaje;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
@@ -143,7 +154,24 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private static double NormalizarUmbralPendienteRuta(double valor)
|
private static double NormalizarUmbralPendienteRuta(double valor)
|
||||||
=> double.IsFinite(valor)
|
=> double.IsFinite(valor)
|
||||||
? Math.Clamp(Math.Abs(valor), 0.0, 50.0)
|
? Math.Clamp(Math.Abs(valor), 0.0, 50.0)
|
||||||
: 3.0;
|
: UmbralPendienteSubidaRutaPorDefecto;
|
||||||
|
|
||||||
|
private static double ParseUmbralPisosPendienteRuta(object? valor, double actual)
|
||||||
|
{
|
||||||
|
var texto = Convert.ToString(valor, CultureInfo.InvariantCulture)?.Replace(',', '.');
|
||||||
|
if (!double.TryParse(texto, NumberStyles.Float, CultureInfo.InvariantCulture, out var nuevo) ||
|
||||||
|
!double.IsFinite(nuevo))
|
||||||
|
{
|
||||||
|
return NormalizarUmbralPisosPendienteRuta(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NormalizarUmbralPisosPendienteRuta(nuevo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double NormalizarUmbralPisosPendienteRuta(double valor)
|
||||||
|
=> double.IsFinite(valor)
|
||||||
|
? Math.Clamp(Math.Abs(valor), 0.0, 100.0)
|
||||||
|
: UmbralPisosPendienteRutaPorDefecto;
|
||||||
|
|
||||||
private async Task DrawWalkingRouteJsAsync(
|
private async Task DrawWalkingRouteJsAsync(
|
||||||
string id,
|
string id,
|
||||||
@@ -152,9 +180,17 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
int versionPintado,
|
int versionPintado,
|
||||||
bool arrowEnd = true)
|
bool arrowEnd = true)
|
||||||
{
|
{
|
||||||
var segmentosElevacion = await ConstruirSegmentosElevacionMapaAsync(linea, CancellationToken.None);
|
var resultadoElevacion = await ConstruirSegmentosElevacionMapaAsync(linea, CancellationToken.None);
|
||||||
ThrowIfPintadoObsoleto(versionPintado);
|
ThrowIfPintadoObsoleto(versionPintado);
|
||||||
|
|
||||||
|
if (_mostrarElevacionesRuta)
|
||||||
|
{
|
||||||
|
_alturaPendientePositivaSignificativaRutaMetros +=
|
||||||
|
resultadoElevacion.AlturaPositivaSignificativaMetros;
|
||||||
|
_distanciaPendientePositivaSignificativaRutaMetros +=
|
||||||
|
resultadoElevacion.DistanciaPositivaSignificativaMetros;
|
||||||
|
}
|
||||||
|
|
||||||
await DrawRouteJsAsync(
|
await DrawRouteJsAsync(
|
||||||
id,
|
id,
|
||||||
linea,
|
linea,
|
||||||
@@ -164,20 +200,25 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
dashArray: DashArrayPiePunteado,
|
dashArray: DashArrayPiePunteado,
|
||||||
arrowEnd: arrowEnd,
|
arrowEnd: arrowEnd,
|
||||||
lineCap: "round",
|
lineCap: "round",
|
||||||
elevationSegments: segmentosElevacion);
|
elevationSegments: resultadoElevacion.Segmentos);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<object>?> ConstruirSegmentosElevacionMapaAsync(double[,] linea, CancellationToken ct)
|
private async Task<(
|
||||||
|
List<object>? Segmentos,
|
||||||
|
double AlturaPositivaSignificativaMetros,
|
||||||
|
double DistanciaPositivaSignificativaMetros)>
|
||||||
|
ConstruirSegmentosElevacionMapaAsync(double[,] linea, CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (!_mostrarElevacionesRuta || linea.GetLength(0) < 2)
|
if (!_mostrarElevacionesRuta || linea.GetLength(0) < 2)
|
||||||
return null;
|
return (null, 0.0, 0.0);
|
||||||
|
|
||||||
var muestras = await ConstruirMuestrasElevacionAsync(linea, ct);
|
var muestras = await ConstruirMuestrasElevacionAsync(linea, ct);
|
||||||
if (muestras.Count < 2)
|
if (muestras.Count < 2)
|
||||||
return null;
|
return (null, 0.0, 0.0);
|
||||||
|
|
||||||
var segmentos = new List<object>();
|
var segmentos = new List<object>();
|
||||||
var hayPendienteCalculada = false;
|
var hayPendienteCalculada = false;
|
||||||
|
var totalesPendientePositiva = CalcularTotalesPendientePositivaSignificativa(muestras);
|
||||||
|
|
||||||
for (var i = 1; i < muestras.Count; i++)
|
for (var i = 1; i < muestras.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -200,7 +241,8 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
|
|
||||||
if (elevAnterior is not null && elevActual is not null)
|
if (elevAnterior is not null && elevActual is not null)
|
||||||
{
|
{
|
||||||
pendiente = (elevActual.Value - elevAnterior.Value) / distancia * 100.0;
|
var diferenciaAltura = elevActual.Value - elevAnterior.Value;
|
||||||
|
pendiente = diferenciaAltura / distancia * 100.0;
|
||||||
hayPendienteCalculada = true;
|
hayPendienteCalculada = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -224,9 +266,64 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return hayPendienteCalculada && segmentos.Count > 0
|
return (
|
||||||
? segmentos
|
hayPendienteCalculada && segmentos.Count > 0 ? segmentos : null,
|
||||||
: null;
|
totalesPendientePositiva.AlturaMetros,
|
||||||
|
totalesPendientePositiva.DistanciaMetros);
|
||||||
|
}
|
||||||
|
|
||||||
|
private (double DistanciaMetros, double AlturaMetros)
|
||||||
|
CalcularTotalesPendientePositivaSignificativa(IReadOnlyList<MuestraElevacion>? muestras)
|
||||||
|
{
|
||||||
|
if (muestras is null || muestras.Count < 2)
|
||||||
|
return (0.0, 0.0);
|
||||||
|
|
||||||
|
var distanciaTotal = 0.0;
|
||||||
|
var alturaTotal = 0.0;
|
||||||
|
var umbralSubida = NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta);
|
||||||
|
|
||||||
|
for (var i = 1; i < muestras.Count; i++)
|
||||||
|
{
|
||||||
|
var anterior = muestras[i - 1];
|
||||||
|
var actual = muestras[i];
|
||||||
|
if (anterior.ElevacionIdee is null || actual.ElevacionIdee is null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var distancia = CalcularDistanciaMetros(
|
||||||
|
anterior.Latitud,
|
||||||
|
anterior.Longitud,
|
||||||
|
actual.Latitud,
|
||||||
|
actual.Longitud);
|
||||||
|
if (distancia <= 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var diferenciaAltura = actual.ElevacionIdee.Value - anterior.ElevacionIdee.Value;
|
||||||
|
var pendiente = diferenciaAltura / distancia * 100.0;
|
||||||
|
if (pendiente <= umbralSubida)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
distanciaTotal += distancia;
|
||||||
|
alturaTotal += Math.Max(0, diferenciaAltura);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (distanciaTotal, alturaTotal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatearDistanciaResumenElevacion(double distanciaMetros)
|
||||||
|
{
|
||||||
|
if (!double.IsFinite(distanciaMetros) || distanciaMetros <= 0)
|
||||||
|
return "-";
|
||||||
|
|
||||||
|
if (distanciaMetros < 950)
|
||||||
|
return $"{distanciaMetros:0.00} m";
|
||||||
|
|
||||||
|
return $"{(distanciaMetros / 1000.0):0.000} km";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ReiniciarResumenPendientesRuta()
|
||||||
|
{
|
||||||
|
_alturaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||||
|
_distanciaPendientePositivaSignificativaRutaMetros = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double? ElevacionPreferidaMapa(MuestraElevacion muestra)
|
private static double? ElevacionPreferidaMapa(MuestraElevacion muestra)
|
||||||
@@ -259,6 +356,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
{
|
{
|
||||||
var versionPintado = IniciarNuevaVersionPintadoRuta();
|
var versionPintado = IniciarNuevaVersionPintadoRuta();
|
||||||
var mensajeAnterior = _mensajeRuta;
|
var mensajeAnterior = _mensajeRuta;
|
||||||
|
ReiniciarResumenPendientesRuta();
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -60,7 +60,9 @@
|
|||||||
|
|
||||||
<button class="btn-ghost" @onclick="PintarItSeleccionado">Ver</button>
|
<button class="btn-ghost" @onclick="PintarItSeleccionado">Ver</button>
|
||||||
<button class="btn-ghost" @onclick="QuitarItSeleccionado">Ocultar</button>
|
<button class="btn-ghost" @onclick="QuitarItSeleccionado">Ocultar</button>
|
||||||
<button class="btn-ghost" @onclick="PintarItFiltro">Ver filtro</button>
|
<button class="btn-ghost"
|
||||||
|
disabled="@(!PuedePintarItinerariosFiltrados)"
|
||||||
|
@onclick="PintarItFiltro">Ver filtro</button>
|
||||||
|
|
||||||
<button class="btn-ghost" @onclick="QuitarTodosItinerarios">Ocultar Todos</button>
|
<button class="btn-ghost" @onclick="QuitarTodosItinerarios">Ocultar Todos</button>
|
||||||
|
|
||||||
@@ -344,29 +346,38 @@
|
|||||||
<div class="popup-parada-body">
|
<div class="popup-parada-body">
|
||||||
<div><b>Latitud:</b> @_inspectorLat.ToString("0.000000")</div>
|
<div><b>Latitud:</b> @_inspectorLat.ToString("0.000000")</div>
|
||||||
<div><b>Longitud:</b> @_inspectorLon.ToString("0.000000")</div>
|
<div><b>Longitud:</b> @_inspectorLon.ToString("0.000000")</div>
|
||||||
@if (!string.IsNullOrWhiteSpace(_inspectorCallejero))
|
|
||||||
{
|
|
||||||
<div><b>Callejero:</b> @_inspectorCallejero</div>
|
|
||||||
}
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<b>Altitud (O):</b>
|
<b>Altitud (O):</b>
|
||||||
@(_inspectorElev is null
|
@(_inspectorElev is null
|
||||||
? "-"
|
? "-"
|
||||||
: _inspectorElev.Value.ToString("0.00", CultureInfo.InvariantCulture) + " m")
|
: _inspectorElev.Value.ToString("0.00", CultureInfo.InvariantCulture) + " m")
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<b>Altitud (G):</b>
|
<b>Altitud (G):</b>
|
||||||
@(_inspectorElevIdee is null
|
@(_inspectorElevIdee is null
|
||||||
? "-"
|
? "-"
|
||||||
: _inspectorElevIdee.Value.ToString("0.00", CultureInfo.InvariantCulture) + " m")
|
: _inspectorElevIdee.Value.ToString("0.00", CultureInfo.InvariantCulture) + " m")
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<button class="popup-parada-cerrar" @onclick="() => _popupInspectorVisible = false">×</button>
|
@if (_inspectorLugaresCercanos.Count > 0)
|
||||||
|
{
|
||||||
|
<div class="inspector-address-list">
|
||||||
|
@for (var indiceLugar = 0; indiceLugar < _inspectorLugaresCercanos.Count; indiceLugar++)
|
||||||
|
{
|
||||||
|
var lugarCercano = _inspectorLugaresCercanos[indiceLugar];
|
||||||
|
<div class="inspector-address-item">
|
||||||
|
<span class="inspector-address-number">@(indiceLugar + 1)</span>
|
||||||
|
<span>@FormatearLugarCallejeroResumen(lugarCercano)</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button class="popup-parada-cerrar" @onclick="CerrarInspectorAsync">×</button>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -427,6 +438,11 @@
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
<div class="muted" style="font-size:0.72rem; margin:0 0 .35rem;">
|
||||||
|
<b>Umbral de subida (Esf.):</b>
|
||||||
|
@NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta).ToString("0.#", CultureInfo.InvariantCulture) %
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="popup-elev-table" style="
|
<table class="popup-elev-table" style="
|
||||||
width:100%;
|
width:100%;
|
||||||
font-size:0.8rem;
|
font-size:0.8rem;
|
||||||
@@ -545,10 +561,12 @@
|
|||||||
double totalDist = 0;
|
double totalDist = 0;
|
||||||
double totalEsfuerzoPositivo = 0;
|
double totalEsfuerzoPositivo = 0;
|
||||||
double totalEsfuerzo = 0;
|
double totalEsfuerzo = 0;
|
||||||
double totalDistPendPositiva = 0;
|
var totalesPendientePositiva = CalcularTotalesPendientePositivaSignificativa(s);
|
||||||
double totalAlturaPendPositiva = 0;
|
double totalDistPendPositiva = totalesPendientePositiva.DistanciaMetros;
|
||||||
|
double totalAlturaPendPositiva = totalesPendientePositiva.AlturaMetros;
|
||||||
var hayEsfuerzoPositivo = false;
|
var hayEsfuerzoPositivo = false;
|
||||||
var hayEsfuerzo = false;
|
var hayEsfuerzo = false;
|
||||||
|
var umbralPendientePositiva = NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta);
|
||||||
for (int i = 1; i < s.Count; i++)
|
for (int i = 1; i < s.Count; i++)
|
||||||
{
|
{
|
||||||
var anterior = s[i - 1];
|
var anterior = s[i - 1];
|
||||||
@@ -560,12 +578,6 @@
|
|||||||
if (distancia > 0 && anterior.ElevacionIdee is not null && actual.ElevacionIdee is not null)
|
if (distancia > 0 && anterior.ElevacionIdee is not null && actual.ElevacionIdee is not null)
|
||||||
{
|
{
|
||||||
pendiente = (actual.ElevacionIdee.Value - anterior.ElevacionIdee.Value) / distancia * 100.0;
|
pendiente = (actual.ElevacionIdee.Value - anterior.ElevacionIdee.Value) / distancia * 100.0;
|
||||||
|
|
||||||
if (pendiente > 0)
|
|
||||||
{
|
|
||||||
totalDistPendPositiva += distancia;
|
|
||||||
totalAlturaPendPositiva += Math.Max(0, actual.ElevacionIdee.Value - anterior.ElevacionIdee.Value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var esfuerzo = CalcularEsfuerzoElevacion(distancia, pendiente);
|
var esfuerzo = CalcularEsfuerzoElevacion(distancia, pendiente);
|
||||||
@@ -574,7 +586,7 @@
|
|||||||
totalEsfuerzo += esfuerzo.Value;
|
totalEsfuerzo += esfuerzo.Value;
|
||||||
hayEsfuerzo = true;
|
hayEsfuerzo = true;
|
||||||
|
|
||||||
if (pendiente > 0)
|
if (pendiente > umbralPendientePositiva)
|
||||||
{
|
{
|
||||||
totalEsfuerzoPositivo += esfuerzo.Value;
|
totalEsfuerzoPositivo += esfuerzo.Value;
|
||||||
hayEsfuerzoPositivo = true;
|
hayEsfuerzoPositivo = true;
|
||||||
@@ -582,32 +594,23 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string FDist(double d)
|
|
||||||
{
|
|
||||||
if (d <= 0) return "-";
|
|
||||||
if (d < 950)
|
|
||||||
return $"{d:0.00} m";
|
|
||||||
return $"{(d / 1000.0):0.000} km";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<div class="muted" style="font-size:0.78rem; margin-top:.5rem;">
|
<div class="muted" style="font-size:0.78rem; margin-top:.5rem;">
|
||||||
<div><b>O:</b> (ini→fin) = @F(OIni) → @F(OFin) · (min/max) = @F(OMin) / @F(OMax)</div>
|
<div><b>O:</b> (inicio → fin) = @F(OIni) → @F(OFin) · (mínimo/máximo) = @F(OMin) / @F(OMax)</div>
|
||||||
<div><b>G:</b> (ini→fin) = @F(GIni) → @F(GFin) · (min/max) = @F(GMin) / @F(GMax)</div>
|
<div><b>G:</b> (inicio → fin) = @F(GIni) → @F(GFin) · (mínimo/máximo) = @F(GMin) / @F(GMax)</div>
|
||||||
<div style="margin-top:.25rem;">
|
<div style="margin-top:.25rem;">
|
||||||
<b>Suma distancia de puntos:</b> @FDist(totalDist)
|
<b>Suma de distancias entre puntos:</b> @FormatearDistanciaResumenElevacion(totalDist)
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>Suma dist pend positiva:</b> @FDist(totalDistPendPositiva)
|
<b>Suma de distancias con pendiente positiva:</b> @FormatearDistanciaResumenElevacion(totalDistPendPositiva)
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>Suma alturas pend positiva:</b> @(totalAlturaPendPositiva > 0
|
<b>Suma de alturas con pendiente positiva:</b> @(totalAlturaPendPositiva > 0
|
||||||
? totalAlturaPendPositiva.ToString("0.00", CultureInfo.InvariantCulture) + " m (" + (totalAlturaPendPositiva / 3.0).ToString("0.0", CultureInfo.InvariantCulture) + " pisos)"
|
? totalAlturaPendPositiva.ToString("0.00", CultureInfo.InvariantCulture) + " m (" + (totalAlturaPendPositiva / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture) + " pisos)"
|
||||||
: "-")
|
: "-")
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<b>Suma de esfuerzos positivos:</b> @(hayEsfuerzoPositivo ? totalEsfuerzoPositivo.ToString("0.00", CultureInfo.InvariantCulture) : "-")
|
<b>Suma de esfuerzos positivos:</b> @(hayEsfuerzoPositivo ? totalEsfuerzoPositivo.ToString("0.00", CultureInfo.InvariantCulture) : "-")
|
||||||
<span style="margin-left:.5rem;"><b>total:</b> @(hayEsfuerzo ? totalEsfuerzo.ToString("0.00", CultureInfo.InvariantCulture) : "-")</span>
|
<span style="margin-left:.5rem;"><b>Total:</b> @(hayEsfuerzo ? totalEsfuerzo.ToString("0.00", CultureInfo.InvariantCulture) : "-")</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
@@ -670,33 +673,74 @@
|
|||||||
<span class="route-elevation-switch-thumb"></span>
|
<span class="route-elevation-switch-thumb"></span>
|
||||||
</span>
|
</span>
|
||||||
<span class="route-elevation-switch-text">
|
<span class="route-elevation-switch-text">
|
||||||
@(_pintandoElevacionesRuta ? "Elevaciones..." : "Elevaciones")
|
Visualizar Pendientes
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<div class="route-elevation-thresholds" title="Porcentaje minimo para colorear subida o bajada">
|
<div class="route-elevation-settings">
|
||||||
<label class="route-elevation-threshold">
|
<button type="button"
|
||||||
<span>Sube</span>
|
class="route-elevation-settings-trigger @(_mostrarConfiguracionPendientesRuta ? "route-elevation-settings-trigger--active" : "")"
|
||||||
<input type="number"
|
title="Configurar pendientes"
|
||||||
min="0"
|
aria-label="Configurar pendientes"
|
||||||
max="50"
|
aria-haspopup="dialog"
|
||||||
step="0.5"
|
aria-expanded="@_mostrarConfiguracionPendientesRuta"
|
||||||
value="@_umbralPendienteSubidaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
disabled="@_pintandoElevacionesRuta"
|
||||||
disabled="@_pintandoElevacionesRuta"
|
@onclick="() => _mostrarConfiguracionPendientesRuta = !_mostrarConfiguracionPendientesRuta">
|
||||||
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, true))" />
|
<span aria-hidden="true">⚙</span>
|
||||||
<span>%</span>
|
</button>
|
||||||
</label>
|
|
||||||
<label class="route-elevation-threshold">
|
@if (_mostrarConfiguracionPendientesRuta)
|
||||||
<span>Baja</span>
|
{
|
||||||
<input type="number"
|
<div class="route-elevation-settings-popover"
|
||||||
min="0"
|
role="dialog"
|
||||||
max="50"
|
aria-label="Umbrales de pendientes"
|
||||||
step="0.5"
|
@onclick:stopPropagation="true">
|
||||||
value="@_umbralPendienteBajadaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
<div class="route-elevation-settings-header">
|
||||||
disabled="@_pintandoElevacionesRuta"
|
<span>Configurar Pendientes</span>
|
||||||
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, false))" />
|
<button type="button"
|
||||||
<span>%</span>
|
class="route-elevation-settings-close"
|
||||||
</label>
|
title="Cerrar"
|
||||||
|
aria-label="Cerrar configuración de pendientes"
|
||||||
|
@onclick="() => _mostrarConfiguracionPendientesRuta = false">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="route-elevation-settings-fields">
|
||||||
|
<label class="route-elevation-setting-row">
|
||||||
|
<span>Subida</span>
|
||||||
|
<input type="text"
|
||||||
|
inputmode="decimal"
|
||||||
|
aria-label="Porcentaje de subida"
|
||||||
|
value="@_umbralPendienteSubidaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, true))" />
|
||||||
|
<span>%</span>
|
||||||
|
</label>
|
||||||
|
<label class="route-elevation-setting-row">
|
||||||
|
<span>Bajada</span>
|
||||||
|
<input type="text"
|
||||||
|
inputmode="decimal"
|
||||||
|
aria-label="Porcentaje de bajada"
|
||||||
|
value="@_umbralPendienteBajadaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, false))" />
|
||||||
|
<span>%</span>
|
||||||
|
</label>
|
||||||
|
<label class="route-elevation-setting-row"
|
||||||
|
title="Pisos acumulados para mostrar el aviso de pendientes positivas">
|
||||||
|
<span>Aviso</span>
|
||||||
|
<input type="text"
|
||||||
|
inputmode="decimal"
|
||||||
|
aria-label="Pisos para mostrar el aviso"
|
||||||
|
value="@_umbralPisosPendienteRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="CambiarUmbralPisosPendienteRutaAsync" />
|
||||||
|
<span>pisos</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -768,6 +812,20 @@
|
|||||||
{
|
{
|
||||||
var alt = _opcionesRuta[_indiceAlternativaSeleccionada];
|
var alt = _opcionesRuta[_indiceAlternativaSeleccionada];
|
||||||
|
|
||||||
|
@if (MostrarAvisoPendientesPositivasSignificativas)
|
||||||
|
{
|
||||||
|
<div class="route-slope-warning" role="status">
|
||||||
|
<div>Solución con pendientes positivas significativas.</div>
|
||||||
|
<div style="margin-top:.2rem; padding-left:.55rem;">
|
||||||
|
<div>- Distancia con pendiente: @FormatearDistanciaResumenElevacion(_distanciaPendientePositivaSignificativaRutaMetros)</div>
|
||||||
|
<div>
|
||||||
|
- Altura subida: @_alturaPendientePositivaSignificativaRutaMetros.ToString("0.00", CultureInfo.InvariantCulture) m
|
||||||
|
(@((_alturaPendientePositivaSignificativaRutaMetros / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture)) pisos)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
@* ========= RESUMEN BONITO ARRIBA (único) ========= *@
|
@* ========= RESUMEN BONITO ARRIBA (único) ========= *@
|
||||||
<p class="route-main-text">
|
<p class="route-main-text">
|
||||||
@if (alt.EsSoloAPie)
|
@if (alt.EsSoloAPie)
|
||||||
@@ -928,6 +986,7 @@
|
|||||||
: _duracionTotalSegundos;
|
: _duracionTotalSegundos;
|
||||||
var dRecta = DistanciaLineaRectaOrigenDestino();
|
var dRecta = DistanciaLineaRectaOrigenDestino();
|
||||||
var dCorregida = CalcularDistanciaCorregidaAlternativa(altSeleccionada);
|
var dCorregida = CalcularDistanciaCorregidaAlternativa(altSeleccionada);
|
||||||
|
var dEquilibradaAndando = CalcularDistanciaEquilibradaAndandoAlternativa(altSeleccionada);
|
||||||
var xyzOrigen = FormatearResumenXYZOrigen();
|
var xyzOrigen = FormatearResumenXYZOrigen();
|
||||||
var xyzDestino = FormatearResumenXYZDestino();
|
var xyzDestino = FormatearResumenXYZDestino();
|
||||||
var callejeroOrigen = FormatearResumenCallejeroOrigen();
|
var callejeroOrigen = FormatearResumenCallejeroOrigen();
|
||||||
@@ -948,7 +1007,7 @@
|
|||||||
</p>
|
</p>
|
||||||
}
|
}
|
||||||
|
|
||||||
@if (dRecta > 0 || dCorregida > 0)
|
@if (dRecta > 0 || dCorregida > 0 || dEquilibradaAndando > 0)
|
||||||
{
|
{
|
||||||
<div class="muted" style="font-size:0.75rem;margin-top:4px;">
|
<div class="muted" style="font-size:0.75rem;margin-top:4px;">
|
||||||
@if (dRecta > 0)
|
@if (dRecta > 0)
|
||||||
@@ -959,6 +1018,10 @@
|
|||||||
{
|
{
|
||||||
<div>d.Corregida a pie: @FormatearDistanciaDetalleRuta(dCorregida)</div>
|
<div>d.Corregida a pie: @FormatearDistanciaDetalleRuta(dCorregida)</div>
|
||||||
}
|
}
|
||||||
|
@if (dEquilibradaAndando > 0)
|
||||||
|
{
|
||||||
|
<div>d.Equilib. A: @FormatearDistanciaDetalleRuta(dEquilibradaAndando)</div>
|
||||||
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(scoreEquilibradoSeleccionado))
|
@if (!string.IsNullOrWhiteSpace(scoreEquilibradoSeleccionado))
|
||||||
{
|
{
|
||||||
<div>Score equilibrado: @scoreEquilibradoSeleccionado</div>
|
<div>Score equilibrado: @scoreEquilibradoSeleccionado</div>
|
||||||
@@ -1177,6 +1240,22 @@
|
|||||||
<button type="button" class="config-close-btn" @onclick="ToggleConfigPanel">×</button>
|
<button type="button" class="config-close-btn" @onclick="ToggleConfigPanel">×</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="config-row config-row-full">
|
||||||
|
<div class="modelos-horarios">
|
||||||
|
<button type="button"
|
||||||
|
class="modelos-horarios__toggle"
|
||||||
|
@onclick="AbrirModelosHorariosPanel">
|
||||||
|
<span class="modelos-horarios__toggle-label">Modelo:</span>
|
||||||
|
<span class="modelos-horarios__activo">
|
||||||
|
@FechaModeloHorarioActivo
|
||||||
|
</span>
|
||||||
|
<span class="modelos-horarios__toggle-action">
|
||||||
|
Gestionar
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@* <div class="config-row config-row-full">
|
@* <div class="config-row config-row-full">
|
||||||
<label>Selección de paradas (rápida):</label>
|
<label>Selección de paradas (rápida):</label>
|
||||||
|
|
||||||
@@ -1254,22 +1333,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="config-row config-row-full">
|
|
||||||
<div class="modelos-horarios">
|
|
||||||
<button type="button"
|
|
||||||
class="modelos-horarios__toggle"
|
|
||||||
@onclick="AbrirModelosHorariosPanel">
|
|
||||||
<span class="modelos-horarios__toggle-label">Modelo DBUS</span>
|
|
||||||
<span class="modelos-horarios__activo">
|
|
||||||
@(ModeloHorarioActivo?.NombreArchivo ?? "Sin modelo cargado")
|
|
||||||
</span>
|
|
||||||
<span class="modelos-horarios__toggle-action">
|
|
||||||
Gestionar
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@* =========================
|
@* =========================
|
||||||
RANGO DE LÍNEAS
|
RANGO DE LÍNEAS
|
||||||
========================= *@
|
========================= *@
|
||||||
@@ -1470,13 +1533,10 @@
|
|||||||
<span>Factor</span>
|
<span>Factor</span>
|
||||||
<input type="number" class="config-input" step="0.1" min="0"
|
<input type="number" class="config-input" step="0.1" min="0"
|
||||||
placeholder="12"
|
placeholder="12"
|
||||||
title="1 metro andando pesa como este numero de metros en bus"
|
title="Factor del modo equilibrado"
|
||||||
@bind="FactorEquilibradoAndarBusTexto" @bind:event="oninput"
|
@bind="FactorEquilibradoAndarBusTexto" @bind:event="oninput"
|
||||||
@onblur="OnBlurFactorEquilibradoAndarBus" />
|
@onblur="OnBlurFactorEquilibradoAndarBus" />
|
||||||
</label>
|
</label>
|
||||||
<span class="muted config-equilibrado-help" title="Formula: metros en bus + metros andando x factor">
|
|
||||||
1 m andando = @FactorEquilibradoAndarBusResumen m bus
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public sealed class ParametrosPlanificadorRuta
|
|||||||
public int MinutosPorTransbordo { get; set; } = 5;
|
public int MinutosPorTransbordo { get; set; } = 5;
|
||||||
public int MargenTransbordoSegundos { get; set; } = 120;
|
public int MargenTransbordoSegundos { get; set; } = 120;
|
||||||
public int SegundosPorParadaBus { get; set; } = 30;
|
public int SegundosPorParadaBus { get; set; } = 30;
|
||||||
public double FactorPenalizacionDistanciaPie { get; set; } = 1.6;
|
public double FactorPenalizacionDistanciaPie { get; set; } = 1.35;
|
||||||
public double ParametroPenalizacionDistanciaPieMetros { get; set; } = 100.0;
|
public double ParametroPenalizacionDistanciaPieMetros { get; set; } = 100.0;
|
||||||
public double PesoTransbordoRankingMinutos { get; set; } = 4.0;
|
public double PesoTransbordoRankingMinutos { get; set; } = 4.0;
|
||||||
public double FactorEquilibradoAndarBus { get; set; } = 12.0;
|
public double FactorEquilibradoAndarBus { get; set; } = 12.0;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ public sealed class GeocodificadorLocalServicio : IGeocodificadorLocalServicio
|
|||||||
private readonly IWebHostEnvironment _entorno;
|
private readonly IWebHostEnvironment _entorno;
|
||||||
private readonly SemaphoreSlim _semaforoCarga = new(1, 1);
|
private readonly SemaphoreSlim _semaforoCarga = new(1, 1);
|
||||||
private readonly ConcurrentDictionary<string, LugarGeocodificadoCercano> _cacheCercanos = new(StringComparer.Ordinal);
|
private readonly ConcurrentDictionary<string, LugarGeocodificadoCercano> _cacheCercanos = new(StringComparer.Ordinal);
|
||||||
|
private readonly ConcurrentDictionary<string, IReadOnlyList<LugarGeocodificadoCercano>> _cacheCercanosPorRadio = new(StringComparer.Ordinal);
|
||||||
private IReadOnlyList<EntradaGeocodificada>? _indice;
|
private IReadOnlyList<EntradaGeocodificada>? _indice;
|
||||||
|
|
||||||
public GeocodificadorLocalServicio(IWebHostEnvironment entorno)
|
public GeocodificadorLocalServicio(IWebHostEnvironment entorno)
|
||||||
@@ -109,6 +110,73 @@ public sealed class GeocodificadorLocalServicio : IGeocodificadorLocalServicio
|
|||||||
return cercano;
|
return cercano;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<IReadOnlyList<LugarGeocodificadoCercano>> BuscarCercanosAsync(
|
||||||
|
double latitud,
|
||||||
|
double longitud,
|
||||||
|
double radioMetros = 20,
|
||||||
|
int maxResultados = 3,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!double.IsFinite(latitud) || !double.IsFinite(longitud))
|
||||||
|
return Array.Empty<LugarGeocodificadoCercano>();
|
||||||
|
|
||||||
|
radioMetros = double.IsFinite(radioMetros)
|
||||||
|
? Math.Clamp(radioMetros, 0, 1000)
|
||||||
|
: 20;
|
||||||
|
maxResultados = Math.Clamp(maxResultados, 1, 10);
|
||||||
|
|
||||||
|
var clave = ClaveCercanos(latitud, longitud, radioMetros, maxResultados);
|
||||||
|
if (_cacheCercanosPorRadio.TryGetValue(clave, out var cacheados))
|
||||||
|
return cacheados;
|
||||||
|
|
||||||
|
var indice = await ObtenerIndiceAsync(cancellationToken);
|
||||||
|
if (indice.Count == 0)
|
||||||
|
return Array.Empty<LugarGeocodificadoCercano>();
|
||||||
|
|
||||||
|
LugarGeocodificadoCercano? masCercano = null;
|
||||||
|
var dentroDelRadio = new List<LugarGeocodificadoCercano>();
|
||||||
|
|
||||||
|
foreach (var entrada in indice)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var lugar = entrada.Lugar;
|
||||||
|
var distancia = DistanciaHaversineMetros(latitud, longitud, lugar.Latitud, lugar.Longitud);
|
||||||
|
if (masCercano is null || distancia < masCercano.DistanciaMetros)
|
||||||
|
{
|
||||||
|
masCercano = new LugarGeocodificadoCercano
|
||||||
|
{
|
||||||
|
Lugar = lugar,
|
||||||
|
DistanciaMetros = distancia
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (distancia <= radioMetros)
|
||||||
|
{
|
||||||
|
dentroDelRadio.Add(new LugarGeocodificadoCercano
|
||||||
|
{
|
||||||
|
Lugar = lugar,
|
||||||
|
DistanciaMetros = distancia
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
IReadOnlyList<LugarGeocodificadoCercano> resultado = dentroDelRadio.Count > 0
|
||||||
|
? dentroDelRadio
|
||||||
|
.OrderBy(x => x.DistanciaMetros)
|
||||||
|
.Take(maxResultados)
|
||||||
|
.ToArray()
|
||||||
|
: masCercano is null
|
||||||
|
? Array.Empty<LugarGeocodificadoCercano>()
|
||||||
|
: new[] { masCercano! };
|
||||||
|
|
||||||
|
if (resultado.Count > 0)
|
||||||
|
_cacheCercanos.TryAdd(ClaveCercano(latitud, longitud), resultado[0]);
|
||||||
|
|
||||||
|
_cacheCercanosPorRadio.TryAdd(clave, resultado);
|
||||||
|
return resultado;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<IReadOnlyList<EntradaGeocodificada>> ObtenerIndiceAsync(CancellationToken cancellationToken)
|
private async Task<IReadOnlyList<EntradaGeocodificada>> ObtenerIndiceAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_indice is not null)
|
if (_indice is not null)
|
||||||
@@ -411,6 +479,9 @@ public sealed class GeocodificadorLocalServicio : IGeocodificadorLocalServicio
|
|||||||
private static string ClaveCercano(double latitud, double longitud)
|
private static string ClaveCercano(double latitud, double longitud)
|
||||||
=> $"{latitud.ToString("0.000000", CultureInfo.InvariantCulture)}|{longitud.ToString("0.000000", CultureInfo.InvariantCulture)}";
|
=> $"{latitud.ToString("0.000000", CultureInfo.InvariantCulture)}|{longitud.ToString("0.000000", CultureInfo.InvariantCulture)}";
|
||||||
|
|
||||||
|
private static string ClaveCercanos(double latitud, double longitud, double radioMetros, int maxResultados)
|
||||||
|
=> $"{ClaveCercano(latitud, longitud)}|{radioMetros.ToString("0.##", CultureInfo.InvariantCulture)}|{maxResultados}";
|
||||||
|
|
||||||
private static double DistanciaHaversineMetros(double lat1, double lon1, double lat2, double lon2)
|
private static double DistanciaHaversineMetros(double lat1, double lon1, double lat2, double lon2)
|
||||||
{
|
{
|
||||||
const double radioTierraMetros = 6371000.0;
|
const double radioTierraMetros = 6371000.0;
|
||||||
|
|||||||
@@ -14,5 +14,12 @@ public interface IGeocodificadorLocalServicio
|
|||||||
double longitud,
|
double longitud,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<IReadOnlyList<LugarGeocodificadoCercano>> BuscarCercanosAsync(
|
||||||
|
double latitud,
|
||||||
|
double longitud,
|
||||||
|
double radioMetros = 20,
|
||||||
|
int maxResultados = 3,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<int> ContarLugaresAsync(CancellationToken cancellationToken = default);
|
Task<int> ContarLugaresAsync(CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -313,7 +313,7 @@ internal static class UtilidadesTransitoDbus
|
|||||||
|
|
||||||
var factor = double.IsFinite(parametros.FactorPenalizacionDistanciaPie)
|
var factor = double.IsFinite(parametros.FactorPenalizacionDistanciaPie)
|
||||||
? parametros.FactorPenalizacionDistanciaPie
|
? parametros.FactorPenalizacionDistanciaPie
|
||||||
: 1.6;
|
: 1.35;
|
||||||
var parametroBaseMetros = double.IsFinite(parametros.ParametroPenalizacionDistanciaPieMetros)
|
var parametroBaseMetros = double.IsFinite(parametros.ParametroPenalizacionDistanciaPieMetros)
|
||||||
? Math.Abs(parametros.ParametroPenalizacionDistanciaPieMetros)
|
? Math.Abs(parametros.ParametroPenalizacionDistanciaPieMetros)
|
||||||
: 100.0;
|
: 100.0;
|
||||||
|
|||||||
@@ -13,6 +13,63 @@
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.inspector-address-list {
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
margin: 5px 0 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.popup-parada-body .inspector-address-item {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 22px minmax(0, 1fr);
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
margin: 0;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspector-address-number,
|
||||||
|
.rt-inspector-place-marker {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border: 2px solid #ecfeff;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #ffffff;
|
||||||
|
background: #0f766e;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1;
|
||||||
|
box-shadow: 0 0 0 2px rgba(15, 23, 42, .78), 0 4px 10px rgba(15, 23, 42, .3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspector-address-number {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
font-size: .68rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspector-address-item > span:last-child {
|
||||||
|
min-width: 0;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-inspector-place-icon {
|
||||||
|
border: 0 !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-inspector-place-marker {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: .72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rt-inspector-tooltip {
|
||||||
|
max-width: min(280px, calc(100vw - 28px));
|
||||||
|
white-space: normal;
|
||||||
|
font-size: .75rem;
|
||||||
|
}
|
||||||
|
|
||||||
.history-open-icon {
|
.history-open-icon {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
@@ -247,6 +304,14 @@ h1:focus {
|
|||||||
background: #1d4ed8;
|
background: #1d4ed8;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-ghost:disabled,
|
||||||
|
.btn-ghost:disabled:hover {
|
||||||
|
background: #111827;
|
||||||
|
color: #94a3b8;
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: .48;
|
||||||
|
}
|
||||||
.page, main, .full-page, .content, article.content {
|
.page, main, .full-page, .content, article.content {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -810,9 +875,9 @@ html, body {
|
|||||||
.route-visual-options {
|
.route-visual-options {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-start;
|
||||||
flex-wrap: wrap;
|
flex-wrap: nowrap;
|
||||||
gap: .35rem .5rem;
|
gap: .35rem;
|
||||||
margin: -.15rem 0 .4rem;
|
margin: -.15rem 0 .4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -877,27 +942,109 @@ html, body {
|
|||||||
opacity: .55;
|
opacity: .55;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-thresholds {
|
.route-elevation-settings {
|
||||||
|
position: relative;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: .25rem;
|
margin-left: auto;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-threshold {
|
.route-elevation-settings-trigger {
|
||||||
display: inline-flex;
|
display: grid;
|
||||||
align-items: center;
|
place-items: center;
|
||||||
gap: .18rem;
|
width: 26px;
|
||||||
min-height: 24px;
|
height: 24px;
|
||||||
|
padding: 0;
|
||||||
|
border: 1px solid rgba(148, 163, 184, .55);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(2, 6, 23, .78);
|
||||||
color: #cbd5e1;
|
color: #cbd5e1;
|
||||||
font-size: .64rem;
|
cursor: pointer;
|
||||||
font-weight: 700;
|
font-family: "Segoe UI Symbol", sans-serif;
|
||||||
white-space: nowrap;
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-threshold input {
|
.route-elevation-settings-trigger:hover,
|
||||||
width: 40px;
|
.route-elevation-settings-trigger--active {
|
||||||
|
border-color: rgba(56, 189, 248, .9);
|
||||||
|
background: rgba(30, 64, 175, .55);
|
||||||
|
color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-settings-trigger:disabled {
|
||||||
|
cursor: default;
|
||||||
|
opacity: .55;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-settings-popover {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 20;
|
||||||
|
top: calc(100% + 6px);
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
width: 238px;
|
||||||
|
max-width: calc(100vw - 24px);
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: .55rem;
|
||||||
|
border: 1px solid rgba(148, 163, 184, .55);
|
||||||
|
border-radius: 7px;
|
||||||
|
background: rgba(15, 23, 42, .99);
|
||||||
|
box-shadow: 0 14px 32px rgba(0, 0, 0, .5);
|
||||||
|
color: #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-settings-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: .5rem;
|
||||||
|
margin-bottom: .45rem;
|
||||||
|
font-size: .72rem;
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-settings-close {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: transparent;
|
||||||
|
color: #cbd5e1;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-settings-close:hover {
|
||||||
|
background: rgba(71, 85, 105, .55);
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-settings-fields {
|
||||||
|
display: grid;
|
||||||
|
gap: .35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-setting-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 44px 32px;
|
||||||
|
align-items: center;
|
||||||
|
gap: .35rem;
|
||||||
|
min-height: 26px;
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-size: .68rem;
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-setting-row input {
|
||||||
|
width: 44px;
|
||||||
|
height: 22px;
|
||||||
|
box-sizing: border-box;
|
||||||
border: 1px solid rgba(148, 163, 184, .55);
|
border: 1px solid rgba(148, 163, 184, .55);
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
background: rgba(2, 6, 23, .78);
|
background: rgba(2, 6, 23, .78);
|
||||||
@@ -909,15 +1056,27 @@ html, body {
|
|||||||
outline: none;
|
outline: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-threshold input:focus {
|
.route-elevation-setting-row input:focus {
|
||||||
border-color: rgba(56, 189, 248, .95);
|
border-color: rgba(56, 189, 248, .95);
|
||||||
box-shadow: 0 0 0 2px rgba(56, 189, 248, .18);
|
box-shadow: 0 0 0 2px rgba(56, 189, 248, .18);
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-threshold input:disabled {
|
.route-elevation-setting-row input:disabled {
|
||||||
opacity: .55;
|
opacity: .55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.route-slope-warning {
|
||||||
|
margin: 0 0 .45rem;
|
||||||
|
padding: .36rem .5rem;
|
||||||
|
border-left: 3px solid #f59e0b;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(245, 158, 11, .13);
|
||||||
|
color: #fde68a;
|
||||||
|
font-size: .72rem;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 1.25;
|
||||||
|
}
|
||||||
|
|
||||||
/* Lista de alternativas de ruta */
|
/* Lista de alternativas de ruta */
|
||||||
|
|
||||||
.route-alt-list {
|
.route-alt-list {
|
||||||
@@ -1623,16 +1782,9 @@ html, body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.config-dual-labeled--equilibrado {
|
.config-dual-labeled--equilibrado {
|
||||||
grid-template-columns: minmax(0, 4rem) minmax(0, 4.4rem);
|
grid-template-columns: minmax(0, 4rem);
|
||||||
align-items: end;
|
align-items: end;
|
||||||
}
|
justify-content: start;
|
||||||
|
|
||||||
.config-equilibrado-help {
|
|
||||||
align-self: end;
|
|
||||||
padding-bottom: .12rem;
|
|
||||||
font-size: .58rem;
|
|
||||||
line-height: 1.08;
|
|
||||||
text-align: left;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -2837,6 +2989,7 @@ html, body {
|
|||||||
|
|
||||||
.route-visual-options {
|
.route-visual-options {
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
gap: 6px 10px;
|
gap: 6px 10px;
|
||||||
margin: 0 0 8px;
|
margin: 0 0 8px;
|
||||||
}
|
}
|
||||||
@@ -2846,14 +2999,27 @@ html, body {
|
|||||||
font-size: .72rem;
|
font-size: .72rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-threshold {
|
.route-elevation-settings-trigger {
|
||||||
min-height: 44px;
|
width: 40px;
|
||||||
font-size: .68rem;
|
height: 40px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.route-elevation-threshold input {
|
.route-elevation-settings-popover {
|
||||||
|
right: 0;
|
||||||
|
left: auto;
|
||||||
|
width: 250px;
|
||||||
|
max-width: calc(100vw - 20px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-setting-row {
|
||||||
|
grid-template-columns: minmax(0, 1fr) 48px 36px;
|
||||||
|
min-height: 40px;
|
||||||
|
font-size: .72rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-setting-row input {
|
||||||
width: 48px;
|
width: 48px;
|
||||||
height: 40px;
|
height: 36px;
|
||||||
font-size: .78rem;
|
font-size: .78rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3056,8 +3222,7 @@ html, body {
|
|||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.config-dual-labeled__field span,
|
.config-dual-labeled__field span {
|
||||||
.config-equilibrado-help {
|
|
||||||
font-size: .64rem;
|
font-size: .64rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -96,6 +96,7 @@ window.rt.ensureRouteLayer = function () {
|
|||||||
window.rt._routeLayer = window.L.layerGroup().addTo(map);
|
window.rt._routeLayer = window.L.layerGroup().addTo(map);
|
||||||
}
|
}
|
||||||
if (!window.rt._routeLines) window.rt._routeLines = {};
|
if (!window.rt._routeLines) window.rt._routeLines = {};
|
||||||
|
if (!window.rt._routeCoords) window.rt._routeCoords = {};
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,6 +104,7 @@ window.rt.clearRoutes = function () {
|
|||||||
if (!window.rt.ensureRouteLayer()) return;
|
if (!window.rt.ensureRouteLayer()) return;
|
||||||
window.rt._routeLayer.clearLayers();
|
window.rt._routeLayer.clearLayers();
|
||||||
window.rt._routeLines = {};
|
window.rt._routeLines = {};
|
||||||
|
window.rt._routeCoords = {};
|
||||||
|
|
||||||
if (window.rt._routeStopsLayer) {
|
if (window.rt._routeStopsLayer) {
|
||||||
window.rt._routeStopsLayer.clearLayers();
|
window.rt._routeStopsLayer.clearLayers();
|
||||||
@@ -123,6 +125,19 @@ window.rt.drawRoute = function (id, coords, options) {
|
|||||||
delete window.rt._routeLines[arrowId];
|
delete window.rt._routeLines[arrowId];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const routeCoords = Array.isArray(coords)
|
||||||
|
? coords
|
||||||
|
.map(function (point) {
|
||||||
|
return [Number(point && point[0]), Number(point && point[1])];
|
||||||
|
})
|
||||||
|
.filter(function (point) {
|
||||||
|
return Number.isFinite(point[0]) && Number.isFinite(point[1]);
|
||||||
|
})
|
||||||
|
: [];
|
||||||
|
|
||||||
|
if (routeCoords.length > 0) window.rt._routeCoords[id] = routeCoords;
|
||||||
|
else delete window.rt._routeCoords[id];
|
||||||
|
|
||||||
const color = (options && options.color) || "#000";
|
const color = (options && options.color) || "#000";
|
||||||
const weight = (options && options.weight) || 4;
|
const weight = (options && options.weight) || 4;
|
||||||
const opacity = (options && options.opacity) || 0.9;
|
const opacity = (options && options.opacity) || 0.9;
|
||||||
@@ -307,14 +322,53 @@ window.rt.drawRoute = function (id, coords, options) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
window.rt.fitRoutes = function () {
|
window.rt.fitRoutes = function (coords) {
|
||||||
const map = window.__rtLeafletMap;
|
const map = window.__rtLeafletMap;
|
||||||
if (!map || !window.rt._routeLayer) return;
|
if (!map || !window.L) return false;
|
||||||
|
|
||||||
const bounds = window.rt._routeLayer.getBounds();
|
const routePoints = [];
|
||||||
if (bounds && bounds.isValid()) {
|
if (Array.isArray(coords)) {
|
||||||
map.fitBounds(bounds, { padding: [30, 30] });
|
coords.forEach(function (point) {
|
||||||
|
routePoints.push(point);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (!window.rt._routeCoords) return false;
|
||||||
|
Object.values(window.rt._routeCoords).forEach(function (routeCoords) {
|
||||||
|
if (!Array.isArray(routeCoords)) return;
|
||||||
|
routeCoords.forEach(function (point) {
|
||||||
|
routePoints.push(point);
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const points = routePoints
|
||||||
|
.filter(function (point) {
|
||||||
|
return Array.isArray(point)
|
||||||
|
&& point.length >= 2
|
||||||
|
&& Number.isFinite(Number(point[0]))
|
||||||
|
&& Number.isFinite(Number(point[1]));
|
||||||
|
})
|
||||||
|
.map(function (point) {
|
||||||
|
return [Number(point[0]), Number(point[1])];
|
||||||
|
});
|
||||||
|
|
||||||
|
if (points.length === 0) return false;
|
||||||
|
|
||||||
|
const visibleBounds = map.getBounds();
|
||||||
|
const algunPuntoVisible = visibleBounds && points.some(function (point) {
|
||||||
|
return visibleBounds.contains(window.L.latLng(point[0], point[1]));
|
||||||
|
});
|
||||||
|
|
||||||
|
if (algunPuntoVisible) return false;
|
||||||
|
|
||||||
|
const geometryBounds = window.L.latLngBounds(points);
|
||||||
|
if (!geometryBounds.isValid()) return false;
|
||||||
|
|
||||||
|
map.panTo(geometryBounds.getCenter(), {
|
||||||
|
animate: true,
|
||||||
|
duration: 0.35
|
||||||
|
});
|
||||||
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
window.rt = window.rt || {};
|
window.rt = window.rt || {};
|
||||||
@@ -1084,6 +1138,131 @@ window.rt.clearEndpointMarkers = function (kind) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
// Punto Z y lugares cercanos del callejero
|
||||||
|
// -----------------------------------------------------------------------------
|
||||||
|
window.rt.ensureInspectorLayer = function () {
|
||||||
|
const map = window.__rtLeafletMap;
|
||||||
|
if (!map || !window.L) return false;
|
||||||
|
|
||||||
|
if (!map.getPane("rtInspectorPane")) {
|
||||||
|
const pane = map.createPane("rtInspectorPane");
|
||||||
|
pane.style.zIndex = 790;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!window.rt._inspectorLayer) {
|
||||||
|
window.rt._inspectorLayer = window.L.layerGroup().addTo(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
window.rt.setInspectorMarkers = function (lat, lon, places) {
|
||||||
|
if (!window.rt.ensureInspectorLayer()) return;
|
||||||
|
|
||||||
|
window.rt._inspectorLayer.clearLayers();
|
||||||
|
|
||||||
|
const zLat = Number(lat);
|
||||||
|
const zLon = Number(lon);
|
||||||
|
if (!Number.isFinite(zLat) || !Number.isFinite(zLon)) return;
|
||||||
|
|
||||||
|
const puntoZ = window.L.circleMarker([zLat, zLon], {
|
||||||
|
pane: "rtInspectorPane",
|
||||||
|
radius: 12,
|
||||||
|
color: "#713f12",
|
||||||
|
weight: 3,
|
||||||
|
opacity: 1,
|
||||||
|
fillColor: "#fde047",
|
||||||
|
fillOpacity: 0.42,
|
||||||
|
bubblingMouseEvents: false
|
||||||
|
});
|
||||||
|
puntoZ.bindTooltip("Punto Z", {
|
||||||
|
direction: "top",
|
||||||
|
opacity: 0.95,
|
||||||
|
className: "rt-inspector-tooltip"
|
||||||
|
});
|
||||||
|
puntoZ.addTo(window.rt._inspectorLayer);
|
||||||
|
|
||||||
|
const lugares = Array.isArray(places) ? places.slice(0, 3) : [];
|
||||||
|
const coincidencias = {};
|
||||||
|
lugares.forEach(function (place) {
|
||||||
|
const placeLat = Number(place && place.latitud);
|
||||||
|
const placeLon = Number(place && place.longitud);
|
||||||
|
if (!Number.isFinite(placeLat) || !Number.isFinite(placeLon)) return;
|
||||||
|
|
||||||
|
const clave = `${placeLat.toFixed(6)}|${placeLon.toFixed(6)}`;
|
||||||
|
coincidencias[clave] = (coincidencias[clave] || 0) + 1;
|
||||||
|
});
|
||||||
|
|
||||||
|
const posicionCoincidente = {};
|
||||||
|
lugares.forEach(function (place, index) {
|
||||||
|
const placeLat = Number(place && place.latitud);
|
||||||
|
const placeLon = Number(place && place.longitud);
|
||||||
|
if (!Number.isFinite(placeLat) || !Number.isFinite(placeLon)) return;
|
||||||
|
|
||||||
|
const clave = `${placeLat.toFixed(6)}|${placeLon.toFixed(6)}`;
|
||||||
|
const totalCoincidentes = coincidencias[clave] || 1;
|
||||||
|
const posicion = posicionCoincidente[clave] || 0;
|
||||||
|
posicionCoincidente[clave] = posicion + 1;
|
||||||
|
|
||||||
|
let offsetX = 0;
|
||||||
|
if (totalCoincidentes === 2)
|
||||||
|
offsetX = posicion === 0 ? -14 : 14;
|
||||||
|
else if (totalCoincidentes >= 3)
|
||||||
|
offsetX = [-26, 0, 26][Math.min(posicion, 2)];
|
||||||
|
|
||||||
|
window.L.polyline(
|
||||||
|
[[zLat, zLon], [placeLat, placeLon]],
|
||||||
|
{
|
||||||
|
pane: "rtInspectorPane",
|
||||||
|
color: "#0f766e",
|
||||||
|
weight: 1.5,
|
||||||
|
opacity: 0.75,
|
||||||
|
dashArray: "4 5",
|
||||||
|
interactive: false
|
||||||
|
})
|
||||||
|
.addTo(window.rt._inspectorLayer);
|
||||||
|
|
||||||
|
const numero = index + 1;
|
||||||
|
const size = 24;
|
||||||
|
const icon = window.L.divIcon({
|
||||||
|
className: "rt-inspector-place-icon",
|
||||||
|
html: `<span class="rt-inspector-place-marker">${numero}</span>`,
|
||||||
|
iconSize: [size, size],
|
||||||
|
iconAnchor: [(size / 2) - offsetX, size / 2]
|
||||||
|
});
|
||||||
|
|
||||||
|
const texto = place && typeof place.texto === "string"
|
||||||
|
? place.texto
|
||||||
|
: `Lugar ${numero}`;
|
||||||
|
const marker = window.L.marker([placeLat, placeLon], {
|
||||||
|
pane: "rtInspectorPane",
|
||||||
|
icon,
|
||||||
|
title: texto,
|
||||||
|
keyboard: false,
|
||||||
|
bubblingMouseEvents: false,
|
||||||
|
zIndexOffset: 20 + numero
|
||||||
|
});
|
||||||
|
|
||||||
|
const tooltip = document.createElement("span");
|
||||||
|
tooltip.textContent = texto;
|
||||||
|
marker.bindTooltip(tooltip, {
|
||||||
|
direction: "top",
|
||||||
|
offset: [offsetX, -12],
|
||||||
|
opacity: 0.95,
|
||||||
|
className: "rt-inspector-tooltip"
|
||||||
|
});
|
||||||
|
marker.addTo(window.rt._inspectorLayer);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
window.rt.clearInspectorMarkers = function () {
|
||||||
|
if (window.rt._inspectorLayer) {
|
||||||
|
window.rt._inspectorLayer.clearLayers();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
window.rt = window.rt || {};
|
window.rt = window.rt || {};
|
||||||
|
|
||||||
window.rt.scrollToHorarioProximo = function () {
|
window.rt.scrollToHorarioProximo = function () {
|
||||||
|
|||||||
Reference in New Issue
Block a user