From f2e0b59faacaf224bdf37696b293c191a1dc85d2 Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 21 Jul 2026 13:19:14 +0200 Subject: [PATCH] =?UTF-8?q?-=20Integrado=20el=20esfuerzo=20extra=20por=20p?= =?UTF-8?q?endiente=20en=20la=20distancia=20corregida=20a=20pie.=20-=20Apl?= =?UTF-8?q?icada=20la=20distancia=20corregida=20al=20c=C3=A1lculo=20de=20d?= =?UTF-8?q?.Equilib.=20A,=20score=20y=20ordenaci=C3=B3n=20de=20alternativa?= =?UTF-8?q?s.=20-=20Conservadas=20las=20distancias=20reales=20en=20las=20t?= =?UTF-8?q?arjetas=20y=20descripciones=20de=20las=20rutas.=20-=20A=C3=B1ad?= =?UTF-8?q?ido=20el=20rec=C3=A1lculo=20del=20ranking=20al=20modificar=20el?= =?UTF-8?q?=20umbral=20o=20los=20coeficientes=20de=20pendientes.=20-=20Sim?= =?UTF-8?q?plificado=20el=20c=C3=A1lculo=20de=20elevaciones=20para=20utili?= =?UTF-8?q?zar=20=C3=BAnicamente=20la=20fuente=20G=20(IDEE).=20-=20A=C3=B1?= =?UTF-8?q?adida=20la=20suma=20de=20esfuerzo=20extra=20al=20detalle=20de?= =?UTF-8?q?=20puntos=20de=20elevaci=C3=B3n.=20-=20Reorganizada=20la=20cabe?= =?UTF-8?q?cera=20del=20modal=20de=20elevaci=C3=B3n=20y=20compactado=20su?= =?UTF-8?q?=20resumen.=20-=20Eliminado=20el=20factor=20x12=20de=20la=20rep?= =?UTF-8?q?resentaci=C3=B3n=20visual=20del=20score.=20-=20Mejorado=20el=20?= =?UTF-8?q?aviso=20de=20pendientes=20positivas=20significativas.=20-=20Cor?= =?UTF-8?q?regidos=20los=20tooltips=20del=20punto=20Z=20para=20mostrarse?= =?UTF-8?q?=20por=20encima=20de=20los=20marcadores.=20-=20Agrupados=20el?= =?UTF-8?q?=20interruptor=20de=20visualizaci=C3=B3n=20de=20pendientes=20y?= =?UTF-8?q?=20su=20bot=C3=B3n=20de=20configuraci=C3=B3n.=20-=20Personaliza?= =?UTF-8?q?das=20las=20barras=20de=20desplazamiento=20de=20modales=20y=20p?= =?UTF-8?q?aneles.=20-=20Priorizado=20C:\ProgramData\RutasDBUS=20para=20co?= =?UTF-8?q?mpartir=20historial,=20modelos=20externos=20y=20modelo=20activo?= =?UTF-8?q?=20entre=20distintas=20publicaciones=20IIS.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pages/PlanificadorRutas.CicloVidaYMapa.cs | 1 - .../Pages/PlanificadorRutas.Estado.cs | 21 +- .../Pages/PlanificadorRutas.Itinerarios.cs | 2 +- .../Pages/PlanificadorRutas.Planificacion.cs | 203 +++++++----------- .../Pages/PlanificadorRutas.Renderizado.cs | 129 +++++++++-- .../Components/Pages/PlanificadorRutas.razor | 87 ++------ .../Modelos/Planificacion/AlternativaRuta.cs | 3 + .../Modelos/Planificacion/MuestraElevacion.cs | 1 - .../ServicioHistorialRutasCompartido.cs | 4 +- ...vicioArchivoImportacionDbusMonitorizado.cs | 2 +- .../BuscadorRutasTemporalesDbus.cs | 5 +- .../PlanificadorRutasServicio.cs | 64 +++++- RutasDBUS/wwwroot/app.css | 166 +++++++++++++- RutasDBUS/wwwroot/app.js | 10 +- 14 files changed, 446 insertions(+), 252 deletions(-) diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs index 07f37c0..f4130f8 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.CicloVidaYMapa.cs @@ -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 _inspectorLugaresCercanos = Array.Empty(); private int _versionInspector; diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Estado.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Estado.cs index 47abedd..fac233e 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Estado.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Estado.cs @@ -274,6 +274,7 @@ public partial class PlanificadorRutas : ComponentBase { SincronizarTextosPenalizacionPie(); await PersistirConfiguracionAsync(); + await RecalcularRankingTrasCambioEsfuerzoPendienteAsync(); } /// @@ -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() diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs index 1a32ea2..c6b3754 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Itinerarios.cs @@ -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; diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs index 0435fd3..a85c6d9 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Planificacion.cs @@ -105,7 +105,7 @@ public partial class PlanificadorRutas : ComponentBase /// /// 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). /// 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 _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 - - /// - /// Gestiona clave. - /// - 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 ConstruirMuestrasElevacionDesdeCache(double[,] line) + { + var sampled = SampleLine(line, _elevMaxPuntosPorTramo); var res = new List(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 alternativas, + CancellationToken ct) + { + var lineasPorAlternativa = alternativas + .Select(alternativa => new + { + Alternativa = alternativa, + Lineas = (alternativa.EsSoloAPie + ? alternativa.LineaSoloAPie is { } lineaSolo + ? new[] { lineaSolo } + : Enumerable.Empty() + : alternativa.LineasPieCalculoElevacion) + .Where(linea => linea.GetLength(0) >= 2) + .ToList() + }) + .ToList(); + + var puntosPendientes = new Dictionary(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; + } + } /// /// Gestiona tramo bus es basura. @@ -539,64 +559,6 @@ public partial class PlanificadorRutas : ComponentBase - private sealed class RespuestaOpenElevation - { - public List? results { get; set; } - } - private sealed class ResultadoOpenElevation - { - public double latitude { get; set; } - public double longitude { get; set; } - public double elevation { get; set; } - } - - /// - /// Recupera elevations el lote. - /// - 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(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(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 - } - } - /// /// Gestiona geometria completa variante. /// @@ -1271,7 +1233,6 @@ public partial class PlanificadorRutas : ComponentBase var versionInspector = ++_versionInspector; _inspectorLat = lat; _inspectorLon = lon; - _inspectorElev = null; _inspectorElevIdee = null; _inspectorLugaresCercanos = Array.Empty(); _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) diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs b/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs index e44a8de..c51949d 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.Renderizado.cs @@ -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); + } + /// /// Asegura que elevacion tramo este disponible. /// @@ -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? muestras) + { + var resumen = CalcularResumenEsfuerzoElevacion(muestras); + return (resumen.DistanciaPendientePositivaMetros, resumen.AlturaPendientePositivaMetros); + } + + private ResumenEsfuerzoElevacion CalcularResumenEsfuerzoElevacion( + IReadOnlyList? 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) { diff --git a/RutasDBUS/Components/Pages/PlanificadorRutas.razor b/RutasDBUS/Components/Pages/PlanificadorRutas.razor index 6f92d82..7816e4a 100644 --- a/RutasDBUS/Components/Pages/PlanificadorRutas.razor +++ b/RutasDBUS/Components/Pages/PlanificadorRutas.razor @@ -346,13 +346,6 @@