- Integrado el esfuerzo extra por pendiente en la distancia corregida a pie.
- Aplicada la distancia corregida al cálculo de d.Equilib. A, score y ordenación de alternativas. - Conservadas las distancias reales en las tarjetas y descripciones de las rutas. - Añadido el recálculo del ranking al modificar el umbral o los coeficientes de pendientes. - Simplificado el cálculo de elevaciones para utilizar únicamente la fuente G (IDEE). - Añadida la suma de esfuerzo extra al detalle de puntos de elevación. - Reorganizada la cabecera del modal de elevación y compactado su resumen. - Eliminado el factor x12 de la representación visual del score. - Mejorado el aviso de pendientes positivas significativas. - Corregidos los tooltips del punto Z para mostrarse por encima de los marcadores. - Agrupados el interruptor de visualización de pendientes y su botón de configuración. - Personalizadas las barras de desplazamiento de modales y paneles. - Priorizado C:\ProgramData\RutasDBUS para compartir historial, modelos externos y modelo activo entre distintas publicaciones IIS.
This commit is contained in:
@@ -255,7 +255,6 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
|
||||
|
||||
private bool _popupInspectorVisible = false;
|
||||
private double _inspectorLat, _inspectorLon;
|
||||
private double? _inspectorElev;
|
||||
private double? _inspectorElevIdee;
|
||||
private IReadOnlyList<LugarGeocodificadoCercano> _inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
|
||||
private int _versionInspector;
|
||||
|
||||
@@ -274,6 +274,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
{
|
||||
SincronizarTextosPenalizacionPie();
|
||||
await PersistirConfiguracionAsync();
|
||||
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1209,12 +1210,16 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
var parametros = CrearParametrosPenalizacionActuales();
|
||||
|
||||
if (alternativa.EsSoloAPie)
|
||||
return UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaSoloAPie, parametros);
|
||||
var distanciaCorregidaBase = alternativa.EsSoloAPie
|
||||
? UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaSoloAPie, parametros)
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieInicioAprox, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieTransbordosAprox, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieFinAprox, parametros);
|
||||
var esfuerzoExtra = double.IsFinite(alternativa.EsfuerzoExtraPendienteMetros)
|
||||
? Math.Max(0, alternativa.EsfuerzoExtraPendienteMetros)
|
||||
: 0;
|
||||
|
||||
return UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieInicioAprox, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieTransbordosAprox, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieFinAprox, parametros);
|
||||
return distanciaCorregidaBase + esfuerzoExtra;
|
||||
}
|
||||
|
||||
private double CalcularCosteAndarEquilibradoSegundos(AlternativaRuta? alternativa)
|
||||
@@ -1237,9 +1242,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
if (alternativa is null)
|
||||
return 0;
|
||||
|
||||
var distanciaAndando = alternativa.EsSoloAPie
|
||||
? alternativa.DistanciaSoloAPie
|
||||
: alternativa.DistanciaPieTotalAprox;
|
||||
var distanciaAndando = CalcularDistanciaCorregidaAlternativa(alternativa);
|
||||
var factor = double.IsFinite(_factorEquilibradoAndarBus)
|
||||
? Math.Max(0, _factorEquilibradoAndarBus)
|
||||
: 12.0;
|
||||
@@ -1269,7 +1272,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
if (!double.IsFinite(score))
|
||||
return string.Empty;
|
||||
|
||||
return $"Score {FormatearDistanciaTarjeta(score)} (x{_factorEquilibradoAndarBus.ToString("0.##", CultureInfo.InvariantCulture)})";
|
||||
return $"Score {FormatearDistanciaTarjeta(score)}";
|
||||
}
|
||||
|
||||
private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync()
|
||||
|
||||
@@ -1204,7 +1204,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
private bool _elevPopupMinimizado = false;
|
||||
|
||||
|
||||
private const string _versionApp = "(v-20260720c)";
|
||||
private const string _versionApp = "(v-20260721a)";
|
||||
|
||||
|
||||
private double? _paradaElevG;
|
||||
|
||||
@@ -105,7 +105,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
/// <summary>
|
||||
/// Actualiza las alturas (Z) de origen y destino para el resumen del detalle de ruta.
|
||||
/// Prioriza IDEE y usa OpenElevation como respaldo.
|
||||
/// Usa la fuente G (IDEE).
|
||||
/// </summary>
|
||||
private async Task ActualizarAlturasResumenRutaAsync(CancellationToken ct)
|
||||
{
|
||||
@@ -137,20 +137,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
? gDst
|
||||
: (double?)null;
|
||||
|
||||
var faltanOpen = new List<(double lat, double lon)>(2);
|
||||
if (zOrigen is null && !_elevCache.ContainsKey(ElevKey(origen.Item1, origen.Item2)))
|
||||
faltanOpen.Add(origen);
|
||||
if (zDestino is null && !_elevCache.ContainsKey(ElevKey(destino.Item1, destino.Item2)))
|
||||
faltanOpen.Add(destino);
|
||||
|
||||
if (faltanOpen.Count > 0)
|
||||
await ObtenerElevacionesPorLoteAsync(faltanOpen, CancellationToken.None);
|
||||
|
||||
if (zOrigen is null && _elevCache.TryGetValue(ElevKey(origen.Item1, origen.Item2), out var oOri))
|
||||
zOrigen = oOri;
|
||||
if (zDestino is null && _elevCache.TryGetValue(ElevKey(destino.Item1, destino.Item2), out var oDst))
|
||||
zDestino = oDst;
|
||||
|
||||
_altitudOrigenResumenRuta = zOrigen;
|
||||
_altitudDestinoResumenRuta = zDestino;
|
||||
}
|
||||
@@ -306,26 +292,11 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
|
||||
|
||||
private static readonly ConcurrentDictionary<string, double> _elevCache =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Ajustes (rendimiento)
|
||||
/* private const int ELEV_MAX_PUNTOS_POR_TRAMO = 24; */
|
||||
private int _elevMaxPuntosPorTramo = 999; // configurable
|
||||
private const int ELEV_OPEN_TIMEOUT_MS = 1500;
|
||||
// muestreo por tramo
|
||||
private const int ELEV_BATCH_MAX_PUNTOS = 90; // por request (prudente)
|
||||
private const int ELEV_DECIMALES_CACHE = 6; // redondeo coord para cache
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona clave.
|
||||
/// </summary>
|
||||
private static string ElevKey(double lat, double lon)
|
||||
{
|
||||
double rLat = Math.Round(lat, ELEV_DECIMALES_CACHE);
|
||||
double rLon = Math.Round(lon, ELEV_DECIMALES_CACHE);
|
||||
return $"{rLat.ToString("0.000000", CultureInfo.InvariantCulture)},{rLon.ToString("0.000000", CultureInfo.InvariantCulture)}";
|
||||
}
|
||||
|
||||
private static List<(double lat, double lon)> SampleLine(double[,] line, int maxPoints)
|
||||
{
|
||||
@@ -385,29 +356,16 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
await ObtenerElevacionesIdeePorLoteAsync(missesIdee.Skip(i).Take(ELEV_BATCH_MAX_PUNTOS).ToList(), ct);
|
||||
}
|
||||
|
||||
// --- OpenElevation: best-effort y con timeout corto ---
|
||||
var missesOpen = new List<(double lat, double lon)>();
|
||||
foreach (var (lat, lon) in sampled)
|
||||
{
|
||||
var k = ElevKey(lat, lon);
|
||||
if (!_elevCache.ContainsKey(k))
|
||||
missesOpen.Add((Math.Round(lat, ELEV_DECIMALES_CACHE), Math.Round(lon, ELEV_DECIMALES_CACHE)));
|
||||
}
|
||||
return ConstruirMuestrasElevacionDesdeCache(line);
|
||||
}
|
||||
|
||||
for (int i = 0; i < missesOpen.Count; i += ELEV_BATCH_MAX_PUNTOS)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
await ObtenerElevacionesPorLoteAsync(missesOpen.Skip(i).Take(ELEV_BATCH_MAX_PUNTOS).ToList(), ct);
|
||||
}
|
||||
|
||||
// --- montar resultado (UNA sola vez) ---
|
||||
private List<MuestraElevacion> ConstruirMuestrasElevacionDesdeCache(double[,] line)
|
||||
{
|
||||
var sampled = SampleLine(line, _elevMaxPuntosPorTramo);
|
||||
var res = new List<MuestraElevacion>(sampled.Count);
|
||||
for (int i = 0; i < sampled.Count; i++)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var (lat, lon) = sampled[i];
|
||||
|
||||
var elevOpen = _elevCache.TryGetValue(ElevKey(lat, lon), out var e1) ? e1 : (double?)null;
|
||||
var elevIdee = _elevIdeeCache.TryGetValue(ClaveElevacionIdee(lat, lon), out var e2) ? e2 : (double?)null;
|
||||
|
||||
res.Add(new MuestraElevacion
|
||||
@@ -415,7 +373,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
Indice = i,
|
||||
Latitud = lat,
|
||||
Longitud = lon,
|
||||
Elevacion = elevOpen,
|
||||
ElevacionIdee = elevIdee
|
||||
});
|
||||
}
|
||||
@@ -423,6 +380,69 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
return res;
|
||||
}
|
||||
|
||||
private async Task CalcularEsfuerzoExtraPendienteAlternativasAsync(
|
||||
IReadOnlyList<AlternativaRuta> alternativas,
|
||||
CancellationToken ct)
|
||||
{
|
||||
var lineasPorAlternativa = alternativas
|
||||
.Select(alternativa => new
|
||||
{
|
||||
Alternativa = alternativa,
|
||||
Lineas = (alternativa.EsSoloAPie
|
||||
? alternativa.LineaSoloAPie is { } lineaSolo
|
||||
? new[] { lineaSolo }
|
||||
: Enumerable.Empty<double[,]>()
|
||||
: alternativa.LineasPieCalculoElevacion)
|
||||
.Where(linea => linea.GetLength(0) >= 2)
|
||||
.ToList()
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var puntosPendientes = new Dictionary<string, (double lat, double lon)>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var item in lineasPorAlternativa)
|
||||
{
|
||||
foreach (var linea in item.Lineas)
|
||||
{
|
||||
foreach (var (lat, lon) in SampleLine(linea, _elevMaxPuntosPorTramo))
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var clave = ClaveElevacionIdee(lat, lon);
|
||||
if (!_elevIdeeCache.ContainsKey(clave))
|
||||
{
|
||||
puntosPendientes.TryAdd(
|
||||
clave,
|
||||
(Math.Round(lat, ELEV_IDEE_DECIMALES_CACHE), Math.Round(lon, ELEV_IDEE_DECIMALES_CACHE)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var puntos = puntosPendientes.Values.ToList();
|
||||
for (var i = 0; i < puntos.Count; i += ELEV_BATCH_MAX_PUNTOS)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
await ObtenerElevacionesIdeePorLoteAsync(
|
||||
puntos.Skip(i).Take(ELEV_BATCH_MAX_PUNTOS).ToList(),
|
||||
ct);
|
||||
}
|
||||
|
||||
foreach (var item in lineasPorAlternativa)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var esfuerzoExtra = 0.0;
|
||||
foreach (var linea in item.Lineas)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
var muestras = ConstruirMuestrasElevacionDesdeCache(linea);
|
||||
esfuerzoExtra += CalcularResumenEsfuerzoElevacion(muestras).EsfuerzoExtraMetros;
|
||||
}
|
||||
|
||||
item.Alternativa.EsfuerzoExtraPendienteMetros = double.IsFinite(esfuerzoExtra)
|
||||
? Math.Max(0, esfuerzoExtra)
|
||||
: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona tramo bus es basura.
|
||||
/// </summary>
|
||||
@@ -539,64 +559,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
|
||||
|
||||
private sealed class RespuestaOpenElevation
|
||||
{
|
||||
public List<ResultadoOpenElevation>? results { get; set; }
|
||||
}
|
||||
private sealed class ResultadoOpenElevation
|
||||
{
|
||||
public double latitude { get; set; }
|
||||
public double longitude { get; set; }
|
||||
public double elevation { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recupera elevations el lote.
|
||||
/// </summary>
|
||||
private async Task ObtenerElevacionesPorLoteAsync(List<(double lat, double lon)> points, CancellationToken ct)
|
||||
{
|
||||
if (points.Count == 0) return;
|
||||
|
||||
// locations=lat,lon|lat,lon|...
|
||||
var parts = new List<string>(points.Count);
|
||||
foreach (var (lat, lon) in points)
|
||||
{
|
||||
ct.ThrowIfCancellationRequested();
|
||||
parts.Add($"{lat.ToString(CultureInfo.InvariantCulture)},{lon.ToString(CultureInfo.InvariantCulture)}");
|
||||
}
|
||||
|
||||
string url = "https://openelevation.tecnosis.net/api/v1/lookup?locations=" + string.Join("|", parts);
|
||||
|
||||
try
|
||||
{
|
||||
using var req = new HttpRequestMessage(HttpMethod.Get, url);
|
||||
using var ctsTimeout = CancellationTokenSource.CreateLinkedTokenSource(ct);
|
||||
ctsTimeout.CancelAfter(ELEV_OPEN_TIMEOUT_MS);
|
||||
|
||||
using var resp = await Http.SendAsync(req, ctsTimeout.Token);
|
||||
if (!resp.IsSuccessStatusCode) return;
|
||||
|
||||
var json = await resp.Content.ReadAsStringAsync(ctsTimeout.Token);
|
||||
|
||||
var data = JsonSerializer.Deserialize<RespuestaOpenElevation>(json, new JsonSerializerOptions
|
||||
{
|
||||
PropertyNameCaseInsensitive = true
|
||||
});
|
||||
|
||||
if (data?.results is null || data.results.Count == 0) return;
|
||||
|
||||
foreach (var r in data.results)
|
||||
{
|
||||
var k = ElevKey(r.latitude, r.longitude);
|
||||
_elevCache[k] = r.elevation;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// silencioso: elevación es informativa
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gestiona geometria completa variante.
|
||||
/// </summary>
|
||||
@@ -1271,7 +1233,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
var versionInspector = ++_versionInspector;
|
||||
_inspectorLat = lat;
|
||||
_inspectorLon = lon;
|
||||
_inspectorElev = null;
|
||||
_inspectorElevIdee = null;
|
||||
_inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
|
||||
_popupInspectorVisible = true;
|
||||
@@ -1286,29 +1247,9 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
maxResultados: 3,
|
||||
cancellationToken: CancellationToken.None);
|
||||
|
||||
double? elevacionOpen = null;
|
||||
double? elevacionIdee = null;
|
||||
try
|
||||
{
|
||||
// -------------------------
|
||||
// OpenElevation (cache primero)
|
||||
// -------------------------
|
||||
var kOpen = ElevKey(lat, lon);
|
||||
if (_elevCache.TryGetValue(kOpen, out var cachedOpen))
|
||||
{
|
||||
elevacionOpen = cachedOpen;
|
||||
}
|
||||
else
|
||||
{
|
||||
await ObtenerElevacionesPorLoteAsync(
|
||||
new List<(double lat, double lon)> { (lat, lon) },
|
||||
CancellationToken.None);
|
||||
|
||||
elevacionOpen = _elevCache.TryGetValue(ElevKey(lat, lon), out var elevOpen)
|
||||
? elevOpen
|
||||
: (double?)null;
|
||||
}
|
||||
|
||||
// -------------------------
|
||||
// IDEE (cache primero)
|
||||
// -------------------------
|
||||
@@ -1351,7 +1292,6 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
if (versionInspector != _versionInspector)
|
||||
return;
|
||||
|
||||
_inspectorElev = elevacionOpen;
|
||||
_inspectorElevIdee = elevacionIdee;
|
||||
await ActualizarMarcadoresInspectorJsAsync();
|
||||
StateHasChanged();
|
||||
@@ -1701,6 +1641,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
_opcionesRuta.Clear();
|
||||
_opcionesRuta.AddRange(filtradas);
|
||||
await CalcularEsfuerzoExtraPendienteAlternativasAsync(_opcionesRuta, ct);
|
||||
OrdenarOpcionesRutaSegunCriterio();
|
||||
|
||||
if (!resultado.Ok || _opcionesRuta.Count == 0)
|
||||
|
||||
@@ -18,6 +18,19 @@ namespace RutasDBUS.Components.Pages;
|
||||
|
||||
public partial class PlanificadorRutas : ComponentBase
|
||||
{
|
||||
private readonly record struct ResumenEsfuerzoElevacion(
|
||||
double DistanciaTotalMetros,
|
||||
double DistanciaPendientePositivaMetros,
|
||||
double AlturaPendientePositivaMetros,
|
||||
double EsfuerzoPositivo,
|
||||
double EsfuerzoTotal,
|
||||
bool HayEsfuerzoPositivo,
|
||||
bool HayEsfuerzo)
|
||||
{
|
||||
public double EsfuerzoExtraMetros =>
|
||||
Math.Max(0, EsfuerzoPositivo - DistanciaPendientePositivaMetros);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asegura que elevacion tramo este disponible.
|
||||
/// </summary>
|
||||
@@ -40,20 +53,19 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
{
|
||||
var ctNone = CancellationToken.None;
|
||||
|
||||
// 1) Samples bajo demanda, priorizando G y dejando O como best-effort
|
||||
// Samples bajo demanda usando la fuente G.
|
||||
var samples = await ConstruirMuestrasElevacionAsync(tramo.Linea, ctNone);
|
||||
tramo.MuestrasElevacion = samples;
|
||||
|
||||
// 2) Resumen O solo con lo que ya haya llegado, sin bloquear más la UI
|
||||
var muestrasOpen = samples
|
||||
.Where(x => x.Elevacion is not null)
|
||||
.Select(x => x.Elevacion!.Value)
|
||||
var muestrasIdee = samples
|
||||
.Where(x => x.ElevacionIdee is not null)
|
||||
.Select(x => x.ElevacionIdee!.Value)
|
||||
.ToList();
|
||||
|
||||
tramo.ElevInicio = samples.FirstOrDefault()?.Elevacion;
|
||||
tramo.ElevFin = samples.LastOrDefault()?.Elevacion;
|
||||
tramo.ElevMin = muestrasOpen.Count > 0 ? muestrasOpen.Min() : null;
|
||||
tramo.ElevMax = muestrasOpen.Count > 0 ? muestrasOpen.Max() : null;
|
||||
tramo.ElevInicio = samples.FirstOrDefault()?.ElevacionIdee;
|
||||
tramo.ElevFin = samples.LastOrDefault()?.ElevacionIdee;
|
||||
tramo.ElevMin = muestrasIdee.Count > 0 ? muestrasIdee.Min() : null;
|
||||
tramo.ElevMax = muestrasIdee.Count > 0 ? muestrasIdee.Max() : null;
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -95,12 +107,54 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
await PersistirConfiguracionAsync();
|
||||
|
||||
if (_mostrarElevacionesRuta)
|
||||
if (esSubida)
|
||||
{
|
||||
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
|
||||
}
|
||||
else if (_mostrarElevacionesRuta)
|
||||
await RepintarRutaSeleccionadaPorElevacionesAsync("Aplicando pendientes...");
|
||||
else
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task RecalcularRankingTrasCambioEsfuerzoPendienteAsync()
|
||||
{
|
||||
if (_opcionesRuta.Count == 0)
|
||||
{
|
||||
StateHasChanged();
|
||||
return;
|
||||
}
|
||||
|
||||
var opcionSeleccionadaAnterior = _indiceAlternativaSeleccionada >= 0 &&
|
||||
_indiceAlternativaSeleccionada < _opcionesRuta.Count
|
||||
? _opcionesRuta[_indiceAlternativaSeleccionada]
|
||||
: null;
|
||||
|
||||
await CalcularEsfuerzoExtraPendienteAlternativasAsync(_opcionesRuta, CancellationToken.None);
|
||||
OrdenarOpcionesRutaSegunCriterio();
|
||||
|
||||
var nuevoIndice = CriterioPrioritarioTiempoActivo && opcionSeleccionadaAnterior is not null
|
||||
? _opcionesRuta.IndexOf(opcionSeleccionadaAnterior)
|
||||
: BuscarIndicePreferidoPorCriterio(_criterioPrioritarioRuta);
|
||||
if (nuevoIndice < 0 || nuevoIndice >= _opcionesRuta.Count)
|
||||
nuevoIndice = 0;
|
||||
|
||||
var cambioSeleccion = _indiceAlternativaSeleccionada != nuevoIndice ||
|
||||
!ReferenceEquals(opcionSeleccionadaAnterior, _opcionesRuta[nuevoIndice]);
|
||||
_indiceAlternativaSeleccionada = nuevoIndice;
|
||||
|
||||
if (_mostrarElevacionesRuta || cambioSeleccion)
|
||||
{
|
||||
await RepintarRutaSeleccionadaPorElevacionesAsync(_mostrarElevacionesRuta
|
||||
? "Aplicando pendientes..."
|
||||
: "Representando ruta seleccionada...");
|
||||
}
|
||||
else
|
||||
{
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CambiarUmbralPisosPendienteRutaAsync(ChangeEventArgs e)
|
||||
{
|
||||
_umbralPisosPendienteRuta = ParseUmbralPisosPendienteRuta(
|
||||
@@ -153,7 +207,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
private static double NormalizarUmbralPendienteRuta(double valor)
|
||||
=> double.IsFinite(valor)
|
||||
? Math.Clamp(Math.Abs(valor), 0.0, 50.0)
|
||||
? Math.Clamp(Math.Abs(valor), 0.0, 100.0)
|
||||
: UmbralPendienteSubidaRutaPorDefecto;
|
||||
|
||||
private static double ParseUmbralPisosPendienteRuta(object? valor, double actual)
|
||||
@@ -274,39 +328,68 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
|
||||
private (double DistanciaMetros, double AlturaMetros)
|
||||
CalcularTotalesPendientePositivaSignificativa(IReadOnlyList<MuestraElevacion>? muestras)
|
||||
{
|
||||
var resumen = CalcularResumenEsfuerzoElevacion(muestras);
|
||||
return (resumen.DistanciaPendientePositivaMetros, resumen.AlturaPendientePositivaMetros);
|
||||
}
|
||||
|
||||
private ResumenEsfuerzoElevacion CalcularResumenEsfuerzoElevacion(
|
||||
IReadOnlyList<MuestraElevacion>? muestras)
|
||||
{
|
||||
if (muestras is null || muestras.Count < 2)
|
||||
return (0.0, 0.0);
|
||||
return default;
|
||||
|
||||
var distanciaTotal = 0.0;
|
||||
var alturaTotal = 0.0;
|
||||
var distanciaPendientePositiva = 0.0;
|
||||
var alturaPendientePositiva = 0.0;
|
||||
var esfuerzoPositivo = 0.0;
|
||||
var esfuerzoTotal = 0.0;
|
||||
var hayEsfuerzoPositivo = false;
|
||||
var hayEsfuerzo = false;
|
||||
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)
|
||||
if (!double.IsFinite(distancia) || distancia <= 0)
|
||||
continue;
|
||||
|
||||
distanciaTotal += distancia;
|
||||
if (anterior.ElevacionIdee is null || actual.ElevacionIdee is null)
|
||||
continue;
|
||||
|
||||
var diferenciaAltura = actual.ElevacionIdee.Value - anterior.ElevacionIdee.Value;
|
||||
var pendiente = diferenciaAltura / distancia * 100.0;
|
||||
if (pendiente <= umbralSubida)
|
||||
var esfuerzo = CalcularEsfuerzoElevacion(distancia, pendiente);
|
||||
if (esfuerzo is null)
|
||||
continue;
|
||||
|
||||
distanciaTotal += distancia;
|
||||
alturaTotal += Math.Max(0, diferenciaAltura);
|
||||
esfuerzoTotal += esfuerzo.Value;
|
||||
hayEsfuerzo = true;
|
||||
|
||||
if (pendiente < umbralSubida)
|
||||
continue;
|
||||
|
||||
distanciaPendientePositiva += distancia;
|
||||
alturaPendientePositiva += Math.Max(0, diferenciaAltura);
|
||||
esfuerzoPositivo += esfuerzo.Value;
|
||||
hayEsfuerzoPositivo = true;
|
||||
}
|
||||
|
||||
return (distanciaTotal, alturaTotal);
|
||||
return new ResumenEsfuerzoElevacion(
|
||||
distanciaTotal,
|
||||
distanciaPendientePositiva,
|
||||
alturaPendientePositiva,
|
||||
esfuerzoPositivo,
|
||||
esfuerzoTotal,
|
||||
hayEsfuerzoPositivo,
|
||||
hayEsfuerzo);
|
||||
}
|
||||
|
||||
private static string FormatearDistanciaResumenElevacion(double distanciaMetros)
|
||||
@@ -327,7 +410,7 @@ public partial class PlanificadorRutas : ComponentBase
|
||||
}
|
||||
|
||||
private static double? ElevacionPreferidaMapa(MuestraElevacion muestra)
|
||||
=> muestra.ElevacionIdee ?? muestra.Elevacion;
|
||||
=> muestra.ElevacionIdee;
|
||||
|
||||
private (string Categoria, string Color) ClasificarPendienteMapa(double? pendientePorcentaje)
|
||||
{
|
||||
|
||||
@@ -346,13 +346,6 @@
|
||||
<div class="popup-parada-body">
|
||||
<div><b>Latitud:</b> @_inspectorLat.ToString("0.000000")</div>
|
||||
<div><b>Longitud:</b> @_inspectorLon.ToString("0.000000")</div>
|
||||
<div>
|
||||
<b>Altitud (O):</b>
|
||||
@(_inspectorElev is null
|
||||
? "-"
|
||||
: _inspectorElev.Value.ToString("0.00", CultureInfo.InvariantCulture) + " m")
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<b>Altitud (G):</b>
|
||||
@(_inspectorElevIdee is null
|
||||
@@ -404,7 +397,11 @@
|
||||
|
||||
<div class="popup-parada-header popup-elev-header draggable-popup-handle">
|
||||
<span class="popup-elev-title">
|
||||
Puntos de elevación · @_tramoElevDetalle.Tipo
|
||||
<span class="popup-elev-title-main">Puntos de elevación</span>
|
||||
<span class="popup-elev-title-threshold">
|
||||
Umbral de subida (Esf.):
|
||||
@NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta).ToString("0.#", CultureInfo.InvariantCulture) %
|
||||
</span>
|
||||
</span>
|
||||
|
||||
<div class="popup-elev-actions">
|
||||
@@ -438,11 +435,6 @@
|
||||
}
|
||||
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="
|
||||
width:100%;
|
||||
font-size:0.8rem;
|
||||
@@ -542,13 +534,6 @@
|
||||
{
|
||||
var s = _tramoElevDetalle.MuestrasElevacion ?? new List<MuestraElevacion>();
|
||||
|
||||
// --- resumen altitudes ---
|
||||
double? OIni = s.Count > 0 ? s[0].Elevacion : null;
|
||||
double? OFin = s.Count > 0 ? s[^1].Elevacion : null;
|
||||
double? OMin = s.Where(x => x.Elevacion is not null).Select(x => x.Elevacion!.Value).DefaultIfEmpty().Min();
|
||||
double? OMax = s.Where(x => x.Elevacion is not null).Select(x => x.Elevacion!.Value).DefaultIfEmpty().Max();
|
||||
if (!s.Any(x => x.Elevacion is not null)) { OMin = null; OMax = null; }
|
||||
|
||||
double? GIni = s.Count > 0 ? s[0].ElevacionIdee : null;
|
||||
double? GFin = s.Count > 0 ? s[^1].ElevacionIdee : null;
|
||||
double? GMin = s.Where(x => x.ElevacionIdee is not null).Select(x => x.ElevacionIdee!.Value).DefaultIfEmpty().Min();
|
||||
@@ -556,61 +541,27 @@
|
||||
if (!s.Any(x => x.ElevacionIdee is not null)) { GMin = null; GMax = null; }
|
||||
|
||||
string F(double? v) => v is null ? "-" : v.Value.ToString("0.00", CultureInfo.InvariantCulture) + " m";
|
||||
|
||||
// --- suma distancias entre puntos ---
|
||||
double totalDist = 0;
|
||||
double totalEsfuerzoPositivo = 0;
|
||||
double totalEsfuerzo = 0;
|
||||
var totalesPendientePositiva = CalcularTotalesPendientePositivaSignificativa(s);
|
||||
double totalDistPendPositiva = totalesPendientePositiva.DistanciaMetros;
|
||||
double totalAlturaPendPositiva = totalesPendientePositiva.AlturaMetros;
|
||||
var hayEsfuerzoPositivo = false;
|
||||
var hayEsfuerzo = false;
|
||||
var umbralPendientePositiva = NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta);
|
||||
for (int i = 1; i < s.Count; i++)
|
||||
{
|
||||
var anterior = s[i - 1];
|
||||
var actual = s[i];
|
||||
var distancia = CalcularDistanciaMetros(anterior.Latitud, anterior.Longitud, actual.Latitud, actual.Longitud);
|
||||
totalDist += distancia;
|
||||
|
||||
double? pendiente = null;
|
||||
if (distancia > 0 && anterior.ElevacionIdee is not null && actual.ElevacionIdee is not null)
|
||||
{
|
||||
pendiente = (actual.ElevacionIdee.Value - anterior.ElevacionIdee.Value) / distancia * 100.0;
|
||||
}
|
||||
|
||||
var esfuerzo = CalcularEsfuerzoElevacion(distancia, pendiente);
|
||||
if (esfuerzo is not null)
|
||||
{
|
||||
totalEsfuerzo += esfuerzo.Value;
|
||||
hayEsfuerzo = true;
|
||||
|
||||
if (pendiente > umbralPendientePositiva)
|
||||
{
|
||||
totalEsfuerzoPositivo += esfuerzo.Value;
|
||||
hayEsfuerzoPositivo = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
var resumenEsfuerzo = CalcularResumenEsfuerzoElevacion(s);
|
||||
|
||||
<div class="muted" style="font-size:0.78rem; margin-top:.5rem;">
|
||||
<div><b>O:</b> (inicio → fin) = @F(OIni) → @F(OFin) · (mínimo/máximo) = @F(OMin) / @F(OMax)</div>
|
||||
<div><b>G:</b> (inicio → fin) = @F(GIni) → @F(GFin) · (mínimo/máximo) = @F(GMin) / @F(GMax)</div>
|
||||
<div><b>G:</b> Ini/Fin: @F(GIni) / @F(GFin) · Min/Max: @F(GMin) / @F(GMax)</div>
|
||||
<div style="margin-top:.25rem;">
|
||||
<b>Suma de distancias entre puntos:</b> @FormatearDistanciaResumenElevacion(totalDist)
|
||||
<b>Suma de distancias entre puntos:</b> @FormatearDistanciaResumenElevacion(resumenEsfuerzo.DistanciaTotalMetros)
|
||||
</div>
|
||||
<div>
|
||||
<b>Suma de distancias con pendiente positiva:</b> @FormatearDistanciaResumenElevacion(totalDistPendPositiva)
|
||||
<b>Suma de distancias con pendiente positiva:</b> @FormatearDistanciaResumenElevacion(resumenEsfuerzo.DistanciaPendientePositivaMetros)
|
||||
</div>
|
||||
<div>
|
||||
<b>Suma de alturas con pendiente positiva:</b> @(totalAlturaPendPositiva > 0
|
||||
? totalAlturaPendPositiva.ToString("0.00", CultureInfo.InvariantCulture) + " m (" + (totalAlturaPendPositiva / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture) + " pisos)"
|
||||
<b>Suma de alturas con pendiente positiva:</b> @(resumenEsfuerzo.AlturaPendientePositivaMetros > 0
|
||||
? resumenEsfuerzo.AlturaPendientePositivaMetros.ToString("0.00", CultureInfo.InvariantCulture) + " m (" + (resumenEsfuerzo.AlturaPendientePositivaMetros / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture) + " pisos)"
|
||||
: "-")
|
||||
</div>
|
||||
<div>
|
||||
<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>
|
||||
<b>Suma de esfuerzos positivos:</b> @(resumenEsfuerzo.HayEsfuerzoPositivo ? resumenEsfuerzo.EsfuerzoPositivo.ToString("0.00", CultureInfo.InvariantCulture) : "-")
|
||||
<span style="margin-left:.5rem;"><b>Total:</b> @(resumenEsfuerzo.HayEsfuerzo ? resumenEsfuerzo.EsfuerzoTotal.ToString("0.00", CultureInfo.InvariantCulture) : "-")</span>
|
||||
</div>
|
||||
<div>
|
||||
<b>Suma de esfuerzo extra:</b> @(resumenEsfuerzo.HayEsfuerzo ? resumenEsfuerzo.EsfuerzoExtraMetros.ToString("0.00", CultureInfo.InvariantCulture) : "-")
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -815,11 +766,11 @@
|
||||
@if (MostrarAvisoPendientesPositivasSignificativas)
|
||||
{
|
||||
<div class="route-slope-warning" role="status">
|
||||
<div>Solución con pendientes positivas significativas.</div>
|
||||
<div>Pendientes positivas significativas.</div>
|
||||
<div style="margin-top:.2rem; padding-left:.55rem;">
|
||||
<div>- Distancia con pendiente: @FormatearDistanciaResumenElevacion(_distanciaPendientePositivaSignificativaRutaMetros)</div>
|
||||
<div>- Distancia con subidas: @FormatearDistanciaResumenElevacion(_distanciaPendientePositivaSignificativaRutaMetros)</div>
|
||||
<div>
|
||||
- Altura subida: @_alturaPendientePositivaSignificativaRutaMetros.ToString("0.00", CultureInfo.InvariantCulture) m
|
||||
- Subidas acumuladas: @_alturaPendientePositivaSignificativaRutaMetros.ToString("0.00", CultureInfo.InvariantCulture) m
|
||||
(@((_alturaPendientePositivaSignificativaRutaMetros / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture)) pisos)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,6 +21,9 @@ public sealed class AlternativaRuta
|
||||
public double DistanciaPieTotalAprox =>
|
||||
DistanciaPieInicioAprox + DistanciaPieTransbordosAprox + DistanciaPieFinAprox;
|
||||
|
||||
public List<double[,]> LineasPieCalculoElevacion { get; set; } = new();
|
||||
public double EsfuerzoExtraPendienteMetros { get; set; }
|
||||
|
||||
public double TiempoPieInicio { get; set; }
|
||||
public double TiempoPieFin { get; set; }
|
||||
public double TiempoEsperaInicialSegundos { get; set; }
|
||||
|
||||
@@ -5,6 +5,5 @@ public sealed class MuestraElevacion
|
||||
public int Indice { get; set; }
|
||||
public double Latitud { get; set; }
|
||||
public double Longitud { get; set; }
|
||||
public double? Elevacion { get; set; }
|
||||
public double? ElevacionIdee { get; set; }
|
||||
}
|
||||
|
||||
@@ -239,12 +239,12 @@ public sealed class ServicioHistorialRutasCompartido : IHistorialRutasCompartido
|
||||
|
||||
private static IEnumerable<string> EnumerarCarpetasCandidatas(IWebHostEnvironment env)
|
||||
{
|
||||
yield return Path.Combine(env.ContentRootPath, "Data");
|
||||
|
||||
var commonAppData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
|
||||
if (!string.IsNullOrWhiteSpace(commonAppData))
|
||||
yield return Path.Combine(commonAppData, "RutasDBUS");
|
||||
|
||||
yield return Path.Combine(env.ContentRootPath, "Data");
|
||||
|
||||
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
|
||||
if (!string.IsNullOrWhiteSpace(localAppData))
|
||||
yield return Path.Combine(localAppData, "RutasDBUS");
|
||||
|
||||
@@ -271,8 +271,8 @@ public sealed class ServicioArchivoImportacionDbusMonitorizado : IServicioArchiv
|
||||
|
||||
var candidatas = new[]
|
||||
{
|
||||
Path.Combine(_entorno.ContentRootPath, "App_Data"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "RutasDBUS"),
|
||||
Path.Combine(_entorno.ContentRootPath, "App_Data"),
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "RutasDBUS"),
|
||||
Path.Combine(Path.GetTempPath(), "RutasDBUS")
|
||||
};
|
||||
|
||||
@@ -816,7 +816,10 @@ public sealed class BuscadorRutasTemporalesDbus
|
||||
ParametrosPlanificadorRuta parametros)
|
||||
{
|
||||
var distanciaBus = Math.Max(0, resultado.RutaBus.DistanciaTotalBus);
|
||||
var distanciaAndando = Math.Max(0, resultado.DistanciaPieTotal);
|
||||
var distanciaAndando =
|
||||
UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(resultado.DistanciaPieInicio, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(resultado.DistanciaPieTransbordos, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(resultado.DistanciaPieFin, parametros);
|
||||
var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
||||
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
||||
: 12.0;
|
||||
|
||||
@@ -1516,7 +1516,11 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
if (distanciaBus <= 0 && resultado.RutaBus.Tramos.Count > 0)
|
||||
distanciaBus = CalcularDistanciaBusCandidata(resultado.RutaBus.Tramos);
|
||||
|
||||
var distanciaAndando = Math.Max(0, resultado.DistanciaPieTotal);
|
||||
var distanciaAndando = parametros is null
|
||||
? Math.Max(0, resultado.DistanciaPieTotal)
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(resultado.DistanciaPieInicio, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(resultado.DistanciaPieTransbordos, parametros)
|
||||
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(resultado.DistanciaPieFin, parametros);
|
||||
var factor = parametros is not null && double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
||||
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
||||
: 12.0;
|
||||
@@ -2028,6 +2032,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
Etiqueta = "Solo a pie (v1)",
|
||||
ProveedorRuta = "v1",
|
||||
LineaSoloAPie = osrm.linea,
|
||||
LineasPieCalculoElevacion = new List<double[,]> { osrm.linea },
|
||||
DistanciaSoloAPie = osrm.distancia,
|
||||
DuracionSoloAPie = duracion,
|
||||
DuracionTotalAproxSegundos = duracion,
|
||||
@@ -2046,6 +2051,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
Etiqueta = "Solo a pie (v2)",
|
||||
ProveedorRuta = "v2",
|
||||
LineaSoloAPie = valhalla.linea,
|
||||
LineasPieCalculoElevacion = new List<double[,]> { valhalla.linea },
|
||||
DistanciaSoloAPie = valhalla.distancia,
|
||||
DuracionSoloAPie = duracion,
|
||||
DuracionTotalAproxSegundos = duracion,
|
||||
@@ -2056,6 +2062,35 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
return alternativas;
|
||||
}
|
||||
|
||||
private static void AgregarLineaPieCalculoElevacion(
|
||||
AlternativaRuta alternativa,
|
||||
double[,]? linea,
|
||||
double latitudOrigen,
|
||||
double longitudOrigen,
|
||||
double latitudDestino,
|
||||
double longitudDestino)
|
||||
{
|
||||
if (linea is not null && linea.GetLength(0) >= 2)
|
||||
{
|
||||
alternativa.LineasPieCalculoElevacion.Add(linea);
|
||||
return;
|
||||
}
|
||||
|
||||
var distanciaDirecta = UtilidadesTransitoDbus.CalcularDistanciaMetros(
|
||||
latitudOrigen,
|
||||
longitudOrigen,
|
||||
latitudDestino,
|
||||
longitudDestino);
|
||||
if (!double.IsFinite(distanciaDirecta) || distanciaDirecta <= 0.5)
|
||||
return;
|
||||
|
||||
alternativa.LineasPieCalculoElevacion.Add(new double[,]
|
||||
{
|
||||
{ latitudOrigen, longitudOrigen },
|
||||
{ latitudDestino, longitudDestino }
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Recalcula una alternativa de bus usando caminatas exactas y horarios reales.
|
||||
/// </summary>
|
||||
@@ -2069,6 +2104,9 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
if (alternativa.AlternativaBus is null || alternativa.AlternativaBus.Tramos.Count == 0)
|
||||
return double.PositiveInfinity;
|
||||
|
||||
alternativa.LineasPieCalculoElevacion.Clear();
|
||||
alternativa.EsfuerzoExtraPendienteMetros = 0;
|
||||
|
||||
var tramosBus = alternativa.AlternativaBus.Tramos;
|
||||
var instanteSalida = UtilidadesTransitoDbus.ResolverInstanteSalida(parametros);
|
||||
|
||||
@@ -2107,6 +2145,13 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
var cursor = instanteSalida;
|
||||
|
||||
var acceso = await WalkOsrmCached(origenLatLon[0], origenLatLon[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud);
|
||||
AgregarLineaPieCalculoElevacion(
|
||||
alternativa,
|
||||
acceso.linea,
|
||||
origenLatLon[0],
|
||||
origenLatLon[1],
|
||||
paradaInicioBus.Latitud,
|
||||
paradaInicioBus.Longitud);
|
||||
alternativa.DistanciaPieInicioAprox = acceso.distancia > 0
|
||||
? acceso.distancia
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaMetros(origenLatLon[0], origenLatLon[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud);
|
||||
@@ -2161,6 +2206,14 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
puntoInicioTramo![0],
|
||||
puntoInicioTramo[1]);
|
||||
|
||||
AgregarLineaPieCalculoElevacion(
|
||||
alternativa,
|
||||
transferencia.linea,
|
||||
puntoUltimaLlegada[0],
|
||||
puntoUltimaLlegada[1],
|
||||
puntoInicioTramo[0],
|
||||
puntoInicioTramo[1]);
|
||||
|
||||
var distanciaTransferencia = transferencia.distancia > 0
|
||||
? transferencia.distancia
|
||||
: distanciaUnion;
|
||||
@@ -2282,6 +2335,13 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
alternativa.DestinoParada = paradaFinBus;
|
||||
|
||||
var salidaFinal = await WalkOsrmCached(puntoUltimaLlegada[0], puntoUltimaLlegada[1], destinoLatLon[0], destinoLatLon[1]);
|
||||
AgregarLineaPieCalculoElevacion(
|
||||
alternativa,
|
||||
salidaFinal.linea,
|
||||
puntoUltimaLlegada[0],
|
||||
puntoUltimaLlegada[1],
|
||||
destinoLatLon[0],
|
||||
destinoLatLon[1]);
|
||||
alternativa.DistanciaPieFinAprox = salidaFinal.distancia > 0
|
||||
? salidaFinal.distancia
|
||||
: UtilidadesTransitoDbus.CalcularDistanciaMetros(puntoUltimaLlegada[0], puntoUltimaLlegada[1], destinoLatLon[0], destinoLatLon[1]);
|
||||
@@ -3070,7 +3130,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
|
||||
return double.PositiveInfinity;
|
||||
|
||||
var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
|
||||
var distanciaAndando = Math.Max(0, alternativa.DistanciaPieTotalAprox);
|
||||
var distanciaAndando = CalcularDistanciaAndandoCorregidaMetros(alternativa, parametros);
|
||||
var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus)
|
||||
? Math.Max(0, parametros.FactorEquilibradoAndarBus)
|
||||
: 12.0;
|
||||
|
||||
@@ -873,12 +873,21 @@ html, body {
|
||||
}
|
||||
|
||||
.route-visual-options {
|
||||
display: flex;
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-self: flex-start;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: nowrap;
|
||||
gap: .35rem;
|
||||
width: fit-content;
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
gap: .25rem;
|
||||
margin: -.15rem 0 .4rem;
|
||||
padding: 2px 3px 2px 5px;
|
||||
border: 1px solid rgba(148, 163, 184, .28);
|
||||
border-radius: 7px;
|
||||
background: rgba(2, 6, 23, .3);
|
||||
}
|
||||
|
||||
.route-elevation-switch {
|
||||
@@ -892,6 +901,7 @@ html, body {
|
||||
cursor: pointer;
|
||||
pointer-events: auto;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.route-elevation-switch input {
|
||||
@@ -943,10 +953,11 @@ html, body {
|
||||
}
|
||||
|
||||
.route-elevation-settings {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
margin-left: auto;
|
||||
margin-left: .05rem;
|
||||
padding-left: .3rem;
|
||||
border-left: 1px solid rgba(148, 163, 184, .35);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
@@ -982,8 +993,8 @@ html, body {
|
||||
position: absolute;
|
||||
z-index: 20;
|
||||
top: calc(100% + 6px);
|
||||
right: 0;
|
||||
left: auto;
|
||||
right: auto;
|
||||
left: 0;
|
||||
width: 238px;
|
||||
max-width: calc(100vw - 24px);
|
||||
box-sizing: border-box;
|
||||
@@ -1541,6 +1552,114 @@ html, body {
|
||||
padding: 1rem 1rem .9rem;
|
||||
}
|
||||
|
||||
/* Scrollbars compactas para paneles y modales de la aplicacion. */
|
||||
:where(
|
||||
.route-panel,
|
||||
.route-panel *,
|
||||
.history-panel,
|
||||
.history-panel *,
|
||||
.config-panel,
|
||||
.config-panel *,
|
||||
.custom-modal-card,
|
||||
.custom-modal-card *,
|
||||
.popup-stack-left,
|
||||
.popup-stack-left *,
|
||||
.popup-parada,
|
||||
.popup-parada *
|
||||
) {
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: rgba(100, 116, 139, .78) rgba(2, 6, 23, .18);
|
||||
}
|
||||
|
||||
:where(
|
||||
.route-panel,
|
||||
.route-panel *,
|
||||
.history-panel,
|
||||
.history-panel *,
|
||||
.config-panel,
|
||||
.config-panel *,
|
||||
.custom-modal-card,
|
||||
.custom-modal-card *,
|
||||
.popup-stack-left,
|
||||
.popup-stack-left *,
|
||||
.popup-parada,
|
||||
.popup-parada *
|
||||
)::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
:where(
|
||||
.route-panel,
|
||||
.route-panel *,
|
||||
.history-panel,
|
||||
.history-panel *,
|
||||
.config-panel,
|
||||
.config-panel *,
|
||||
.custom-modal-card,
|
||||
.custom-modal-card *,
|
||||
.popup-stack-left,
|
||||
.popup-stack-left *,
|
||||
.popup-parada,
|
||||
.popup-parada *
|
||||
)::-webkit-scrollbar-track {
|
||||
border-radius: 999px;
|
||||
background: rgba(2, 6, 23, .18);
|
||||
}
|
||||
|
||||
:where(
|
||||
.route-panel,
|
||||
.route-panel *,
|
||||
.history-panel,
|
||||
.history-panel *,
|
||||
.config-panel,
|
||||
.config-panel *,
|
||||
.custom-modal-card,
|
||||
.custom-modal-card *,
|
||||
.popup-stack-left,
|
||||
.popup-stack-left *,
|
||||
.popup-parada,
|
||||
.popup-parada *
|
||||
)::-webkit-scrollbar-thumb {
|
||||
border: 1px solid rgba(15, 23, 42, .88);
|
||||
border-radius: 999px;
|
||||
background: rgba(100, 116, 139, .78);
|
||||
}
|
||||
|
||||
:where(
|
||||
.route-panel,
|
||||
.route-panel *,
|
||||
.history-panel,
|
||||
.history-panel *,
|
||||
.config-panel,
|
||||
.config-panel *,
|
||||
.custom-modal-card,
|
||||
.custom-modal-card *,
|
||||
.popup-stack-left,
|
||||
.popup-stack-left *,
|
||||
.popup-parada,
|
||||
.popup-parada *
|
||||
)::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(56, 189, 248, .78);
|
||||
}
|
||||
|
||||
:where(
|
||||
.route-panel,
|
||||
.route-panel *,
|
||||
.history-panel,
|
||||
.history-panel *,
|
||||
.config-panel,
|
||||
.config-panel *,
|
||||
.custom-modal-card,
|
||||
.custom-modal-card *,
|
||||
.popup-stack-left,
|
||||
.popup-stack-left *,
|
||||
.popup-parada,
|
||||
.popup-parada *
|
||||
)::-webkit-scrollbar-corner {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.custom-modal-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 700;
|
||||
@@ -2279,11 +2398,29 @@ html, body {
|
||||
.popup-elev-title {
|
||||
flex: 1 1 auto;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: .45rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.popup-elev-title-main {
|
||||
flex: 0 0 auto;
|
||||
font-size: .95rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.popup-elev-title-threshold {
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
color: #94a3b8;
|
||||
font-size: .68rem;
|
||||
font-weight: 400;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.popup-elev-actions {
|
||||
display: flex;
|
||||
flex: 0 0 auto;
|
||||
@@ -2389,7 +2526,7 @@ html, body {
|
||||
}
|
||||
|
||||
.popup-elev-topright .popup-elev-title {
|
||||
max-width: 150px;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
.popup-elev-topright .popup-parada-body {
|
||||
@@ -2415,6 +2552,12 @@ html, body {
|
||||
.popup-elev-table thead th:first-child {
|
||||
z-index: 3;
|
||||
}
|
||||
|
||||
.popup-elev-g-summary {
|
||||
white-space: nowrap;
|
||||
font-size: .74rem;
|
||||
}
|
||||
|
||||
/* ===== SLIDER (range) limpio para el panel ===== */
|
||||
.config-range {
|
||||
width: 100%;
|
||||
@@ -2989,9 +3132,10 @@ html, body {
|
||||
|
||||
.route-visual-options {
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 10px;
|
||||
flex-wrap: nowrap;
|
||||
gap: 4px;
|
||||
margin: 0 0 8px;
|
||||
padding: 2px 3px 2px 6px;
|
||||
}
|
||||
|
||||
.route-elevation-switch {
|
||||
@@ -3005,8 +3149,8 @@ html, body {
|
||||
}
|
||||
|
||||
.route-elevation-settings-popover {
|
||||
right: 0;
|
||||
left: auto;
|
||||
right: auto;
|
||||
left: 0;
|
||||
width: 250px;
|
||||
max-width: calc(100vw - 20px);
|
||||
}
|
||||
|
||||
@@ -1150,6 +1150,11 @@ window.rt.ensureInspectorLayer = function () {
|
||||
pane.style.zIndex = 790;
|
||||
}
|
||||
|
||||
const inspectorTooltipPane = map.getPane("rtInspectorTooltipPane")
|
||||
|| map.createPane("rtInspectorTooltipPane");
|
||||
inspectorTooltipPane.style.zIndex = 10000;
|
||||
inspectorTooltipPane.style.pointerEvents = "none";
|
||||
|
||||
if (!window.rt._inspectorLayer) {
|
||||
window.rt._inspectorLayer = window.L.layerGroup().addTo(map);
|
||||
}
|
||||
@@ -1177,7 +1182,9 @@ window.rt.setInspectorMarkers = function (lat, lon, places) {
|
||||
bubblingMouseEvents: false
|
||||
});
|
||||
puntoZ.bindTooltip("Punto Z", {
|
||||
pane: "rtInspectorTooltipPane",
|
||||
direction: "top",
|
||||
offset: [0, -16],
|
||||
opacity: 0.95,
|
||||
className: "rt-inspector-tooltip"
|
||||
});
|
||||
@@ -1247,8 +1254,9 @@ window.rt.setInspectorMarkers = function (lat, lon, places) {
|
||||
const tooltip = document.createElement("span");
|
||||
tooltip.textContent = texto;
|
||||
marker.bindTooltip(tooltip, {
|
||||
pane: "rtInspectorTooltipPane",
|
||||
direction: "top",
|
||||
offset: [offsetX, -12],
|
||||
offset: [offsetX, -24],
|
||||
opacity: 0.95,
|
||||
className: "rt-inspector-tooltip"
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user