- 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 bool _popupInspectorVisible = false;
private double _inspectorLat, _inspectorLon; private double _inspectorLat, _inspectorLon;
private double? _inspectorElev;
private double? _inspectorElevIdee; private double? _inspectorElevIdee;
private IReadOnlyList<LugarGeocodificadoCercano> _inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>(); private IReadOnlyList<LugarGeocodificadoCercano> _inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
private int _versionInspector; private int _versionInspector;

View File

@@ -274,6 +274,7 @@ public partial class PlanificadorRutas : ComponentBase
{ {
SincronizarTextosPenalizacionPie(); SincronizarTextosPenalizacionPie();
await PersistirConfiguracionAsync(); await PersistirConfiguracionAsync();
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
} }
/// <summary> /// <summary>
@@ -1209,12 +1210,16 @@ public partial class PlanificadorRutas : ComponentBase
var parametros = CrearParametrosPenalizacionActuales(); var parametros = CrearParametrosPenalizacionActuales();
if (alternativa.EsSoloAPie) var distanciaCorregidaBase = alternativa.EsSoloAPie
return UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaSoloAPie, parametros); ? UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaSoloAPie, parametros)
: UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieInicioAprox, parametros)
return UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieInicioAprox, parametros)
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieTransbordosAprox, parametros) + UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieTransbordosAprox, parametros)
+ UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieFinAprox, parametros); + UtilidadesTransitoDbus.CalcularDistanciaPieCorregidaMetros(alternativa.DistanciaPieFinAprox, parametros);
var esfuerzoExtra = double.IsFinite(alternativa.EsfuerzoExtraPendienteMetros)
? Math.Max(0, alternativa.EsfuerzoExtraPendienteMetros)
: 0;
return distanciaCorregidaBase + esfuerzoExtra;
} }
private double CalcularCosteAndarEquilibradoSegundos(AlternativaRuta? alternativa) private double CalcularCosteAndarEquilibradoSegundos(AlternativaRuta? alternativa)
@@ -1237,9 +1242,7 @@ public partial class PlanificadorRutas : ComponentBase
if (alternativa is null) if (alternativa is null)
return 0; return 0;
var distanciaAndando = alternativa.EsSoloAPie var distanciaAndando = CalcularDistanciaCorregidaAlternativa(alternativa);
? alternativa.DistanciaSoloAPie
: alternativa.DistanciaPieTotalAprox;
var factor = double.IsFinite(_factorEquilibradoAndarBus) var factor = double.IsFinite(_factorEquilibradoAndarBus)
? Math.Max(0, _factorEquilibradoAndarBus) ? Math.Max(0, _factorEquilibradoAndarBus)
: 12.0; : 12.0;
@@ -1269,7 +1272,7 @@ public partial class PlanificadorRutas : ComponentBase
if (!double.IsFinite(score)) if (!double.IsFinite(score))
return string.Empty; return string.Empty;
return $"Score {FormatearDistanciaTarjeta(score)} (x{_factorEquilibradoAndarBus.ToString("0.##", CultureInfo.InvariantCulture)})"; return $"Score {FormatearDistanciaTarjeta(score)}";
} }
private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync() private async Task RecalcularRutaEquilibradaTrasCambioFactorAsync()

View File

@@ -1204,7 +1204,7 @@ public partial class PlanificadorRutas : ComponentBase
private bool _elevPopupMinimizado = false; private bool _elevPopupMinimizado = false;
private const string _versionApp = "(v-20260720c)"; private const string _versionApp = "(v-20260721a)";
private double? _paradaElevG; private double? _paradaElevG;

View File

@@ -105,7 +105,7 @@ public partial class PlanificadorRutas : ComponentBase
/// <summary> /// <summary>
/// Actualiza las alturas (Z) de origen y destino para el resumen del detalle de ruta. /// 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> /// </summary>
private async Task ActualizarAlturasResumenRutaAsync(CancellationToken ct) private async Task ActualizarAlturasResumenRutaAsync(CancellationToken ct)
{ {
@@ -137,20 +137,6 @@ public partial class PlanificadorRutas : ComponentBase
? gDst ? gDst
: (double?)null; : (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; _altitudOrigenResumenRuta = zOrigen;
_altitudDestinoResumenRuta = zDestino; _altitudDestinoResumenRuta = zDestino;
} }
@@ -306,26 +292,11 @@ public partial class PlanificadorRutas : ComponentBase
private static readonly ConcurrentDictionary<string, double> _elevCache =
new(StringComparer.OrdinalIgnoreCase);
// Ajustes (rendimiento) // Ajustes (rendimiento)
/* private const int ELEV_MAX_PUNTOS_POR_TRAMO = 24; */ /* private const int ELEV_MAX_PUNTOS_POR_TRAMO = 24; */
private int _elevMaxPuntosPorTramo = 999; // configurable private int _elevMaxPuntosPorTramo = 999; // configurable
private const int ELEV_OPEN_TIMEOUT_MS = 1500;
// muestreo por tramo // muestreo por tramo
private const int ELEV_BATCH_MAX_PUNTOS = 90; // por request (prudente) 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) 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); await ObtenerElevacionesIdeePorLoteAsync(missesIdee.Skip(i).Take(ELEV_BATCH_MAX_PUNTOS).ToList(), ct);
} }
// --- OpenElevation: best-effort y con timeout corto --- return ConstruirMuestrasElevacionDesdeCache(line);
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)));
} }
for (int i = 0; i < missesOpen.Count; i += ELEV_BATCH_MAX_PUNTOS) private List<MuestraElevacion> ConstruirMuestrasElevacionDesdeCache(double[,] line)
{ {
ct.ThrowIfCancellationRequested(); var sampled = SampleLine(line, _elevMaxPuntosPorTramo);
await ObtenerElevacionesPorLoteAsync(missesOpen.Skip(i).Take(ELEV_BATCH_MAX_PUNTOS).ToList(), ct);
}
// --- montar resultado (UNA sola vez) ---
var res = new List<MuestraElevacion>(sampled.Count); var res = new List<MuestraElevacion>(sampled.Count);
for (int i = 0; i < sampled.Count; i++) for (int i = 0; i < sampled.Count; i++)
{ {
ct.ThrowIfCancellationRequested();
var (lat, lon) = sampled[i]; 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; var elevIdee = _elevIdeeCache.TryGetValue(ClaveElevacionIdee(lat, lon), out var e2) ? e2 : (double?)null;
res.Add(new MuestraElevacion res.Add(new MuestraElevacion
@@ -415,7 +373,6 @@ public partial class PlanificadorRutas : ComponentBase
Indice = i, Indice = i,
Latitud = lat, Latitud = lat,
Longitud = lon, Longitud = lon,
Elevacion = elevOpen,
ElevacionIdee = elevIdee ElevacionIdee = elevIdee
}); });
} }
@@ -423,6 +380,69 @@ public partial class PlanificadorRutas : ComponentBase
return res; 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> /// <summary>
/// Gestiona tramo bus es basura. /// Gestiona tramo bus es basura.
/// </summary> /// </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> /// <summary>
/// Gestiona geometria completa variante. /// Gestiona geometria completa variante.
/// </summary> /// </summary>
@@ -1271,7 +1233,6 @@ public partial class PlanificadorRutas : ComponentBase
var versionInspector = ++_versionInspector; var versionInspector = ++_versionInspector;
_inspectorLat = lat; _inspectorLat = lat;
_inspectorLon = lon; _inspectorLon = lon;
_inspectorElev = null;
_inspectorElevIdee = null; _inspectorElevIdee = null;
_inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>(); _inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
_popupInspectorVisible = true; _popupInspectorVisible = true;
@@ -1286,29 +1247,9 @@ public partial class PlanificadorRutas : ComponentBase
maxResultados: 3, maxResultados: 3,
cancellationToken: CancellationToken.None); cancellationToken: CancellationToken.None);
double? elevacionOpen = null;
double? elevacionIdee = null; double? elevacionIdee = null;
try 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) // IDEE (cache primero)
// ------------------------- // -------------------------
@@ -1351,7 +1292,6 @@ public partial class PlanificadorRutas : ComponentBase
if (versionInspector != _versionInspector) if (versionInspector != _versionInspector)
return; return;
_inspectorElev = elevacionOpen;
_inspectorElevIdee = elevacionIdee; _inspectorElevIdee = elevacionIdee;
await ActualizarMarcadoresInspectorJsAsync(); await ActualizarMarcadoresInspectorJsAsync();
StateHasChanged(); StateHasChanged();
@@ -1701,6 +1641,7 @@ public partial class PlanificadorRutas : ComponentBase
_opcionesRuta.Clear(); _opcionesRuta.Clear();
_opcionesRuta.AddRange(filtradas); _opcionesRuta.AddRange(filtradas);
await CalcularEsfuerzoExtraPendienteAlternativasAsync(_opcionesRuta, ct);
OrdenarOpcionesRutaSegunCriterio(); OrdenarOpcionesRutaSegunCriterio();
if (!resultado.Ok || _opcionesRuta.Count == 0) if (!resultado.Ok || _opcionesRuta.Count == 0)

View File

@@ -18,6 +18,19 @@ namespace RutasDBUS.Components.Pages;
public partial class PlanificadorRutas : ComponentBase 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> /// <summary>
/// Asegura que elevacion tramo este disponible. /// Asegura que elevacion tramo este disponible.
/// </summary> /// </summary>
@@ -40,20 +53,19 @@ public partial class PlanificadorRutas : ComponentBase
{ {
var ctNone = CancellationToken.None; 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); var samples = await ConstruirMuestrasElevacionAsync(tramo.Linea, ctNone);
tramo.MuestrasElevacion = samples; tramo.MuestrasElevacion = samples;
// 2) Resumen O solo con lo que ya haya llegado, sin bloquear más la UI var muestrasIdee = samples
var muestrasOpen = samples .Where(x => x.ElevacionIdee is not null)
.Where(x => x.Elevacion is not null) .Select(x => x.ElevacionIdee!.Value)
.Select(x => x.Elevacion!.Value)
.ToList(); .ToList();
tramo.ElevInicio = samples.FirstOrDefault()?.Elevacion; tramo.ElevInicio = samples.FirstOrDefault()?.ElevacionIdee;
tramo.ElevFin = samples.LastOrDefault()?.Elevacion; tramo.ElevFin = samples.LastOrDefault()?.ElevacionIdee;
tramo.ElevMin = muestrasOpen.Count > 0 ? muestrasOpen.Min() : null; tramo.ElevMin = muestrasIdee.Count > 0 ? muestrasIdee.Min() : null;
tramo.ElevMax = muestrasOpen.Count > 0 ? muestrasOpen.Max() : null; tramo.ElevMax = muestrasIdee.Count > 0 ? muestrasIdee.Max() : null;
} }
catch catch
{ {
@@ -95,12 +107,54 @@ public partial class PlanificadorRutas : ComponentBase
await PersistirConfiguracionAsync(); await PersistirConfiguracionAsync();
if (_mostrarElevacionesRuta) if (esSubida)
{
await RecalcularRankingTrasCambioEsfuerzoPendienteAsync();
}
else if (_mostrarElevacionesRuta)
await RepintarRutaSeleccionadaPorElevacionesAsync("Aplicando pendientes..."); await RepintarRutaSeleccionadaPorElevacionesAsync("Aplicando pendientes...");
else else
StateHasChanged(); 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) private async Task CambiarUmbralPisosPendienteRutaAsync(ChangeEventArgs e)
{ {
_umbralPisosPendienteRuta = ParseUmbralPisosPendienteRuta( _umbralPisosPendienteRuta = ParseUmbralPisosPendienteRuta(
@@ -153,7 +207,7 @@ public partial class PlanificadorRutas : ComponentBase
private static double NormalizarUmbralPendienteRuta(double valor) private static double NormalizarUmbralPendienteRuta(double valor)
=> double.IsFinite(valor) => double.IsFinite(valor)
? Math.Clamp(Math.Abs(valor), 0.0, 50.0) ? Math.Clamp(Math.Abs(valor), 0.0, 100.0)
: UmbralPendienteSubidaRutaPorDefecto; : UmbralPendienteSubidaRutaPorDefecto;
private static double ParseUmbralPisosPendienteRuta(object? valor, double actual) private static double ParseUmbralPisosPendienteRuta(object? valor, double actual)
@@ -274,39 +328,68 @@ public partial class PlanificadorRutas : ComponentBase
private (double DistanciaMetros, double AlturaMetros) private (double DistanciaMetros, double AlturaMetros)
CalcularTotalesPendientePositivaSignificativa(IReadOnlyList<MuestraElevacion>? muestras) 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) if (muestras is null || muestras.Count < 2)
return (0.0, 0.0); return default;
var distanciaTotal = 0.0; 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); var umbralSubida = NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta);
for (var i = 1; i < muestras.Count; i++) for (var i = 1; i < muestras.Count; i++)
{ {
var anterior = muestras[i - 1]; var anterior = muestras[i - 1];
var actual = muestras[i]; var actual = muestras[i];
if (anterior.ElevacionIdee is null || actual.ElevacionIdee is null)
continue;
var distancia = CalcularDistanciaMetros( var distancia = CalcularDistanciaMetros(
anterior.Latitud, anterior.Latitud,
anterior.Longitud, anterior.Longitud,
actual.Latitud, actual.Latitud,
actual.Longitud); actual.Longitud);
if (distancia <= 0) if (!double.IsFinite(distancia) || distancia <= 0)
continue;
distanciaTotal += distancia;
if (anterior.ElevacionIdee is null || actual.ElevacionIdee is null)
continue; continue;
var diferenciaAltura = actual.ElevacionIdee.Value - anterior.ElevacionIdee.Value; var diferenciaAltura = actual.ElevacionIdee.Value - anterior.ElevacionIdee.Value;
var pendiente = diferenciaAltura / distancia * 100.0; var pendiente = diferenciaAltura / distancia * 100.0;
if (pendiente <= umbralSubida) var esfuerzo = CalcularEsfuerzoElevacion(distancia, pendiente);
if (esfuerzo is null)
continue; continue;
distanciaTotal += distancia; esfuerzoTotal += esfuerzo.Value;
alturaTotal += Math.Max(0, diferenciaAltura); 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) private static string FormatearDistanciaResumenElevacion(double distanciaMetros)
@@ -327,7 +410,7 @@ public partial class PlanificadorRutas : ComponentBase
} }
private static double? ElevacionPreferidaMapa(MuestraElevacion muestra) private static double? ElevacionPreferidaMapa(MuestraElevacion muestra)
=> muestra.ElevacionIdee ?? muestra.Elevacion; => muestra.ElevacionIdee;
private (string Categoria, string Color) ClasificarPendienteMapa(double? pendientePorcentaje) private (string Categoria, string Color) ClasificarPendienteMapa(double? pendientePorcentaje)
{ {

View File

@@ -346,13 +346,6 @@
<div class="popup-parada-body"> <div class="popup-parada-body">
<div><b>Latitud:</b> @_inspectorLat.ToString("0.000000")</div> <div><b>Latitud:</b> @_inspectorLat.ToString("0.000000")</div>
<div><b>Longitud:</b> @_inspectorLon.ToString("0.000000")</div> <div><b>Longitud:</b> @_inspectorLon.ToString("0.000000")</div>
<div>
<b>Altitud (O):</b>
@(_inspectorElev is null
? "-"
: _inspectorElev.Value.ToString("0.00", CultureInfo.InvariantCulture) + " m")
</div>
<div> <div>
<b>Altitud (G):</b> <b>Altitud (G):</b>
@(_inspectorElevIdee is null @(_inspectorElevIdee is null
@@ -404,7 +397,11 @@
<div class="popup-parada-header popup-elev-header draggable-popup-handle"> <div class="popup-parada-header popup-elev-header draggable-popup-handle">
<span class="popup-elev-title"> <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> </span>
<div class="popup-elev-actions"> <div class="popup-elev-actions">
@@ -438,11 +435,6 @@
} }
else else
{ {
<div class="muted" style="font-size:0.72rem; margin:0 0 .35rem;">
<b>Umbral de subida (Esf.):</b>
@NormalizarUmbralPendienteRuta(_umbralPendienteSubidaRuta).ToString("0.#", CultureInfo.InvariantCulture) %
</div>
<table class="popup-elev-table" style=" <table class="popup-elev-table" style="
width:100%; width:100%;
font-size:0.8rem; font-size:0.8rem;
@@ -542,13 +534,6 @@
{ {
var s = _tramoElevDetalle.MuestrasElevacion ?? new List<MuestraElevacion>(); 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? GIni = s.Count > 0 ? s[0].ElevacionIdee : null;
double? GFin = s.Count > 0 ? s[^1].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(); 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; } 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"; string F(double? v) => v is null ? "-" : v.Value.ToString("0.00", CultureInfo.InvariantCulture) + " m";
var resumenEsfuerzo = CalcularResumenEsfuerzoElevacion(s);
// --- 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;
}
}
}
<div class="muted" style="font-size:0.78rem; margin-top:.5rem;"> <div class="muted" style="font-size:0.78rem; margin-top:.5rem;">
<div><b>O:</b> (inicio → fin) = @F(OIni) @F(OFin) · (mínimo/máximo) = @F(OMin) / @F(OMax)</div> <div><b>G:</b> Ini/Fin: @F(GIni) / @F(GFin) · Min/Max: @F(GMin) / @F(GMax)</div>
<div><b>G:</b> (inicio → fin) = @F(GIni) → @F(GFin) · (mínimo/máximo) = @F(GMin) / @F(GMax)</div>
<div style="margin-top:.25rem;"> <div style="margin-top:.25rem;">
<b>Suma de distancias entre puntos:</b> @FormatearDistanciaResumenElevacion(totalDist) <b>Suma de distancias entre puntos:</b> @FormatearDistanciaResumenElevacion(resumenEsfuerzo.DistanciaTotalMetros)
</div> </div>
<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>
<div> <div>
<b>Suma de alturas con pendiente positiva:</b> @(totalAlturaPendPositiva > 0 <b>Suma de alturas con pendiente positiva:</b> @(resumenEsfuerzo.AlturaPendientePositivaMetros > 0
? totalAlturaPendPositiva.ToString("0.00", CultureInfo.InvariantCulture) + " m (" + (totalAlturaPendPositiva / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture) + " pisos)" ? resumenEsfuerzo.AlturaPendientePositivaMetros.ToString("0.00", CultureInfo.InvariantCulture) + " m (" + (resumenEsfuerzo.AlturaPendientePositivaMetros / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture) + " pisos)"
: "-") : "-")
</div> </div>
<div> <div>
<b>Suma de esfuerzos positivos:</b> @(hayEsfuerzoPositivo ? totalEsfuerzoPositivo.ToString("0.00", CultureInfo.InvariantCulture) : "-") <b>Suma de esfuerzos positivos:</b> @(resumenEsfuerzo.HayEsfuerzoPositivo ? resumenEsfuerzo.EsfuerzoPositivo.ToString("0.00", CultureInfo.InvariantCulture) : "-")
<span style="margin-left:.5rem;"><b>Total:</b> @(hayEsfuerzo ? totalEsfuerzo.ToString("0.00", CultureInfo.InvariantCulture) : "-")</span> <span style="margin-left:.5rem;"><b>Total:</b> @(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>
</div> </div>
} }
@@ -815,11 +766,11 @@
@if (MostrarAvisoPendientesPositivasSignificativas) @if (MostrarAvisoPendientesPositivasSignificativas)
{ {
<div class="route-slope-warning" role="status"> <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 style="margin-top:.2rem; padding-left:.55rem;">
<div>- Distancia con pendiente: @FormatearDistanciaResumenElevacion(_distanciaPendientePositivaSignificativaRutaMetros)</div> <div>- Distancia con subidas: @FormatearDistanciaResumenElevacion(_distanciaPendientePositivaSignificativaRutaMetros)</div>
<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) (@((_alturaPendientePositivaSignificativaRutaMetros / MetrosPorPiso).ToString("0.0", CultureInfo.InvariantCulture)) pisos)
</div> </div>
</div> </div>

View File

@@ -21,6 +21,9 @@ public sealed class AlternativaRuta
public double DistanciaPieTotalAprox => public double DistanciaPieTotalAprox =>
DistanciaPieInicioAprox + DistanciaPieTransbordosAprox + DistanciaPieFinAprox; DistanciaPieInicioAprox + DistanciaPieTransbordosAprox + DistanciaPieFinAprox;
public List<double[,]> LineasPieCalculoElevacion { get; set; } = new();
public double EsfuerzoExtraPendienteMetros { get; set; }
public double TiempoPieInicio { get; set; } public double TiempoPieInicio { get; set; }
public double TiempoPieFin { get; set; } public double TiempoPieFin { get; set; }
public double TiempoEsperaInicialSegundos { get; set; } public double TiempoEsperaInicialSegundos { get; set; }

View File

@@ -5,6 +5,5 @@ public sealed class MuestraElevacion
public int Indice { get; set; } public int Indice { get; set; }
public double Latitud { get; set; } public double Latitud { get; set; }
public double Longitud { get; set; } public double Longitud { get; set; }
public double? Elevacion { get; set; }
public double? ElevacionIdee { get; set; } public double? ElevacionIdee { get; set; }
} }

View File

@@ -239,12 +239,12 @@ public sealed class ServicioHistorialRutasCompartido : IHistorialRutasCompartido
private static IEnumerable<string> EnumerarCarpetasCandidatas(IWebHostEnvironment env) private static IEnumerable<string> EnumerarCarpetasCandidatas(IWebHostEnvironment env)
{ {
yield return Path.Combine(env.ContentRootPath, "Data");
var commonAppData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); var commonAppData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData);
if (!string.IsNullOrWhiteSpace(commonAppData)) if (!string.IsNullOrWhiteSpace(commonAppData))
yield return Path.Combine(commonAppData, "RutasDBUS"); yield return Path.Combine(commonAppData, "RutasDBUS");
yield return Path.Combine(env.ContentRootPath, "Data");
var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
if (!string.IsNullOrWhiteSpace(localAppData)) if (!string.IsNullOrWhiteSpace(localAppData))
yield return Path.Combine(localAppData, "RutasDBUS"); yield return Path.Combine(localAppData, "RutasDBUS");

View File

@@ -271,8 +271,8 @@ public sealed class ServicioArchivoImportacionDbusMonitorizado : IServicioArchiv
var candidatas = new[] var candidatas = new[]
{ {
Path.Combine(_entorno.ContentRootPath, "App_Data"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "RutasDBUS"), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "RutasDBUS"),
Path.Combine(_entorno.ContentRootPath, "App_Data"),
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "RutasDBUS"), Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "RutasDBUS"),
Path.Combine(Path.GetTempPath(), "RutasDBUS") Path.Combine(Path.GetTempPath(), "RutasDBUS")
}; };

View File

@@ -816,7 +816,10 @@ public sealed class BuscadorRutasTemporalesDbus
ParametrosPlanificadorRuta parametros) ParametrosPlanificadorRuta parametros)
{ {
var distanciaBus = Math.Max(0, resultado.RutaBus.DistanciaTotalBus); 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) var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus)
? Math.Max(0, parametros.FactorEquilibradoAndarBus) ? Math.Max(0, parametros.FactorEquilibradoAndarBus)
: 12.0; : 12.0;

View File

@@ -1516,7 +1516,11 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
if (distanciaBus <= 0 && resultado.RutaBus.Tramos.Count > 0) if (distanciaBus <= 0 && resultado.RutaBus.Tramos.Count > 0)
distanciaBus = CalcularDistanciaBusCandidata(resultado.RutaBus.Tramos); 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) var factor = parametros is not null && double.IsFinite(parametros.FactorEquilibradoAndarBus)
? Math.Max(0, parametros.FactorEquilibradoAndarBus) ? Math.Max(0, parametros.FactorEquilibradoAndarBus)
: 12.0; : 12.0;
@@ -2028,6 +2032,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
Etiqueta = "Solo a pie (v1)", Etiqueta = "Solo a pie (v1)",
ProveedorRuta = "v1", ProveedorRuta = "v1",
LineaSoloAPie = osrm.linea, LineaSoloAPie = osrm.linea,
LineasPieCalculoElevacion = new List<double[,]> { osrm.linea },
DistanciaSoloAPie = osrm.distancia, DistanciaSoloAPie = osrm.distancia,
DuracionSoloAPie = duracion, DuracionSoloAPie = duracion,
DuracionTotalAproxSegundos = duracion, DuracionTotalAproxSegundos = duracion,
@@ -2046,6 +2051,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
Etiqueta = "Solo a pie (v2)", Etiqueta = "Solo a pie (v2)",
ProveedorRuta = "v2", ProveedorRuta = "v2",
LineaSoloAPie = valhalla.linea, LineaSoloAPie = valhalla.linea,
LineasPieCalculoElevacion = new List<double[,]> { valhalla.linea },
DistanciaSoloAPie = valhalla.distancia, DistanciaSoloAPie = valhalla.distancia,
DuracionSoloAPie = duracion, DuracionSoloAPie = duracion,
DuracionTotalAproxSegundos = duracion, DuracionTotalAproxSegundos = duracion,
@@ -2056,6 +2062,35 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
return alternativas; 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> /// <summary>
/// Recalcula una alternativa de bus usando caminatas exactas y horarios reales. /// Recalcula una alternativa de bus usando caminatas exactas y horarios reales.
/// </summary> /// </summary>
@@ -2069,6 +2104,9 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
if (alternativa.AlternativaBus is null || alternativa.AlternativaBus.Tramos.Count == 0) if (alternativa.AlternativaBus is null || alternativa.AlternativaBus.Tramos.Count == 0)
return double.PositiveInfinity; return double.PositiveInfinity;
alternativa.LineasPieCalculoElevacion.Clear();
alternativa.EsfuerzoExtraPendienteMetros = 0;
var tramosBus = alternativa.AlternativaBus.Tramos; var tramosBus = alternativa.AlternativaBus.Tramos;
var instanteSalida = UtilidadesTransitoDbus.ResolverInstanteSalida(parametros); var instanteSalida = UtilidadesTransitoDbus.ResolverInstanteSalida(parametros);
@@ -2107,6 +2145,13 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
var cursor = instanteSalida; var cursor = instanteSalida;
var acceso = await WalkOsrmCached(origenLatLon[0], origenLatLon[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud); 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 alternativa.DistanciaPieInicioAprox = acceso.distancia > 0
? acceso.distancia ? acceso.distancia
: UtilidadesTransitoDbus.CalcularDistanciaMetros(origenLatLon[0], origenLatLon[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud); : UtilidadesTransitoDbus.CalcularDistanciaMetros(origenLatLon[0], origenLatLon[1], paradaInicioBus.Latitud, paradaInicioBus.Longitud);
@@ -2161,6 +2206,14 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
puntoInicioTramo![0], puntoInicioTramo![0],
puntoInicioTramo[1]); puntoInicioTramo[1]);
AgregarLineaPieCalculoElevacion(
alternativa,
transferencia.linea,
puntoUltimaLlegada[0],
puntoUltimaLlegada[1],
puntoInicioTramo[0],
puntoInicioTramo[1]);
var distanciaTransferencia = transferencia.distancia > 0 var distanciaTransferencia = transferencia.distancia > 0
? transferencia.distancia ? transferencia.distancia
: distanciaUnion; : distanciaUnion;
@@ -2282,6 +2335,13 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
alternativa.DestinoParada = paradaFinBus; alternativa.DestinoParada = paradaFinBus;
var salidaFinal = await WalkOsrmCached(puntoUltimaLlegada[0], puntoUltimaLlegada[1], destinoLatLon[0], destinoLatLon[1]); 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 alternativa.DistanciaPieFinAprox = salidaFinal.distancia > 0
? salidaFinal.distancia ? salidaFinal.distancia
: UtilidadesTransitoDbus.CalcularDistanciaMetros(puntoUltimaLlegada[0], puntoUltimaLlegada[1], destinoLatLon[0], destinoLatLon[1]); : UtilidadesTransitoDbus.CalcularDistanciaMetros(puntoUltimaLlegada[0], puntoUltimaLlegada[1], destinoLatLon[0], destinoLatLon[1]);
@@ -3070,7 +3130,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
return double.PositiveInfinity; return double.PositiveInfinity;
var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus); var distanciaBus = Math.Max(0, alternativa.AlternativaBus.DistanciaTotalBus);
var distanciaAndando = Math.Max(0, alternativa.DistanciaPieTotalAprox); var distanciaAndando = CalcularDistanciaAndandoCorregidaMetros(alternativa, parametros);
var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus) var factor = double.IsFinite(parametros.FactorEquilibradoAndarBus)
? Math.Max(0, parametros.FactorEquilibradoAndarBus) ? Math.Max(0, parametros.FactorEquilibradoAndarBus)
: 12.0; : 12.0;

View File

@@ -873,12 +873,21 @@ html, body {
} }
.route-visual-options { .route-visual-options {
display: flex; position: relative;
display: inline-flex;
align-self: flex-start;
align-items: center; align-items: center;
justify-content: flex-start; justify-content: flex-start;
flex-wrap: nowrap; flex-wrap: nowrap;
gap: .35rem; width: fit-content;
max-width: 100%;
box-sizing: border-box;
gap: .25rem;
margin: -.15rem 0 .4rem; 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 { .route-elevation-switch {
@@ -892,6 +901,7 @@ html, body {
cursor: pointer; cursor: pointer;
pointer-events: auto; pointer-events: auto;
user-select: none; user-select: none;
white-space: nowrap;
} }
.route-elevation-switch input { .route-elevation-switch input {
@@ -943,10 +953,11 @@ html, body {
} }
.route-elevation-settings { .route-elevation-settings {
position: relative;
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
margin-left: auto; margin-left: .05rem;
padding-left: .3rem;
border-left: 1px solid rgba(148, 163, 184, .35);
pointer-events: auto; pointer-events: auto;
} }
@@ -982,8 +993,8 @@ html, body {
position: absolute; position: absolute;
z-index: 20; z-index: 20;
top: calc(100% + 6px); top: calc(100% + 6px);
right: 0; right: auto;
left: auto; left: 0;
width: 238px; width: 238px;
max-width: calc(100vw - 24px); max-width: calc(100vw - 24px);
box-sizing: border-box; box-sizing: border-box;
@@ -1541,6 +1552,114 @@ html, body {
padding: 1rem 1rem .9rem; 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 { .custom-modal-title {
font-size: 1rem; font-size: 1rem;
font-weight: 700; font-weight: 700;
@@ -2279,11 +2398,29 @@ html, body {
.popup-elev-title { .popup-elev-title {
flex: 1 1 auto; flex: 1 1 auto;
min-width: 0; min-width: 0;
display: flex;
align-items: baseline;
gap: .45rem;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; 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 { .popup-elev-actions {
display: flex; display: flex;
flex: 0 0 auto; flex: 0 0 auto;
@@ -2389,7 +2526,7 @@ html, body {
} }
.popup-elev-topright .popup-elev-title { .popup-elev-topright .popup-elev-title {
max-width: 150px; max-width: none;
} }
.popup-elev-topright .popup-parada-body { .popup-elev-topright .popup-parada-body {
@@ -2415,6 +2552,12 @@ html, body {
.popup-elev-table thead th:first-child { .popup-elev-table thead th:first-child {
z-index: 3; z-index: 3;
} }
.popup-elev-g-summary {
white-space: nowrap;
font-size: .74rem;
}
/* ===== SLIDER (range) limpio para el panel ===== */ /* ===== SLIDER (range) limpio para el panel ===== */
.config-range { .config-range {
width: 100%; width: 100%;
@@ -2989,9 +3132,10 @@ html, body {
.route-visual-options { .route-visual-options {
justify-content: flex-start; justify-content: flex-start;
flex-wrap: wrap; flex-wrap: nowrap;
gap: 6px 10px; gap: 4px;
margin: 0 0 8px; margin: 0 0 8px;
padding: 2px 3px 2px 6px;
} }
.route-elevation-switch { .route-elevation-switch {
@@ -3005,8 +3149,8 @@ html, body {
} }
.route-elevation-settings-popover { .route-elevation-settings-popover {
right: 0; right: auto;
left: auto; left: 0;
width: 250px; width: 250px;
max-width: calc(100vw - 20px); max-width: calc(100vw - 20px);
} }

View File

@@ -1150,6 +1150,11 @@ window.rt.ensureInspectorLayer = function () {
pane.style.zIndex = 790; pane.style.zIndex = 790;
} }
const inspectorTooltipPane = map.getPane("rtInspectorTooltipPane")
|| map.createPane("rtInspectorTooltipPane");
inspectorTooltipPane.style.zIndex = 10000;
inspectorTooltipPane.style.pointerEvents = "none";
if (!window.rt._inspectorLayer) { if (!window.rt._inspectorLayer) {
window.rt._inspectorLayer = window.L.layerGroup().addTo(map); window.rt._inspectorLayer = window.L.layerGroup().addTo(map);
} }
@@ -1177,7 +1182,9 @@ window.rt.setInspectorMarkers = function (lat, lon, places) {
bubblingMouseEvents: false bubblingMouseEvents: false
}); });
puntoZ.bindTooltip("Punto Z", { puntoZ.bindTooltip("Punto Z", {
pane: "rtInspectorTooltipPane",
direction: "top", direction: "top",
offset: [0, -16],
opacity: 0.95, opacity: 0.95,
className: "rt-inspector-tooltip" className: "rt-inspector-tooltip"
}); });
@@ -1247,8 +1254,9 @@ window.rt.setInspectorMarkers = function (lat, lon, places) {
const tooltip = document.createElement("span"); const tooltip = document.createElement("span");
tooltip.textContent = texto; tooltip.textContent = texto;
marker.bindTooltip(tooltip, { marker.bindTooltip(tooltip, {
pane: "rtInspectorTooltipPane",
direction: "top", direction: "top",
offset: [offsetX, -12], offset: [offsetX, -24],
opacity: 0.95, opacity: 0.95,
className: "rt-inspector-tooltip" className: "rt-inspector-tooltip"
}); });