Mejoras en elevaciones, equilibrado y callejero inverso
Añadida visualización de pendientes en los tramos a pie con puntos coloreados: llano, subida y bajada. Añadidos umbrales configurables para subida y bajada junto al interruptor de elevaciones. Añadido callejero inverso en el detalle de la solución, mostrando el lugar del callejero más cercano al origen y al destino. Ajustado el modo Equilib. A para limpiar la tarjeta visualmente y guardar/restaurar el factor usado desde el historial. Mejoras menores en el refresco del resumen técnico de rutas y validación de compilación.
This commit is contained in:
@@ -7,6 +7,7 @@ using LeafletForBlazor;
|
|||||||
using Microsoft.AspNetCore.Components;
|
using Microsoft.AspNetCore.Components;
|
||||||
using Microsoft.JSInterop;
|
using Microsoft.JSInterop;
|
||||||
using RutasDBUS.Modelos.Planificacion;
|
using RutasDBUS.Modelos.Planificacion;
|
||||||
|
using RutasDBUS.Modelos.Geocodificacion;
|
||||||
using RutasDBUS.Modelos.RedBus;
|
using RutasDBUS.Modelos.RedBus;
|
||||||
using RutasDBUS.Servicios;
|
using RutasDBUS.Servicios;
|
||||||
using RutasDBUS.Servicios.Caminata;
|
using RutasDBUS.Servicios.Caminata;
|
||||||
@@ -344,6 +345,8 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private double _duracionTotalSegundos = 0;
|
private double _duracionTotalSegundos = 0;
|
||||||
private double? _altitudOrigenResumenRuta = null;
|
private double? _altitudOrigenResumenRuta = null;
|
||||||
private double? _altitudDestinoResumenRuta = null;
|
private double? _altitudDestinoResumenRuta = null;
|
||||||
|
private string? _callejeroOrigenResumenRuta = null;
|
||||||
|
private string? _callejeroDestinoResumenRuta = null;
|
||||||
|
|
||||||
private readonly List<AlternativaRuta> _opcionesRuta = new();
|
private readonly List<AlternativaRuta> _opcionesRuta = new();
|
||||||
private int _indiceAlternativaSeleccionada = 0;
|
private int _indiceAlternativaSeleccionada = 0;
|
||||||
@@ -351,6 +354,8 @@ 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 double _umbralPendienteSubidaRuta = 3.0;
|
||||||
|
private double _umbralPendienteBajadaRuta = 3.0;
|
||||||
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);
|
||||||
|
|
||||||
@@ -1037,7 +1042,8 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
double opacity,
|
double opacity,
|
||||||
string? dashArray = null,
|
string? dashArray = null,
|
||||||
bool arrowEnd = false,
|
bool arrowEnd = false,
|
||||||
string? lineCap = null)
|
string? lineCap = null,
|
||||||
|
IReadOnlyList<object>? elevationSegments = null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -1045,28 +1051,11 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
"rt.drawRoute",
|
"rt.drawRoute",
|
||||||
id,
|
id,
|
||||||
LineaToLatLng(linea),
|
LineaToLatLng(linea),
|
||||||
new { color, weight, opacity, dashArray, arrowEnd, lineCap }
|
new { color, weight, opacity, dashArray, arrowEnd, lineCap, elevationSegments }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
catch { }
|
catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task DrawRouteElevationPointsJsAsync(string id, IReadOnlyList<object> points)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await JS.InvokeVoidAsync("rt.drawElevPoints", $"route_elev_{id}", points, new
|
|
||||||
{
|
|
||||||
radius = 4,
|
|
||||||
opacity = 0.98,
|
|
||||||
fillOpacity = 0.95,
|
|
||||||
showLabels = false,
|
|
||||||
showSlopeTooltips = true,
|
|
||||||
className = "rt-route-elev-point"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ajusta rutas js.
|
/// Ajusta rutas js.
|
||||||
@@ -1163,6 +1152,67 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
|
|
||||||
return $"D: Y.Lat = {lat} X.Lon = {lon} Z = {alt}";
|
return $"D: Y.Lat = {lat} X.Lon = {lon} Z = {alt}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private string FormatearResumenCallejeroOrigen()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_callejeroOrigenResumenRuta))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return $"O callejero: {_callejeroOrigenResumenRuta}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private string FormatearResumenCallejeroDestino()
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(_callejeroDestinoResumenRuta))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return $"D callejero: {_callejeroDestinoResumenRuta}";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatearLugarCallejeroResumen(LugarGeocodificadoCercano? cercano)
|
||||||
|
{
|
||||||
|
if (cercano is null)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var nombre = LimpiarTextoCallejeroResumen(cercano.Lugar.Nombre);
|
||||||
|
if (string.IsNullOrWhiteSpace(nombre))
|
||||||
|
nombre = LimpiarTextoCallejeroResumen(cercano.Lugar.Subtitulo);
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(nombre))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
var distancia = FormatearDistanciaCallejeroResumen(cercano.DistanciaMetros);
|
||||||
|
if (string.IsNullOrWhiteSpace(distancia))
|
||||||
|
return nombre;
|
||||||
|
|
||||||
|
return cercano.DistanciaMetros <= 20
|
||||||
|
? $"{nombre} ({distancia})"
|
||||||
|
: $"mas cercano: {nombre} ({distancia})";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string LimpiarTextoCallejeroResumen(string texto)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(texto))
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
texto = texto
|
||||||
|
.Replace(" · ", " - ", StringComparison.Ordinal)
|
||||||
|
.Replace("·", "-", StringComparison.Ordinal)
|
||||||
|
.Replace("·", "-", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
return Regex.Replace(texto, @"\s+", " ").Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string FormatearDistanciaCallejeroResumen(double distanciaMetros)
|
||||||
|
{
|
||||||
|
if (!double.IsFinite(distanciaMetros) || distanciaMetros < 0)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
if (distanciaMetros < 1000)
|
||||||
|
return $"{Math.Round(distanciaMetros):0} m";
|
||||||
|
|
||||||
|
return $"{(distanciaMetros / 1000.0).ToString("0.000", CultureInfo.InvariantCulture)} km";
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Formatea duracion.
|
/// Formatea duracion.
|
||||||
@@ -1638,6 +1688,8 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
_duracionTotalSegundos = 0;
|
_duracionTotalSegundos = 0;
|
||||||
_altitudOrigenResumenRuta = null;
|
_altitudOrigenResumenRuta = null;
|
||||||
_altitudDestinoResumenRuta = null;
|
_altitudDestinoResumenRuta = null;
|
||||||
|
_callejeroOrigenResumenRuta = null;
|
||||||
|
_callejeroDestinoResumenRuta = null;
|
||||||
|
|
||||||
_paradaOrigenSeleccionada = null;
|
_paradaOrigenSeleccionada = null;
|
||||||
_paradaDestinoSeleccionada = null;
|
_paradaDestinoSeleccionada = null;
|
||||||
|
|||||||
@@ -1261,7 +1261,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
private bool _elevPopupMinimizado = false;
|
private bool _elevPopupMinimizado = false;
|
||||||
|
|
||||||
|
|
||||||
private const string _versionApp = "(v-20260709a)";
|
private const string _versionApp = "(v-20260710a)";
|
||||||
|
|
||||||
|
|
||||||
private double? _paradaElevG;
|
private double? _paradaElevG;
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ public partial class PlanificadorRutas
|
|||||||
public double? CoeficienteEsfuerzoAltura { get; set; }
|
public double? CoeficienteEsfuerzoAltura { get; set; }
|
||||||
public double? PesoTransbordoRankingMinutos { get; set; }
|
public double? PesoTransbordoRankingMinutos { get; set; }
|
||||||
public double? FactorEquilibradoAndarBus { get; set; }
|
public double? FactorEquilibradoAndarBus { get; set; }
|
||||||
|
public double? UmbralPendienteSubidaRuta { get; set; }
|
||||||
|
public double? UmbralPendienteBajadaRuta { 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; }
|
||||||
@@ -136,6 +138,8 @@ public partial class PlanificadorRutas
|
|||||||
CoeficienteEsfuerzoAltura = _coeficienteEsfuerzoAltura,
|
CoeficienteEsfuerzoAltura = _coeficienteEsfuerzoAltura,
|
||||||
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
PesoTransbordoRankingMinutos = _pesoTransbordoRankingMinutos,
|
||||||
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
|
||||||
|
UmbralPendienteSubidaRuta = _umbralPendienteSubidaRuta,
|
||||||
|
UmbralPendienteBajadaRuta = _umbralPendienteBajadaRuta,
|
||||||
VelocidadPieKmH = _velocidadPieKmH,
|
VelocidadPieKmH = _velocidadPieKmH,
|
||||||
VelocidadBusKmH = _velocidadBusKmH,
|
VelocidadBusKmH = _velocidadBusKmH,
|
||||||
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
|
ElevMaxPuntosPorTramo = _elevMaxPuntosPorTramo,
|
||||||
@@ -192,6 +196,10 @@ public partial class PlanificadorRutas
|
|||||||
_pesoTransbordoRankingMinutos = Math.Abs(pesoTransbordo - 8.0) < 0.0001 ? 4.0 : Math.Max(0, pesoTransbordo);
|
_pesoTransbordoRankingMinutos = Math.Abs(pesoTransbordo - 8.0) < 0.0001 ? 4.0 : Math.Max(0, pesoTransbordo);
|
||||||
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
|
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado && double.IsFinite(factorEquilibrado))
|
||||||
_factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
|
_factorEquilibradoAndarBus = Math.Max(0, factorEquilibrado);
|
||||||
|
if (config.UmbralPendienteSubidaRuta is { } umbralSubida && double.IsFinite(umbralSubida))
|
||||||
|
_umbralPendienteSubidaRuta = NormalizarUmbralPendienteRuta(umbralSubida);
|
||||||
|
if (config.UmbralPendienteBajadaRuta is { } umbralBajada && double.IsFinite(umbralBajada))
|
||||||
|
_umbralPendienteBajadaRuta = NormalizarUmbralPendienteRuta(umbralBajada);
|
||||||
_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 ||
|
||||||
@@ -295,6 +303,8 @@ public partial class PlanificadorRutas
|
|||||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
_pesoTransbordoRankingMinutos = 4.0;
|
_pesoTransbordoRankingMinutos = 4.0;
|
||||||
_factorEquilibradoAndarBus = 12.0;
|
_factorEquilibradoAndarBus = 12.0;
|
||||||
|
_umbralPendienteSubidaRuta = 3.0;
|
||||||
|
_umbralPendienteBajadaRuta = 3.0;
|
||||||
_velocidadPieKmH = 3.6;
|
_velocidadPieKmH = 3.6;
|
||||||
_velocidadBusKmH = 16.0;
|
_velocidadBusKmH = 16.0;
|
||||||
_minutosTransbordo = 5;
|
_minutosTransbordo = 5;
|
||||||
@@ -342,6 +352,8 @@ public partial class PlanificadorRutas
|
|||||||
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
|
||||||
_pesoTransbordoRankingMinutos = 4.0;
|
_pesoTransbordoRankingMinutos = 4.0;
|
||||||
_factorEquilibradoAndarBus = 12.0;
|
_factorEquilibradoAndarBus = 12.0;
|
||||||
|
_umbralPendienteSubidaRuta = 3.0;
|
||||||
|
_umbralPendienteBajadaRuta = 3.0;
|
||||||
_velocidadPieKmH = 3.6;
|
_velocidadPieKmH = 3.6;
|
||||||
_velocidadBusKmH = 16.0;
|
_velocidadBusKmH = 16.0;
|
||||||
_minutosTransbordo = 5;
|
_minutosTransbordo = 5;
|
||||||
|
|||||||
@@ -331,6 +331,43 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
_altitudDestinoResumenRuta = null;
|
_altitudDestinoResumenRuta = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task ActualizarCallejeroResumenRutaAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
if (_coordenadasOrigen is null || _coordenadasDestino is null)
|
||||||
|
{
|
||||||
|
_callejeroOrigenResumenRuta = null;
|
||||||
|
_callejeroDestinoResumenRuta = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var origen = await GeocodificadorLocal.BuscarMasCercanoAsync(
|
||||||
|
_coordenadasOrigen[0],
|
||||||
|
_coordenadasOrigen[1],
|
||||||
|
ct);
|
||||||
|
|
||||||
|
var destino = await GeocodificadorLocal.BuscarMasCercanoAsync(
|
||||||
|
_coordenadasDestino[0],
|
||||||
|
_coordenadasDestino[1],
|
||||||
|
ct);
|
||||||
|
|
||||||
|
_callejeroOrigenResumenRuta = FormatearLugarCallejeroResumen(origen);
|
||||||
|
_callejeroDestinoResumenRuta = FormatearLugarCallejeroResumen(destino);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
_callejeroOrigenResumenRuta = null;
|
||||||
|
_callejeroDestinoResumenRuta = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task ActualizarResumenTecnicoRutaAsync(CancellationToken ct)
|
||||||
|
{
|
||||||
|
await ActualizarAlturasResumenRutaAsync(ct);
|
||||||
|
await ActualizarCallejeroResumenRutaAsync(ct);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<string, double> _elevIdeeCache =
|
private static readonly ConcurrentDictionary<string, double> _elevIdeeCache =
|
||||||
@@ -2170,7 +2207,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
_hayRutaValida = false;
|
_hayRutaValida = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ActualizarAlturasResumenRutaAsync(CancellationToken.None);
|
await ActualizarResumenTecnicoRutaAsync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
@@ -2194,7 +2231,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
{
|
{
|
||||||
await RegistrarEscenarioActualEnHistorialLocalAsync();
|
await RegistrarEscenarioActualEnHistorialLocalAsync();
|
||||||
}
|
}
|
||||||
await ActualizarAlturasResumenRutaAsync(CancellationToken.None);
|
await ActualizarResumenTecnicoRutaAsync(CancellationToken.None);
|
||||||
_tiempoUltimoCalculoRuta = cronometroCalculo.Elapsed;
|
_tiempoUltimoCalculoRuta = cronometroCalculo.Elapsed;
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
@@ -2207,7 +2244,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
_hayRutaValida = false;
|
_hayRutaValida = false;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await ActualizarAlturasResumenRutaAsync(CancellationToken.None);
|
await ActualizarResumenTecnicoRutaAsync(CancellationToken.None);
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -76,6 +76,33 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
_ => !_mostrarElevacionesRuta
|
_ => !_mostrarElevacionesRuta
|
||||||
};
|
};
|
||||||
|
|
||||||
|
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
||||||
|
? "Aplicando elevaciones..."
|
||||||
|
: "Representando ruta seleccionada...");
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task CambiarUmbralElevacionRutaAsync(ChangeEventArgs e, bool esSubida)
|
||||||
|
{
|
||||||
|
var actual = esSubida
|
||||||
|
? _umbralPendienteSubidaRuta
|
||||||
|
: _umbralPendienteBajadaRuta;
|
||||||
|
var nuevo = ParseUmbralPendienteRuta(e.Value, actual);
|
||||||
|
|
||||||
|
if (esSubida)
|
||||||
|
_umbralPendienteSubidaRuta = nuevo;
|
||||||
|
else
|
||||||
|
_umbralPendienteBajadaRuta = nuevo;
|
||||||
|
|
||||||
|
await PersistirConfiguracionAsync();
|
||||||
|
|
||||||
|
if (_mostrarElevacionesRuta)
|
||||||
|
await RepintarRutaSeleccionadaPorElevacionesAsync("Aplicando elevaciones...");
|
||||||
|
else
|
||||||
|
StateHasChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task RepintarRutaSeleccionadaPorElevacionesAsync(string mensaje)
|
||||||
|
{
|
||||||
if (_opcionesRuta.Count == 0 ||
|
if (_opcionesRuta.Count == 0 ||
|
||||||
_indiceAlternativaSeleccionada < 0 ||
|
_indiceAlternativaSeleccionada < 0 ||
|
||||||
_indiceAlternativaSeleccionada >= _opcionesRuta.Count)
|
_indiceAlternativaSeleccionada >= _opcionesRuta.Count)
|
||||||
@@ -85,9 +112,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
_pintandoElevacionesRuta = true;
|
_pintandoElevacionesRuta = true;
|
||||||
_mensajeRuta = _mostrarElevacionesRuta
|
_mensajeRuta = mensaje;
|
||||||
? "Aplicando elevaciones..."
|
|
||||||
: "Representando ruta seleccionada...";
|
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
|
|
||||||
@@ -103,6 +128,23 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static double ParseUmbralPendienteRuta(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 NormalizarUmbralPendienteRuta(actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NormalizarUmbralPendienteRuta(nuevo);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double NormalizarUmbralPendienteRuta(double valor)
|
||||||
|
=> double.IsFinite(valor)
|
||||||
|
? Math.Clamp(Math.Abs(valor), 0.0, 50.0)
|
||||||
|
: 3.0;
|
||||||
|
|
||||||
private async Task DrawWalkingRouteJsAsync(
|
private async Task DrawWalkingRouteJsAsync(
|
||||||
string id,
|
string id,
|
||||||
double[,] linea,
|
double[,] linea,
|
||||||
@@ -110,7 +152,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
int versionPintado,
|
int versionPintado,
|
||||||
bool arrowEnd = true)
|
bool arrowEnd = true)
|
||||||
{
|
{
|
||||||
var puntosElevacion = await ConstruirPuntosElevacionMapaAsync(linea, CancellationToken.None);
|
var segmentosElevacion = await ConstruirSegmentosElevacionMapaAsync(linea, CancellationToken.None);
|
||||||
ThrowIfPintadoObsoleto(versionPintado);
|
ThrowIfPintadoObsoleto(versionPintado);
|
||||||
|
|
||||||
await DrawRouteJsAsync(
|
await DrawRouteJsAsync(
|
||||||
@@ -121,16 +163,11 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
0.9,
|
0.9,
|
||||||
dashArray: DashArrayPiePunteado,
|
dashArray: DashArrayPiePunteado,
|
||||||
arrowEnd: arrowEnd,
|
arrowEnd: arrowEnd,
|
||||||
lineCap: "round");
|
lineCap: "round",
|
||||||
|
elevationSegments: segmentosElevacion);
|
||||||
if (puntosElevacion is { Count: > 0 })
|
|
||||||
{
|
|
||||||
ThrowIfPintadoObsoleto(versionPintado);
|
|
||||||
await DrawRouteElevationPointsJsAsync(id, puntosElevacion);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<List<object>?> ConstruirPuntosElevacionMapaAsync(double[,] linea, CancellationToken ct)
|
private async Task<List<object>?> ConstruirSegmentosElevacionMapaAsync(double[,] linea, CancellationToken ct)
|
||||||
{
|
{
|
||||||
if (!_mostrarElevacionesRuta || linea.GetLength(0) < 2)
|
if (!_mostrarElevacionesRuta || linea.GetLength(0) < 2)
|
||||||
return null;
|
return null;
|
||||||
@@ -139,7 +176,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
if (muestras.Count < 2)
|
if (muestras.Count < 2)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var puntos = new List<object>();
|
var segmentos = new List<object>();
|
||||||
var hayPendienteCalculada = false;
|
var hayPendienteCalculada = false;
|
||||||
|
|
||||||
for (var i = 1; i < muestras.Count; i++)
|
for (var i = 1; i < muestras.Count; i++)
|
||||||
@@ -168,10 +205,13 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (categoria, color) = ClasificarPendienteMapa(pendiente);
|
var (categoria, color) = ClasificarPendienteMapa(pendiente);
|
||||||
puntos.Add(new
|
segmentos.Add(new
|
||||||
{
|
{
|
||||||
lat = actual.Latitud,
|
coords = new[]
|
||||||
lon = actual.Longitud,
|
{
|
||||||
|
new[] { anterior.Latitud, anterior.Longitud },
|
||||||
|
new[] { actual.Latitud, actual.Longitud }
|
||||||
|
},
|
||||||
color,
|
color,
|
||||||
category = categoria,
|
category = categoria,
|
||||||
slope = pendiente,
|
slope = pendiente,
|
||||||
@@ -181,32 +221,28 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return hayPendienteCalculada && puntos.Count > 0
|
return hayPendienteCalculada && segmentos.Count > 0
|
||||||
? puntos
|
? segmentos
|
||||||
: null;
|
: null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static double? ElevacionPreferidaMapa(MuestraElevacion muestra)
|
private static double? ElevacionPreferidaMapa(MuestraElevacion muestra)
|
||||||
=> muestra.ElevacionIdee ?? muestra.Elevacion;
|
=> muestra.ElevacionIdee ?? muestra.Elevacion;
|
||||||
|
|
||||||
private static (string Categoria, string Color) ClasificarPendienteMapa(double? pendientePorcentaje)
|
private (string Categoria, string Color) ClasificarPendienteMapa(double? pendientePorcentaje)
|
||||||
{
|
{
|
||||||
if (pendientePorcentaje is null || !double.IsFinite(pendientePorcentaje.Value))
|
if (pendientePorcentaje is null || !double.IsFinite(pendientePorcentaje.Value))
|
||||||
return ("sin-datos", "#94a3b8");
|
return ("sin-datos", "#94a3b8");
|
||||||
|
|
||||||
if (pendientePorcentaje.Value >= 8.0)
|
var umbralSubida = NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta);
|
||||||
return ("subida-fuerte", "#ef4444");
|
var umbralBajada = NormalizarUmbralPendienteRuta(_umbralPendienteBajadaRuta);
|
||||||
|
if (pendientePorcentaje.Value >= umbralSubida)
|
||||||
|
return ("subida", "#000000");
|
||||||
|
|
||||||
if (pendientePorcentaje.Value >= 3.0)
|
if (pendientePorcentaje.Value <= -umbralBajada)
|
||||||
return ("subida", "#f97316");
|
return ("bajada", "#2563eb");
|
||||||
|
|
||||||
if (pendientePorcentaje.Value <= -8.0)
|
return ("llano", "#dc2626");
|
||||||
return ("bajada-fuerte", "#1d4ed8");
|
|
||||||
|
|
||||||
if (pendientePorcentaje.Value <= -3.0)
|
|
||||||
return ("bajada", "#38bdf8");
|
|
||||||
|
|
||||||
return ("llano", "#facc15");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -238,7 +274,7 @@ public partial class PlanificadorRutas : ComponentBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
ThrowIfPintadoObsoleto(versionPintado);
|
ThrowIfPintadoObsoleto(versionPintado);
|
||||||
await ActualizarAlturasResumenRutaAsync(CancellationToken.None);
|
await ActualizarResumenTecnicoRutaAsync(CancellationToken.None);
|
||||||
ThrowIfPintadoObsoleto(versionPintado);
|
ThrowIfPintadoObsoleto(versionPintado);
|
||||||
await CerrarPopupsAsync();
|
await CerrarPopupsAsync();
|
||||||
|
|
||||||
|
|||||||
@@ -653,6 +653,31 @@
|
|||||||
@(_pintandoElevacionesRuta ? "Elevaciones..." : "Elevaciones")
|
@(_pintandoElevacionesRuta ? "Elevaciones..." : "Elevaciones")
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<div class="route-elevation-thresholds" title="Porcentaje minimo para colorear subida o bajada">
|
||||||
|
<label class="route-elevation-threshold">
|
||||||
|
<span>Sube</span>
|
||||||
|
<input type="number"
|
||||||
|
min="0"
|
||||||
|
max="50"
|
||||||
|
step="0.5"
|
||||||
|
value="@_umbralPendienteSubidaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, true))" />
|
||||||
|
<span>%</span>
|
||||||
|
</label>
|
||||||
|
<label class="route-elevation-threshold">
|
||||||
|
<span>Baja</span>
|
||||||
|
<input type="number"
|
||||||
|
min="0"
|
||||||
|
max="50"
|
||||||
|
step="0.5"
|
||||||
|
value="@_umbralPendienteBajadaRuta.ToString("0.#", CultureInfo.InvariantCulture)"
|
||||||
|
disabled="@_pintandoElevacionesRuta"
|
||||||
|
@onchange="@(e => CambiarUmbralElevacionRutaAsync(e, false))" />
|
||||||
|
<span>%</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if (_opcionesRuta.Count > 0)
|
@if (_opcionesRuta.Count > 0)
|
||||||
@@ -885,6 +910,8 @@
|
|||||||
var dCorregida = CalcularDistanciaCorregidaAlternativa(altSeleccionada);
|
var dCorregida = CalcularDistanciaCorregidaAlternativa(altSeleccionada);
|
||||||
var xyzOrigen = FormatearResumenXYZOrigen();
|
var xyzOrigen = FormatearResumenXYZOrigen();
|
||||||
var xyzDestino = FormatearResumenXYZDestino();
|
var xyzDestino = FormatearResumenXYZDestino();
|
||||||
|
var callejeroOrigen = FormatearResumenCallejeroOrigen();
|
||||||
|
var callejeroDestino = FormatearResumenCallejeroDestino();
|
||||||
var tiempoCalculo = FormatearTiempoCalculoRuta();
|
var tiempoCalculo = FormatearTiempoCalculoRuta();
|
||||||
var scoreEquilibradoSeleccionado = CriterioPrioritarioEquilibradoActivo
|
var scoreEquilibradoSeleccionado = CriterioPrioritarioEquilibradoActivo
|
||||||
? FormatearScoreEquilibradoAlternativa(altSeleccionada)
|
? FormatearScoreEquilibradoAlternativa(altSeleccionada)
|
||||||
@@ -920,10 +947,18 @@
|
|||||||
{
|
{
|
||||||
<div style="white-space: pre-wrap;">@xyzOrigen</div>
|
<div style="white-space: pre-wrap;">@xyzOrigen</div>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(callejeroOrigen))
|
||||||
|
{
|
||||||
|
<div style="white-space: pre-wrap;">@callejeroOrigen</div>
|
||||||
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(xyzDestino))
|
@if (!string.IsNullOrWhiteSpace(xyzDestino))
|
||||||
{
|
{
|
||||||
<div style="white-space: pre-wrap;">@xyzDestino</div>
|
<div style="white-space: pre-wrap;">@xyzDestino</div>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(callejeroDestino))
|
||||||
|
{
|
||||||
|
<div style="white-space: pre-wrap;">@callejeroDestino</div>
|
||||||
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(tiempoCalculo))
|
@if (!string.IsNullOrWhiteSpace(tiempoCalculo))
|
||||||
{
|
{
|
||||||
<div>Tiempo de calculo: @tiempoCalculo</div>
|
<div>Tiempo de calculo: @tiempoCalculo</div>
|
||||||
@@ -951,13 +986,17 @@
|
|||||||
var dRectaSinRuta = DistanciaLineaRectaOrigenDestino();
|
var dRectaSinRuta = DistanciaLineaRectaOrigenDestino();
|
||||||
var xyzOrigenSinRuta = FormatearResumenXYZOrigen();
|
var xyzOrigenSinRuta = FormatearResumenXYZOrigen();
|
||||||
var xyzDestinoSinRuta = FormatearResumenXYZDestino();
|
var xyzDestinoSinRuta = FormatearResumenXYZDestino();
|
||||||
|
var callejeroOrigenSinRuta = FormatearResumenCallejeroOrigen();
|
||||||
|
var callejeroDestinoSinRuta = FormatearResumenCallejeroDestino();
|
||||||
var tiempoCalculoSinRuta = FormatearTiempoCalculoRuta();
|
var tiempoCalculoSinRuta = FormatearTiempoCalculoRuta();
|
||||||
|
|
||||||
<p class="route-main-text">@mensajeSinRuta</p>
|
<p class="route-main-text">@mensajeSinRuta</p>
|
||||||
|
|
||||||
@if (dRectaSinRuta > 0
|
@if (dRectaSinRuta > 0
|
||||||
|| !string.IsNullOrWhiteSpace(xyzOrigenSinRuta)
|
|| !string.IsNullOrWhiteSpace(xyzOrigenSinRuta)
|
||||||
|| !string.IsNullOrWhiteSpace(xyzDestinoSinRuta))
|
|| !string.IsNullOrWhiteSpace(xyzDestinoSinRuta)
|
||||||
|
|| !string.IsNullOrWhiteSpace(callejeroOrigenSinRuta)
|
||||||
|
|| !string.IsNullOrWhiteSpace(callejeroDestinoSinRuta))
|
||||||
{
|
{
|
||||||
<div class="muted" style="font-size:0.75rem;margin-top:4px;">
|
<div class="muted" style="font-size:0.75rem;margin-top:4px;">
|
||||||
@if (dRectaSinRuta > 0)
|
@if (dRectaSinRuta > 0)
|
||||||
@@ -968,10 +1007,18 @@
|
|||||||
{
|
{
|
||||||
<div style="white-space: pre-wrap;">@xyzOrigenSinRuta</div>
|
<div style="white-space: pre-wrap;">@xyzOrigenSinRuta</div>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(callejeroOrigenSinRuta))
|
||||||
|
{
|
||||||
|
<div style="white-space: pre-wrap;">@callejeroOrigenSinRuta</div>
|
||||||
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(xyzDestinoSinRuta))
|
@if (!string.IsNullOrWhiteSpace(xyzDestinoSinRuta))
|
||||||
{
|
{
|
||||||
<div style="white-space: pre-wrap;">@xyzDestinoSinRuta</div>
|
<div style="white-space: pre-wrap;">@xyzDestinoSinRuta</div>
|
||||||
}
|
}
|
||||||
|
@if (!string.IsNullOrWhiteSpace(callejeroDestinoSinRuta))
|
||||||
|
{
|
||||||
|
<div style="white-space: pre-wrap;">@callejeroDestinoSinRuta</div>
|
||||||
|
}
|
||||||
@if (!string.IsNullOrWhiteSpace(tiempoCalculoSinRuta))
|
@if (!string.IsNullOrWhiteSpace(tiempoCalculoSinRuta))
|
||||||
{
|
{
|
||||||
<div>Tiempo de calculo: @tiempoCalculoSinRuta</div>
|
<div>Tiempo de calculo: @tiempoCalculoSinRuta</div>
|
||||||
|
|||||||
@@ -11,3 +11,9 @@ public sealed class LugarGeocodificado
|
|||||||
public double Latitud { get; init; }
|
public double Latitud { get; init; }
|
||||||
public double Longitud { get; init; }
|
public double Longitud { get; init; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public sealed class LugarGeocodificadoCercano
|
||||||
|
{
|
||||||
|
public LugarGeocodificado Lugar { get; init; } = new();
|
||||||
|
public double DistanciaMetros { get; init; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using System.Collections.Concurrent;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.IO.Compression;
|
using System.IO.Compression;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
@@ -13,6 +14,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 IReadOnlyList<EntradaGeocodificada>? _indice;
|
private IReadOnlyList<EntradaGeocodificada>? _indice;
|
||||||
|
|
||||||
public GeocodificadorLocalServicio(IWebHostEnvironment entorno)
|
public GeocodificadorLocalServicio(IWebHostEnvironment entorno)
|
||||||
@@ -64,6 +66,51 @@ public sealed class GeocodificadorLocalServicio : IGeocodificadorLocalServicio
|
|||||||
return indice.Count;
|
return indice.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async Task<LugarGeocodificadoCercano?> BuscarMasCercanoAsync(
|
||||||
|
double latitud,
|
||||||
|
double longitud,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
if (!double.IsFinite(latitud) || !double.IsFinite(longitud))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var clave = ClaveCercano(latitud, longitud);
|
||||||
|
if (_cacheCercanos.TryGetValue(clave, out var cercanoCacheado))
|
||||||
|
return cercanoCacheado;
|
||||||
|
|
||||||
|
var indice = await ObtenerIndiceAsync(cancellationToken);
|
||||||
|
if (indice.Count == 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
EntradaGeocodificada? mejorEntrada = null;
|
||||||
|
var mejorDistancia = double.MaxValue;
|
||||||
|
|
||||||
|
foreach (var entrada in indice)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
|
||||||
|
var lugar = entrada.Lugar;
|
||||||
|
var distancia = DistanciaHaversineMetros(latitud, longitud, lugar.Latitud, lugar.Longitud);
|
||||||
|
if (distancia >= mejorDistancia)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
mejorDistancia = distancia;
|
||||||
|
mejorEntrada = entrada;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mejorEntrada is null)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
var cercano = new LugarGeocodificadoCercano
|
||||||
|
{
|
||||||
|
Lugar = mejorEntrada.Lugar,
|
||||||
|
DistanciaMetros = mejorDistancia
|
||||||
|
};
|
||||||
|
|
||||||
|
_cacheCercanos.TryAdd(clave, cercano);
|
||||||
|
return cercano;
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -363,6 +410,29 @@ public sealed class GeocodificadorLocalServicio : IGeocodificadorLocalServicio
|
|||||||
private static string PrimerValor(params string[] valores)
|
private static string PrimerValor(params string[] valores)
|
||||||
=> valores.FirstOrDefault(valor => !string.IsNullOrWhiteSpace(valor))?.Trim() ?? "";
|
=> valores.FirstOrDefault(valor => !string.IsNullOrWhiteSpace(valor))?.Trim() ?? "";
|
||||||
|
|
||||||
|
private static string ClaveCercano(double latitud, double longitud)
|
||||||
|
=> $"{latitud.ToString("0.000000", CultureInfo.InvariantCulture)}|{longitud.ToString("0.000000", CultureInfo.InvariantCulture)}";
|
||||||
|
|
||||||
|
private static double DistanciaHaversineMetros(double lat1, double lon1, double lat2, double lon2)
|
||||||
|
{
|
||||||
|
const double radioTierraMetros = 6371000.0;
|
||||||
|
var dLat = GradosARadianes(lat2 - lat1);
|
||||||
|
var dLon = GradosARadianes(lon2 - lon1);
|
||||||
|
var rLat1 = GradosARadianes(lat1);
|
||||||
|
var rLat2 = GradosARadianes(lat2);
|
||||||
|
|
||||||
|
var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) +
|
||||||
|
Math.Cos(rLat1) * Math.Cos(rLat2) *
|
||||||
|
Math.Sin(dLon / 2) * Math.Sin(dLon / 2);
|
||||||
|
|
||||||
|
a = Math.Clamp(a, 0.0, 1.0);
|
||||||
|
var c = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
|
||||||
|
return radioTierraMetros * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double GradosARadianes(double grados)
|
||||||
|
=> grados * Math.PI / 180.0;
|
||||||
|
|
||||||
private static List<string> ParsearCsv(string linea)
|
private static List<string> ParsearCsv(string linea)
|
||||||
{
|
{
|
||||||
var campos = new List<string>();
|
var campos = new List<string>();
|
||||||
|
|||||||
@@ -9,5 +9,10 @@ public interface IGeocodificadorLocalServicio
|
|||||||
int maxResultados = 8,
|
int maxResultados = 8,
|
||||||
CancellationToken cancellationToken = default);
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
|
Task<LugarGeocodificadoCercano?> BuscarMasCercanoAsync(
|
||||||
|
double latitud,
|
||||||
|
double longitud,
|
||||||
|
CancellationToken cancellationToken = default);
|
||||||
|
|
||||||
Task<int> ContarLugaresAsync(CancellationToken cancellationToken = default);
|
Task<int> ContarLugaresAsync(CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -811,6 +811,8 @@ html, body {
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: .35rem .5rem;
|
||||||
margin: -.15rem 0 .4rem;
|
margin: -.15rem 0 .4rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -875,6 +877,47 @@ html, body {
|
|||||||
opacity: .55;
|
opacity: .55;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.route-elevation-thresholds {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .25rem;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-threshold {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: .18rem;
|
||||||
|
min-height: 24px;
|
||||||
|
color: #cbd5e1;
|
||||||
|
font-size: .64rem;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-threshold input {
|
||||||
|
width: 40px;
|
||||||
|
height: 22px;
|
||||||
|
border: 1px solid rgba(148, 163, 184, .55);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: rgba(2, 6, 23, .78);
|
||||||
|
color: #f8fafc;
|
||||||
|
font-size: .68rem;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0 .18rem;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-threshold input:focus {
|
||||||
|
border-color: rgba(56, 189, 248, .95);
|
||||||
|
box-shadow: 0 0 0 2px rgba(56, 189, 248, .18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.route-elevation-threshold input:disabled {
|
||||||
|
opacity: .55;
|
||||||
|
}
|
||||||
|
|
||||||
/* Lista de alternativas de ruta */
|
/* Lista de alternativas de ruta */
|
||||||
|
|
||||||
.route-alt-list {
|
.route-alt-list {
|
||||||
@@ -2108,53 +2151,6 @@ html, body {
|
|||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rt-route-elev-point {
|
|
||||||
stroke: #020617 !important;
|
|
||||||
stroke-width: 1.8px !important;
|
|
||||||
filter: drop-shadow(0 1px 2px rgba(2, 6, 23, .75));
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-route-elev-point--subida,
|
|
||||||
.rt-route-elev-point--subida-fuerte {
|
|
||||||
animation: rt-route-elev-pulse-up 1.45s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-route-elev-point--bajada,
|
|
||||||
.rt-route-elev-point--bajada-fuerte {
|
|
||||||
animation: rt-route-elev-pulse-down 1.45s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rt-route-elev-point--llano {
|
|
||||||
opacity: .88;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rt-route-elev-pulse-up {
|
|
||||||
0%, 100% {
|
|
||||||
stroke-width: 1.8px;
|
|
||||||
opacity: .95;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
stroke-width: 4px;
|
|
||||||
opacity: .72;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes rt-route-elev-pulse-down {
|
|
||||||
0%, 100% {
|
|
||||||
stroke-width: 1.8px;
|
|
||||||
opacity: .95;
|
|
||||||
}
|
|
||||||
|
|
||||||
50% {
|
|
||||||
stroke-width: 3.5px;
|
|
||||||
opacity: .68;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.leaflet-control-zoom-indicator {
|
.leaflet-control-zoom-indicator {
|
||||||
background: rgba(15,23,42,.96);
|
background: rgba(15,23,42,.96);
|
||||||
color: #e5e7eb;
|
color: #e5e7eb;
|
||||||
|
|||||||
@@ -103,7 +103,6 @@ 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 = {};
|
||||||
if (window.rt.clearRouteElevPoints) window.rt.clearRouteElevPoints();
|
|
||||||
|
|
||||||
if (window.rt._routeStopsLayer) {
|
if (window.rt._routeStopsLayer) {
|
||||||
window.rt._routeStopsLayer.clearLayers();
|
window.rt._routeStopsLayer.clearLayers();
|
||||||
@@ -130,6 +129,9 @@ window.rt.drawRoute = function (id, coords, options) {
|
|||||||
const dashArray = options && options.dashArray;
|
const dashArray = options && options.dashArray;
|
||||||
const lineCap = options && options.lineCap;
|
const lineCap = options && options.lineCap;
|
||||||
const arrowEnd = !!(options && options.arrowEnd);
|
const arrowEnd = !!(options && options.arrowEnd);
|
||||||
|
const elevationSegments = Array.isArray(options && options.elevationSegments)
|
||||||
|
? options.elevationSegments
|
||||||
|
: [];
|
||||||
|
|
||||||
const attachRouteClick = function (line) {
|
const attachRouteClick = function (line) {
|
||||||
line.on("click", function (e) {
|
line.on("click", function (e) {
|
||||||
@@ -153,13 +155,61 @@ window.rt.drawRoute = function (id, coords, options) {
|
|||||||
return line;
|
return line;
|
||||||
};
|
};
|
||||||
|
|
||||||
const lineOpts = { color, weight, opacity };
|
let arrowColor = color;
|
||||||
if (dashArray) lineOpts.dashArray = dashArray;
|
let hasElevationPaint = false;
|
||||||
if (lineCap) lineOpts.lineCap = lineCap;
|
|
||||||
|
|
||||||
const line = buildLine(coords, lineOpts);
|
if (elevationSegments.length > 0 && coords && coords.length >= 2) {
|
||||||
line.addTo(window.rt._routeLayer);
|
const group = window.L.layerGroup();
|
||||||
window.rt._routeLines[id] = line;
|
|
||||||
|
elevationSegments.forEach(function (segment) {
|
||||||
|
const segmentCoords = segment && segment.coords;
|
||||||
|
if (!Array.isArray(segmentCoords) || segmentCoords.length < 2) return;
|
||||||
|
|
||||||
|
const segmentColor = (segment && segment.color) || color;
|
||||||
|
arrowColor = segmentColor;
|
||||||
|
|
||||||
|
const segmentOpts = {
|
||||||
|
color: segmentColor,
|
||||||
|
weight,
|
||||||
|
opacity,
|
||||||
|
lineCap: lineCap || "round"
|
||||||
|
};
|
||||||
|
if (dashArray) segmentOpts.dashArray = dashArray;
|
||||||
|
|
||||||
|
const segmentLine = buildLine(segmentCoords, segmentOpts);
|
||||||
|
const slope = segment && typeof segment.slope === "number" ? segment.slope : null;
|
||||||
|
const label = slope !== null && Number.isFinite(slope)
|
||||||
|
? ((slope > 0 ? "+" : "") + slope.toFixed(1) + "%")
|
||||||
|
: (segment && segment.label ? String(segment.label) : "");
|
||||||
|
|
||||||
|
if (label) {
|
||||||
|
segmentLine.bindTooltip(label, {
|
||||||
|
sticky: true,
|
||||||
|
direction: "top",
|
||||||
|
opacity: 0.92,
|
||||||
|
className: "rt-elev-tooltip"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
segmentLine.addTo(group);
|
||||||
|
hasElevationPaint = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasElevationPaint) {
|
||||||
|
group.addTo(window.rt._routeLayer);
|
||||||
|
window.rt._routeLines[id] = group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasElevationPaint) {
|
||||||
|
const lineOpts = { color, weight, opacity };
|
||||||
|
if (dashArray) lineOpts.dashArray = dashArray;
|
||||||
|
if (lineCap) lineOpts.lineCap = lineCap;
|
||||||
|
|
||||||
|
const line = buildLine(coords, lineOpts);
|
||||||
|
line.addTo(window.rt._routeLayer);
|
||||||
|
window.rt._routeLines[id] = line;
|
||||||
|
}
|
||||||
|
|
||||||
if (arrowEnd && coords && coords.length >= 2) {
|
if (arrowEnd && coords && coords.length >= 2) {
|
||||||
const a = coords[coords.length - 2];
|
const a = coords[coords.length - 2];
|
||||||
@@ -183,7 +233,7 @@ window.rt.drawRoute = function (id, coords, options) {
|
|||||||
html: `<div class="${arrowClass}" style="
|
html: `<div class="${arrowClass}" style="
|
||||||
width:${size}px;height:${size}px;
|
width:${size}px;height:${size}px;
|
||||||
transform: rotate(${brng}deg);
|
transform: rotate(${brng}deg);
|
||||||
--arrow-color:${color};
|
--arrow-color:${arrowColor};
|
||||||
">${arrowInnerHtml}</div>`,
|
">${arrowInnerHtml}</div>`,
|
||||||
iconSize: [size, size],
|
iconSize: [size, size],
|
||||||
iconAnchor: [size / 2, size / 2]
|
iconAnchor: [size / 2, size / 2]
|
||||||
@@ -281,18 +331,6 @@ window.rt.clearElevPoints = function (id) {
|
|||||||
window.rt._elevGroups = {};
|
window.rt._elevGroups = {};
|
||||||
};
|
};
|
||||||
|
|
||||||
window.rt.clearRouteElevPoints = function () {
|
|
||||||
if (!window.rt._elevLayer || !window.rt._elevGroups) return;
|
|
||||||
|
|
||||||
Object.keys(window.rt._elevGroups)
|
|
||||||
.filter(id => id.indexOf("route_elev_") === 0)
|
|
||||||
.forEach(id => {
|
|
||||||
const group = window.rt._elevGroups[id];
|
|
||||||
if (group) window.rt._elevLayer.removeLayer(group);
|
|
||||||
delete window.rt._elevGroups[id];
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// points: [{ lat, lon, label }]
|
// points: [{ lat, lon, label }]
|
||||||
// options: { radius, opacity, fillOpacity, showLabels, labelEvery }
|
// options: { radius, opacity, fillOpacity, showLabels, labelEvery }
|
||||||
window.rt.drawElevPoints = function (id, points, options) {
|
window.rt.drawElevPoints = function (id, points, options) {
|
||||||
|
|||||||
Reference in New Issue
Block a user