- 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:
2026-07-21 13:19:14 +02:00
parent 7b52dd2c73
commit f2e0b59faa
14 changed files with 446 additions and 252 deletions

View File

@@ -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;

View File

@@ -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()

View File

@@ -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;

View File

@@ -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,43 +356,92 @@ 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
{
Indice = i,
Latitud = lat,
Longitud = lon,
Elevacion = elevOpen,
ElevacionIdee = elevIdee
});
}
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.
@@ -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)

View File

@@ -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)
@@ -211,7 +265,7 @@ public partial class PlanificadorRutas : ComponentBase
{
if (!_mostrarElevacionesRuta || linea.GetLength(0) < 2)
return (null, 0.0, 0.0);
var muestras = await ConstruirMuestrasElevacionAsync(linea, ct);
if (muestras.Count < 2)
return (null, 0.0, 0.0);
@@ -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)
{

View File

@@ -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>