- Separado el control de poligonos excluyentes

- Añadido interpreacion de tipologia de suelo en segmentos andando
- Persistencia modo andando
- Transbordo penalizado a 6
- Añadidos varias zonas de prohibicion de poligonos
This commit is contained in:
2026-09-08 13:33:15 +02:00
parent d13ff13da1
commit 235297fd3a
25 changed files with 1288 additions and 375 deletions

View File

@@ -506,13 +506,16 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
private async Task<(double[,]? line, double dist, double dur)> WalkValhallaCached(double lat1, double lon1, double lat2, double lon2)
{
var poligonos = ObtenerPoligonosExclusionCaminata();
var key = WalkKey("valhalla", lat1, lon1, lat2, lon2);
if (poligonos is not null) key += "|" + JsonSerializer.Serialize(poligonos);
if (_walkCache.TryGetValue(key, out var v))
return v;
try
{
var (line, dist, dur) = await ClienteValhalla.ObtenerRutaAsync(lat1, lon1, lat2, lon2, costing: "pedestrian");
var (line, dist, dur) = await ClienteValhalla.ObtenerRutaAsync(lat1, lon1, lat2, lon2,
costing: "pedestrian", excludePolygons: poligonos);
// ✅ Solo cachear si es un resultado "bueno"
if (line is not null && dist > 0)

View File

@@ -68,12 +68,12 @@ public partial class PlanificadorRutas : ComponentBase
private double _factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
private double _parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
private double _coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
private double _pesoTransbordoRankingMinutos = 4.0;
private double _pesoTransbordoRankingMinutos = 6.0;
private double _factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
private string _factorPenalizacionDistanciaPieTextoEdicion = "1.8";
private string _parametroPenalizacionDistanciaPieTextoEdicion = "200";
private string _coeficienteEsfuerzoAlturaTextoEdicion = "5.20";
private string _pesoTransbordoRankingTextoEdicion = "4,0";
private string _pesoTransbordoRankingTextoEdicion = "6,0";
private string _factorEquilibradoAndarBusTextoEdicion = "6";
private double _velocidadBusKmH = 16.0;
private int _minutosTransbordo = 5;

View File

@@ -1522,6 +1522,8 @@ public partial class PlanificadorRutas
_coordenadasOrigen = new[] { item.OrigenLatitud, item.OrigenLongitud };
_coordenadasDestino = new[] { item.DestinoLatitud, item.DestinoLongitud };
if (_modoConfiguracionHistorial == ModoConfiguracionHistorial.Predeterminada)
_configuracionFabrica = await ValoresFabrica.CargarAsync();
var configuracionHistorial = _modoConfiguracionHistorial switch
{
ModoConfiguracionHistorial.Guardada => CrearConfiguracionGuardadaHistorial(item),

View File

@@ -1400,7 +1400,7 @@ public partial class PlanificadorRutas : ComponentBase
private bool _elevPopupMinimizado = false;
private const string _versionApp = "(v-20260907a)";
private const string _versionApp = "(v-20260908a)";
private double? _paradaElevIdee;

View File

@@ -23,6 +23,8 @@ public partial class PlanificadorRutas
/// </summary>
private async Task CargarConfiguracionLocalAsync()
{
_configuracionFabrica = await ValoresFabrica.CargarAsync();
AplicarConfiguracionLocal(CrearConfiguracionPredeterminadaHistorial());
try
{
var json = await JS.InvokeAsync<string?>("rt.loadLocalValue", ClaveConfiguracionLocal);
@@ -146,7 +148,7 @@ public partial class PlanificadorRutas
PreferenciaEtiquetaRuta = _preferenciaEtiquetaRuta
};
private static ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaHistorial()
private static ConfiguracionHistorialRuta CrearConfiguracionBaseHistorial()
{
var ahora = DateTime.Now;
return new ConfiguracionHistorialRuta
@@ -174,7 +176,7 @@ public partial class PlanificadorRutas
ParametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto,
CoeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto,
VersionCoeficienteEsfuerzo = VersionPersistenciaCoeficienteEsfuerzo,
PesoTransbordoRankingMinutos = 4.0,
PesoTransbordoRankingMinutos = 6.0,
FactorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto,
VersionFactorEquilibrado = VersionPersistenciaFactorEquilibrado,
MostrarElevacionesRuta = true,
@@ -267,7 +269,7 @@ public partial class PlanificadorRutas
? CoeficienteEsfuerzoAlturaPorDefecto
: coeficienteAltura;
if (config.PesoTransbordoRankingMinutos is { } pesoTransbordo && double.IsFinite(pesoTransbordo))
_pesoTransbordoRankingMinutos = Math.Abs(pesoTransbordo - 8.0) < 0.0001 ? 4.0 : Math.Max(0, pesoTransbordo);
_pesoTransbordoRankingMinutos = Math.Max(0, pesoTransbordo);
if (config.FactorEquilibradoAndarBus is { } factorEquilibrado &&
double.IsFinite(factorEquilibrado) &&
factorEquilibrado > 0)
@@ -442,48 +444,8 @@ public partial class PlanificadorRutas
if (!confirmado)
return;
_mostrarParadas = true;
_usarHorariosTeoricos = true;
_segundosPorParadaBusManual = 30;
_segundosPorParadaBus = 30;
_activarRangoItinerarios = true;
_itinerarioMin = 1;
_itinerarioMax = 99;
_filtroSentidoItinerario = FiltroSentidoItinerario.Ambos;
_activarRangoTransbordos = true;
_minTransbordosPermitidos = 0;
_maxTransbordosPermitidos = 3;
_factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
_pesoTransbordoRankingMinutos = 4.0;
_factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
_mostrarElevacionesRutaConfiguracion = true;
_fuenteElevacionCalculo = FuenteElevacionLocal5;
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
_umbralPendienteSubidaMediaRuta = UmbralPendienteSubidaMediaRutaPorDefecto;
_umbralPendienteSubidaFuerteRuta = UmbralPendienteSubidaFuerteRutaPorDefecto;
_colorPendienteBajadaRuta = ColorPendienteBajadaRutaPorDefecto;
_colorPendienteLlanoRuta = ColorPendienteLlanoRutaPorDefecto;
_colorPendienteSubidaSuaveRuta = ColorPendienteSubidaSuaveRutaPorDefecto;
_colorPendienteSubidaMediaRuta = ColorPendienteSubidaMediaRutaPorDefecto;
_colorPendienteSubidaFuerteRuta = ColorPendienteSubidaFuerteRutaPorDefecto;
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
_umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
_velocidadPieKmH = 3.6;
_velocidadBusKmH = 16.0;
_minutosTransbordo = 5;
_margenTransbordoSegundos = 120;
_elevMaxPuntosPorTramo = 999;
_radioCandidatasMetros = 1200;
_walkProvider = ProveedorCaminata.Valhalla;
_nivelBrilloMapa = 0;
_usarColorRealLineaEnItinerarios = false;
_usarOsrmParaRanking = false;
_modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual;
_configuracionFabrica = await ValoresFabrica.CargarAsync();
AplicarValoresFabricaConservandoSeleccion();
if (_usarHoraSimulada)
StopClock();
@@ -504,48 +466,8 @@ public partial class PlanificadorRutas
{
_mostrarConfirmacionRestablecer = false;
_mostrarParadas = true;
_usarHorariosTeoricos = true;
_segundosPorParadaBusManual = 30;
_segundosPorParadaBus = 30;
_activarRangoItinerarios = true;
_itinerarioMin = 1;
_itinerarioMax = 99;
_filtroSentidoItinerario = FiltroSentidoItinerario.Ambos;
_activarRangoTransbordos = true;
_minTransbordosPermitidos = 0;
_maxTransbordosPermitidos = 3;
_factorPenalizacionDistanciaPie = FactorPenalizacionDistanciaPiePorDefecto;
_parametroPenalizacionDistanciaPieMetros = ParametroPenalizacionDistanciaPieMetrosPorDefecto;
_coeficienteEsfuerzoAltura = CoeficienteEsfuerzoAlturaPorDefecto;
_pesoTransbordoRankingMinutos = 4.0;
_factorEquilibradoAndarBus = FactorEquilibradoAndarBusPorDefecto;
_mostrarElevacionesRutaConfiguracion = true;
_fuenteElevacionCalculo = FuenteElevacionLocal5;
_umbralPendienteSubidaRuta = UmbralPendienteSubidaRutaPorDefecto;
_umbralPendienteSubidaMediaRuta = UmbralPendienteSubidaMediaRutaPorDefecto;
_umbralPendienteSubidaFuerteRuta = UmbralPendienteSubidaFuerteRutaPorDefecto;
_colorPendienteBajadaRuta = ColorPendienteBajadaRutaPorDefecto;
_colorPendienteLlanoRuta = ColorPendienteLlanoRutaPorDefecto;
_colorPendienteSubidaSuaveRuta = ColorPendienteSubidaSuaveRutaPorDefecto;
_colorPendienteSubidaMediaRuta = ColorPendienteSubidaMediaRutaPorDefecto;
_colorPendienteSubidaFuerteRuta = ColorPendienteSubidaFuerteRutaPorDefecto;
_umbralPendienteBajadaRuta = UmbralPendienteBajadaRutaPorDefecto;
_umbralAlturaAlertaPendienteRutaMetros = UmbralAlturaAlertaPendienteRutaMetrosPorDefecto;
_velocidadPieKmH = 3.6;
_velocidadBusKmH = 16.0;
_minutosTransbordo = 5;
_margenTransbordoSegundos = 120;
_elevMaxPuntosPorTramo = 999;
_radioCandidatasMetros = 1200;
_walkProvider = ProveedorCaminata.Valhalla;
_nivelBrilloMapa = 0;
_usarColorRealLineaEnItinerarios = false;
_usarOsrmParaRanking = false;
_modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual;
_configuracionFabrica = await ValoresFabrica.CargarAsync();
AplicarValoresFabricaConservandoSeleccion();
if (_usarHoraSimulada)
StopClock();

View File

@@ -1146,6 +1146,7 @@ public partial class PlanificadorRutas : ComponentBase
if (indice < 0 || indice >= _opcionesRuta.Count) return;
_indiceAlternativaSeleccionada = indice;
_proveedorSoloAPieRecalculo = _opcionesRuta[indice].EsSoloAPie ? _opcionesRuta[indice].ProveedorRuta : null;
await CerrarPopupsAsync();
ResetEstadoRuta(limpiarOpciones: false, limpiarMensaje: false);
@@ -1755,6 +1756,7 @@ public partial class PlanificadorRutas : ComponentBase
[JSInvokable]
public async Task OnMobileMapLongPress(double lat, double lon)
{
if (_modoDibujoExclusionCaminata) return;
await InvokeAsync(async () =>
{
if (_inspectorActivo)
@@ -1874,15 +1876,27 @@ public partial class PlanificadorRutas : ComponentBase
/// <summary>
/// Calcula ruta completa.
/// </summary>
private string? _proveedorSoloAPieRecalculo;
private string? ObtenerProveedorSoloAPieParaRecalculo(bool conservarSeleccion)
{
if (!conservarSeleccion)
_proveedorSoloAPieRecalculo = null;
else if (_indiceAlternativaSeleccionada >= 0 && _indiceAlternativaSeleccionada < _opcionesRuta.Count)
{
var seleccionada = _opcionesRuta[_indiceAlternativaSeleccionada];
_proveedorSoloAPieRecalculo = seleccionada.EsSoloAPie ? seleccionada.ProveedorRuta : null;
}
// Durante un calculo las opciones se vacian; otro cambio de punto conserva la preferencia.
return _proveedorSoloAPieRecalculo;
}
private async Task CalcularRutaCompletaAsync(bool conservarSeleccionSoloAPie = false)
{
if (_mapa is null || _coordenadasOrigen is null || _coordenadasDestino is null)
return;
var restaurarSeleccionSoloAPie = conservarSeleccionSoloAPie &&
_indiceAlternativaSeleccionada >= 0 &&
_indiceAlternativaSeleccionada < _opcionesRuta.Count &&
_opcionesRuta[_indiceAlternativaSeleccionada].EsSoloAPie;
var proveedorSoloAPiePreferido = ObtenerProveedorSoloAPieParaRecalculo(conservarSeleccionSoloAPie);
_mostrarElevacionesRuta = _mostrarElevacionesRutaConfiguracion;
@@ -2002,8 +2016,9 @@ public partial class PlanificadorRutas : ComponentBase
return;
}
var idxPreferida = restaurarSeleccionSoloAPie
? _opcionesRuta.FindIndex(opcion => opcion.EsSoloAPie)
var idxPreferida = proveedorSoloAPiePreferido is not null
? _opcionesRuta.FindIndex(opcion => opcion.EsSoloAPie &&
string.Equals(opcion.ProveedorRuta, proveedorSoloAPiePreferido, StringComparison.OrdinalIgnoreCase))
: BuscarIndicePreferidoPorCriterio(_criterioPrioritarioRuta);
if (idxPreferida < 0 || idxPreferida >= _opcionesRuta.Count)
{

View File

@@ -1312,7 +1312,7 @@ public partial class PlanificadorRutas : ComponentBase
}
await CerrarPopupsAsync();
await CalcularRutaCompletaAsync();
await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true);
StateHasChanged();
}
@@ -1321,6 +1321,9 @@ public partial class PlanificadorRutas : ComponentBase
/// </summary>
private async Task ReiniciarSeleccion()
{
_proveedorSoloAPieRecalculo = null;
await LimpiarZonasExclusionAsync();
await CerrarHerramientaZonasAsync();
await CerrarTodasLasPreviewsAsync();
await CerrarPopupsAsync();

View File

@@ -4,112 +4,159 @@ namespace RutasDBUS.Components.Pages;
public partial class PlanificadorRutas
{
private bool _modoDibujoExclusionCaminata;
private bool _poligonoExclusionCaminataCerrado;
private readonly List<double[]> _verticesExclusionCaminata = new();
private sealed class ZonaExclusionCaminata
{
public string Id { get; } = Guid.NewGuid().ToString("N");
public List<double[]> Vertices { get; } = new();
public bool Cerrada { get; set; }
}
private bool PuedeProbarExclusionCaminata =>
_walkProvider == ProveedorCaminata.Valhalla &&
_hayRutaValida &&
_opcionesRuta.Count > 0 &&
_indiceAlternativaSeleccionada >= 0 &&
_indiceAlternativaSeleccionada < _opcionesRuta.Count &&
(_opcionesRuta[_indiceAlternativaSeleccionada].EsSoloAPie ||
_tramosRuta.Any(tramo => tramo.Tipo.StartsWith("A pie", StringComparison.OrdinalIgnoreCase)));
private bool PoligonoExclusionCaminataValido =>
_verticesExclusionCaminata.Count >= 3 && _poligonoExclusionCaminataCerrado;
private readonly List<ZonaExclusionCaminata> _zonasExclusionCaminata = new();
private string? _zonaExclusionSeleccionada;
private bool _mostrarHerramientaZonas;
private bool _recalculandoZonas;
private string? _errorZonas;
private ZonaExclusionCaminata? ZonaExclusionSeleccionada =>
_zonasExclusionCaminata.Find(zona => zona.Id == _zonaExclusionSeleccionada);
private bool _modoDibujoExclusionCaminata =>
_mostrarHerramientaZonas && ZonaExclusionSeleccionada is { Cerrada: false };
private int NumeroZonasExclusion => _zonasExclusionCaminata.Count(zona => zona.Cerrada);
private List<List<double[]>>? ObtenerPoligonosExclusionCaminata()
{
if (!PoligonoExclusionCaminataValido)
return null;
var poligonos = _zonasExclusionCaminata.Where(zona => zona.Cerrada)
.Select(zona => zona.Vertices.Select(p => new[] { p[1], p[0] })
.Append(new[] { zona.Vertices[0][1], zona.Vertices[0][0] }).ToList()).ToList();
return poligonos.Count == 0 ? null : poligonos;
}
return new List<List<double[]>>
{
_verticesExclusionCaminata
.Select(punto => new[] { punto[1], punto[0] })
.Append(new[] { _verticesExclusionCaminata[0][1], _verticesExclusionCaminata[0][0] })
.ToList()
};
private async Task AbrirHerramientaZonasAsync()
{
_mostrarConfigAvanzada = false;
_mostrarHerramientaZonas = true;
await ActualizarEditorExclusionCaminataAsync();
}
private async Task CerrarHerramientaZonasAsync()
{
_zonasExclusionCaminata.RemoveAll(zona => !zona.Cerrada);
_zonaExclusionSeleccionada = null;
_mostrarHerramientaZonas = false;
_errorZonas = null;
await JS.InvokeVoidAsync("rt.clearExclusionPolygonEditor");
}
private async Task IniciarDibujoExclusionCaminataAsync()
{
if (!PuedeProbarExclusionCaminata)
return;
_modoDibujoExclusionCaminata = true;
_poligonoExclusionCaminataCerrado = false;
_verticesExclusionCaminata.Clear();
await JS.InvokeVoidAsync("rt.beginExclusionPolygonEditor");
StateHasChanged();
if (_recalculandoZonas || _modoDibujoExclusionCaminata || _zonasExclusionCaminata.Count >= 10) return;
var zona = new ZonaExclusionCaminata();
_zonasExclusionCaminata.Add(zona);
_zonaExclusionSeleccionada = zona.Id;
_errorZonas = null;
await ActualizarEditorExclusionCaminataAsync();
}
private async Task CancelarDibujoExclusionCaminataAsync()
private async Task SeleccionarZonaExclusionAsync(string id)
{
_modoDibujoExclusionCaminata = false;
_poligonoExclusionCaminataCerrado = false;
_verticesExclusionCaminata.Clear();
if (_recalculandoZonas) return;
_zonasExclusionCaminata.RemoveAll(zona => !zona.Cerrada);
_zonaExclusionSeleccionada = id;
_errorZonas = null;
await ActualizarEditorExclusionCaminataAsync();
}
private async Task EliminarZonaExclusionAsync(string id)
{
if (_recalculandoZonas) return;
_zonasExclusionCaminata.RemoveAll(zona => zona.Id == id);
if (_zonaExclusionSeleccionada == id) _zonaExclusionSeleccionada = null;
_errorZonas = null;
await ActualizarEditorExclusionCaminataAsync();
}
private async Task LimpiarZonasExclusionAsync()
{
_zonasExclusionCaminata.Clear();
_zonaExclusionSeleccionada = null;
_errorZonas = null;
await JS.InvokeVoidAsync("rt.clearExclusionPolygonEditor");
StateHasChanged();
}
private async Task DeshacerVerticeExclusionCaminataAsync()
{
if (_verticesExclusionCaminata.Count == 0 || _poligonoExclusionCaminataCerrado)
return;
_verticesExclusionCaminata.RemoveAt(_verticesExclusionCaminata.Count - 1);
if (ZonaExclusionSeleccionada is not { Cerrada: false } zona || zona.Vertices.Count == 0) return;
zona.Vertices.RemoveAt(zona.Vertices.Count - 1);
await ActualizarEditorExclusionCaminataAsync();
}
private async Task CerrarPoligonoExclusionCaminataAsync()
{
if (_verticesExclusionCaminata.Count < 3)
if (ZonaExclusionSeleccionada is not { Cerrada: false } zona || zona.Vertices.Count < 3) return;
if (!await JS.InvokeAsync<bool>("rt.isExclusionPolygonValid", zona.Vertices))
{
_errorZonas = "Revisa la zona: los lados no pueden cruzarse ni quedar alineados.";
return;
_poligonoExclusionCaminataCerrado = true;
await JS.InvokeVoidAsync("rt.closeExclusionPolygonEditor");
StateHasChanged();
}
zona.Cerrada = true;
_errorZonas = null;
await ActualizarEditorExclusionCaminataAsync();
}
private async Task CalcularRutaEvitandoPoligonoCaminataAsync()
{
if (!PoligonoExclusionCaminataValido)
return;
_modoDibujoExclusionCaminata = false;
await JS.InvokeVoidAsync("rt.finishExclusionPolygonEditor");
_omitirRegistroHistorialEnSiguienteCalculo = true;
await CalcularRutaCompletaAsync();
if (_recalculandoZonas || _modoDibujoExclusionCaminata || _coordenadasOrigen is null || _coordenadasDestino is null) return;
foreach (var zona in _zonasExclusionCaminata)
{
if (!await JS.InvokeAsync<bool>("rt.isExclusionPolygonValid", zona.Vertices))
{
_errorZonas = "Una zona tiene lados cruzados o no tiene superficie. Ajusta sus puntos.";
return;
}
}
_recalculandoZonas = true;
_errorZonas = null;
await ActualizarEditorExclusionCaminataAsync();
try
{
_omitirRegistroHistorialEnSiguienteCalculo = true;
await CalcularRutaCompletaAsync(conservarSeleccionSoloAPie: true);
}
finally
{
_recalculandoZonas = false;
await ActualizarEditorExclusionCaminataAsync();
}
}
[JSInvokable]
public async Task OnExclusionPolygonMapClick(double latitud, double longitud)
{
if (!_modoDibujoExclusionCaminata || _poligonoExclusionCaminataCerrado)
return;
_verticesExclusionCaminata.Add(new[] { latitud, longitud });
if (!_modoDibujoExclusionCaminata || _recalculandoZonas || !CoordenadaExclusionValida(latitud, longitud)) return;
var zona = ZonaExclusionSeleccionada!;
if (zona.Vertices.Count >= 50) return;
zona.Vertices.Add(new[] { latitud, longitud });
await ActualizarEditorExclusionCaminataAsync();
StateHasChanged();
}
[JSInvokable]
public Task OnExclusionPolygonVertexDragged(int indice, double latitud, double longitud)
public Task OnExclusionPolygonVertexDragged(string id, int indice, double latitud, double longitud)
{
if (indice >= 0 && indice < _verticesExclusionCaminata.Count)
{
_verticesExclusionCaminata[indice][0] = latitud;
_verticesExclusionCaminata[indice][1] = longitud;
}
var zona = _zonasExclusionCaminata.Find(zona => zona.Id == id);
if (_mostrarHerramientaZonas && !_recalculandoZonas && zona is not null &&
indice >= 0 && indice < zona.Vertices.Count && CoordenadaExclusionValida(latitud, longitud))
zona.Vertices[indice] = new[] { latitud, longitud };
return InvokeAsync(StateHasChanged);
}
private static bool CoordenadaExclusionValida(double latitud, double longitud) =>
double.IsFinite(latitud) && double.IsFinite(longitud) && Math.Abs(latitud) <= 90 && Math.Abs(longitud) <= 180;
private async Task ActualizarEditorExclusionCaminataAsync()
{
await JS.InvokeVoidAsync("rt.updateExclusionPolygonEditor", _verticesExclusionCaminata);
StateHasChanged();
if (!_mostrarHerramientaZonas) return;
await JS.InvokeVoidAsync("rt.updateExclusionPolygonEditor",
_zonasExclusionCaminata.Select(zona => new { id = zona.Id, points = zona.Vertices, closed = zona.Cerrada }),
_zonaExclusionSeleccionada, _recalculandoZonas);
}
}

View File

@@ -0,0 +1,86 @@
using System.Globalization;
using Microsoft.JSInterop;
using RutasDBUS.Modelos.Planificacion;
namespace RutasDBUS.Components.Pages;
public partial class PlanificadorRutas
{
private int _versionConsultaSuperficieSegmento;
/// <summary>
/// Consulta la superficie únicamente del segmento de elevación que se acaba de seleccionar.
/// </summary>
[JSInvokable]
public async Task OnElevationSegmentSelected(
string routeId,
int segmentIndex,
double[][] coordenadas)
{
if (string.IsNullOrWhiteSpace(routeId)
|| segmentIndex < 0
|| coordenadas is null
|| coordenadas.Length < 2)
return;
var linea = new double[coordenadas.Length, 2];
for (var i = 0; i < coordenadas.Length; i++)
{
if (coordenadas[i] is null || coordenadas[i].Length < 2)
return;
linea[i, 0] = coordenadas[i][0];
linea[i, 1] = coordenadas[i][1];
if (!double.IsFinite(linea[i, 0]) || !double.IsFinite(linea[i, 1]))
return;
}
var version = ++_versionConsultaSuperficieSegmento;
string superficie;
try
{
var informacion = await ClienteValhalla.ObtenerInformacionSueloAsync(linea);
superficie = FormatearSuperficieSegmento(informacion);
}
catch
{
superficie = "No disponible";
}
if (version != _versionConsultaSuperficieSegmento)
return;
try
{
await JS.InvokeVoidAsync(
"rt.setElevationSegmentSurface",
routeId,
segmentIndex,
superficie);
}
catch
{
// El mapa puede haber sido redibujado mientras respondía la consulta.
}
}
private static string FormatearSuperficieSegmento(InformacionSueloTramo? informacion)
{
if (informacion is null || !informacion.Disponible || informacion.Segmentos.Count == 0)
return "No disponible";
return string.Join(
"<br>",
informacion.Segmentos.Select(segmento =>
{
var uso = string.IsNullOrWhiteSpace(segmento.Uso)
? ""
: $" ({segmento.Uso})";
var distancia = segmento.DistanciaMetros < 1000
? segmento.DistanciaMetros.ToString("0.##", CultureInfo.InvariantCulture) + " m"
: (segmento.DistanciaMetros / 1000.0).ToString("0.###", CultureInfo.InvariantCulture) + " km";
return $"{segmento.Superficie}{uso}, {distancia}";
}));
}
}

View File

@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.Components;
using RutasDBUS.Modelos.Historial;
using RutasDBUS.Servicios.Configuracion;
namespace RutasDBUS.Components.Pages;
public partial class PlanificadorRutas
{
[Inject] private ServicioValoresFabrica ValoresFabrica { get; set; } = default!;
private ConfiguracionHistorialRuta? _configuracionFabrica;
private ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaHistorial()
{
var config = _configuracionFabrica?.Clonar() ?? CrearConfiguracionBaseHistorial();
var ahora = DateTime.Now;
config.FechaSimulada = ahora.ToString("yyyy-MM-dd", System.Globalization.CultureInfo.InvariantCulture);
config.HoraSimulada = ahora.ToString("HH:mm:ss", System.Globalization.CultureInfo.InvariantCulture);
return config;
}
private void AplicarValoresFabricaConservandoSeleccion()
{
var actual = CrearConfiguracionLocal();
var predeterminada = CrearConfiguracionPredeterminadaHistorial();
predeterminada.ActivarRangoLineas = actual.ActivarRangoLineas;
predeterminada.LineaMin = actual.LineaMin;
predeterminada.LineaMax = actual.LineaMax;
predeterminada.LineasPermitidas = actual.LineasPermitidas;
predeterminada.UsarHoraSimulada = actual.UsarHoraSimulada;
predeterminada.FechaSimulada = actual.FechaSimulada;
predeterminada.HoraSimulada = actual.HoraSimulada;
predeterminada.CriterioPrioritarioRuta = actual.CriterioPrioritarioRuta;
predeterminada.PreferenciaEtiquetaRuta = actual.PreferenciaEtiquetaRuta;
AplicarConfiguracionLocal(predeterminada);
_modoConfiguracionHistorial = ModoConfiguracionHistorial.Actual;
}
}

View File

@@ -227,6 +227,16 @@
</div>
}
<button type="button"
class="map-zones-toggle @(_mostrarHerramientaZonas ? "is-active" : "")"
title="Zonas a evitar: @NumeroZonasExclusion"
aria-label="Abrir zonas a evitar"
aria-pressed="@_mostrarHerramientaZonas"
@onclick="AbrirHerramientaZonasAsync">
<span aria-hidden="true">⬡</span>
<span>Zonas</span>
</button>
</div>
@* STACK DE POPUPS (izquierda) *@
@if (_paradaClicada is not null || _itinerarioClicado is not null || _popupInspectorVisible)
@@ -847,53 +857,6 @@
</div>
}
@if (PuedeProbarExclusionCaminata)
{
<div class="route-exclusion-tool" role="region" aria-label="Evitar zona andando">
<div class="route-exclusion-tool__header">
<strong>Probar evitando una zona andando</strong>
<span class="muted">v2</span>
</div>
@if (!_modoDibujoExclusionCaminata && !PoligonoExclusionCaminataValido)
{
<button type="button" class="btn-ghost route-exclusion-tool__button"
title="Dibujar una zona para evitarla en la siguiente búsqueda"
@onclick="IniciarDibujoExclusionCaminataAsync">
Dibujar zona a evitar
</button>
}
else
{
<div class="route-exclusion-tool__status">
@if (_poligonoExclusionCaminataCerrado)
{
<span>Zona cerrada con @_verticesExclusionCaminata.Count vértices. Puedes arrastrarlos.</span>
}
else
{
<span>Pulsa en el mapa para añadir vértices: @_verticesExclusionCaminata.Count.</span>
}
</div>
<div class="route-exclusion-tool__actions">
@if (!_poligonoExclusionCaminataCerrado)
{
<button type="button" class="btn-ghost" @onclick="DeshacerVerticeExclusionCaminataAsync"
disabled="@(_verticesExclusionCaminata.Count == 0)">Deshacer punto</button>
<button type="button" class="btn-ghost route-exclusion-tool__close" @onclick="CerrarPoligonoExclusionCaminataAsync"
disabled="@(_verticesExclusionCaminata.Count < 3)">Cerrar zona</button>
}
@if (PoligonoExclusionCaminataValido)
{
<button type="button" class="btn-primary route-exclusion-tool__calculate" @onclick="CalcularRutaEvitandoPoligonoCaminataAsync">
Calcular alternativa
</button>
}
<button type="button" class="btn-ghost" @onclick="CancelarDibujoExclusionCaminataAsync">Cancelar</button>
</div>
}
</div>
}
@* ========= RESUMEN BONITO ARRIBA (único) ========= *@
<p class="route-main-text">
@@ -1503,6 +1466,53 @@
</div>
}
@if (_mostrarHerramientaZonas)
{
<section class="zones-panel draggable-popup" data-drag-key="zonas-exclusion" aria-label="Zonas a evitar">
<div class="config-panel-header draggable-popup-handle">
<h5>Zonas a evitar <small>v2</small></h5>
<button type="button" class="config-close-btn" title="Cerrar herramienta" aria-label="Cerrar herramienta" @onclick="CerrarHerramientaZonasAsync">×</button>
</div>
<div class="zones-panel__list">
@for (var i = 0; i < _zonasExclusionCaminata.Count; i++)
{
var zona = _zonasExclusionCaminata[i];
<div class="zones-panel__item" @key="zona.Id">
<button type="button" class="btn-ghost @(zona.Id == _zonaExclusionSeleccionada ? "zones-panel__selected" : "")"
disabled="@(_recalculandoZonas || !zona.Cerrada)" @onclick="() => SeleccionarZonaExclusionAsync(zona.Id)">
Zona @(i + 1) · @zona.Vertices.Count puntos @(!zona.Cerrada ? "(abierta)" : "")
</button>
<button type="button" class="btn-ghost zones-panel__icon" title="Eliminar zona" aria-label="Eliminar zona"
disabled="@_recalculandoZonas" @onclick="() => EliminarZonaExclusionAsync(zona.Id)">×</button>
</div>
}
</div>
<div class="zones-panel__actions">
@if (_modoDibujoExclusionCaminata)
{
<button type="button" class="btn-ghost" title="Deshacer último punto" aria-label="Deshacer último punto"
disabled="@(ZonaExclusionSeleccionada!.Vertices.Count == 0)" @onclick="DeshacerVerticeExclusionCaminataAsync">↶</button>
<button type="button" class="btn-ghost" disabled="@(ZonaExclusionSeleccionada!.Vertices.Count < 3)"
@onclick="CerrarPoligonoExclusionCaminataAsync">Cerrar zona</button>
}
else
{
<button type="button" class="btn-ghost" disabled="@(_recalculandoZonas || _zonasExclusionCaminata.Count >= 10)"
@onclick="IniciarDibujoExclusionCaminataAsync">Añadir zona</button>
}
<button type="button" class="btn-ghost" disabled="@(_recalculandoZonas || _zonasExclusionCaminata.Count == 0)"
@onclick="LimpiarZonasExclusionAsync">Borrar todas</button>
<button type="button" class="btn-ghost zones-panel__primary"
disabled="@(_recalculandoZonas || _modoDibujoExclusionCaminata || _coordenadasOrigen is null || _coordenadasDestino is null)"
@onclick="CalcularRutaEvitandoPoligonoCaminataAsync">@(_recalculandoZonas ? "Calculando..." : "Recalcular")</button>
</div>
@if (!string.IsNullOrWhiteSpace(_errorZonas))
{
<p class="zones-panel__error" role="alert">@_errorZonas</p>
}
</section>
}
<!-- CONFIG -->
@* <button class="config-toggle-btn" @onclick="ToggleConfigPanel">⚙</button>
*@