cambaido ctrl por los shift

mover ventana de zonas de exclusion
puntos en zonas de exclusion
vertices mas pequeños en las zonas de exclusion
check y persistencia de las zonas d eexclusion*
distancia andando visual de un punto z al anterior
primer lector del lidar para simualciones de terreno
quitado el traductor de el selector de superficie de valhalla
This commit is contained in:
2026-09-09 14:59:44 +02:00
parent acbdd1501d
commit d50333f82a
16 changed files with 316 additions and 54 deletions

View File

@@ -56,6 +56,7 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
try { await JS.InvokeVoidAsync("rt.initMobileMapControls"); } catch (Exception ex) { Console.WriteLine("rt.initMobileMapControls: " + ex); } try { await JS.InvokeVoidAsync("rt.initMobileMapControls"); } catch (Exception ex) { Console.WriteLine("rt.initMobileMapControls: " + ex); }
try { await JS.InvokeVoidAsync("rt.initDraggablePopups"); } catch (Exception ex) { Console.WriteLine("rt.initDraggablePopups: " + ex); } try { await JS.InvokeVoidAsync("rt.initDraggablePopups"); } catch (Exception ex) { Console.WriteLine("rt.initDraggablePopups: " + ex); }
await CargarConfiguracionLocalAsync(); await CargarConfiguracionLocalAsync();
await CargarZonasAsync();
await CargarModoConfiguracionHistorialAsync(); await CargarModoConfiguracionHistorialAsync();
await AsegurarHistorialLocalCargadoAsync(); await AsegurarHistorialLocalCargadoAsync();
await CargarHistorialCompartidoAsync(); await CargarHistorialCompartidoAsync();
@@ -509,15 +510,17 @@ public partial class PlanificadorRutas : ComponentBase, IDisposable
private async Task<(double[,]? line, double dist, double dur)> WalkValhallaCached(double lat1, double lon1, double lat2, double lon2) private async Task<(double[,]? line, double dist, double dur)> WalkValhallaCached(double lat1, double lon1, double lat2, double lon2)
{ {
var poligonos = ObtenerPoligonosExclusionCaminata(); var poligonos = ObtenerPoligonosExclusionCaminata();
var puntos = ObtenerPuntosExclusionCaminata();
var key = WalkKey("valhalla", lat1, lon1, lat2, lon2); var key = WalkKey("valhalla", lat1, lon1, lat2, lon2);
if (poligonos is not null) key += "|" + JsonSerializer.Serialize(poligonos); if (poligonos is not null) key += "|" + JsonSerializer.Serialize(poligonos);
if (puntos is not null) key += "|puntos|" + JsonSerializer.Serialize(puntos);
if (_walkCache.TryGetValue(key, out var v)) if (_walkCache.TryGetValue(key, out var v))
return v; return v;
try try
{ {
var (line, dist, dur) = await ClienteValhalla.ObtenerRutaAsync(lat1, lon1, lat2, lon2, var (line, dist, dur) = await ClienteValhalla.ObtenerRutaAsync(lat1, lon1, lat2, lon2,
costing: "pedestrian", excludePolygons: poligonos); costing: "pedestrian", excludePolygons: poligonos, excludeLocations: puntos);
// ✅ Solo cachear si es un resultado "bueno" // ✅ Solo cachear si es un resultado "bueno"
if (line is not null && dist > 0) if (line is not null && dist > 0)

View File

@@ -1,10 +1,29 @@
using System.Globalization; using System.Globalization;
using Microsoft.JSInterop;
namespace RutasDBUS.Components.Pages; namespace RutasDBUS.Components.Pages;
public partial class PlanificadorRutas public partial class PlanificadorRutas
{ {
private PuntoInspectorAnterior? _inspectorPuntoAnterior; private PuntoInspectorAnterior? _inspectorPuntoAnterior;
private double[,]? _caminoInspectorAnterior;
private bool _caminoInspectorVisible;
private async Task OcultarCaminoInspectorAsync()
{
_caminoInspectorAnterior = null;
_caminoInspectorVisible = false;
try { await JS.InvokeVoidAsync("rt.setInspectorWalk", Array.Empty<double[]>(), false); } catch { }
}
private async Task AlternarCaminoInspectorAsync()
{
if (_caminoInspectorAnterior is null) return;
_caminoInspectorVisible = !_caminoInspectorVisible;
var puntos = Enumerable.Range(0, _caminoInspectorAnterior.GetLength(0))
.Select(i => new[] { _caminoInspectorAnterior[i, 0], _caminoInspectorAnterior[i, 1] }).ToArray();
await JS.InvokeVoidAsync("rt.setInspectorWalk", puntos, _caminoInspectorVisible);
}
private sealed record PuntoInspectorAnterior( private sealed record PuntoInspectorAnterior(
double Latitud, double Latitud,
@@ -81,7 +100,7 @@ public partial class PlanificadorRutas
try try
{ {
var tareaDireccion = ResolverDireccionPuntoInspectorAnteriorAsync(punto); var tareaDireccion = ResolverDireccionPuntoInspectorAnteriorAsync(punto);
var tareaCaminata = WalkCached( var tareaCaminata = ClienteValhalla.ObtenerRutaAsync(
punto.Latitud, punto.Latitud,
punto.Longitud, punto.Longitud,
_inspectorLat, _inspectorLat,
@@ -96,11 +115,12 @@ public partial class PlanificadorRutas
} }
var caminata = await tareaCaminata; var caminata = await tareaCaminata;
_caminoInspectorAnterior = caminata.Linea;
_inspectorPuntoAnterior = puntoVigente with _inspectorPuntoAnterior = puntoVigente with
{ {
Direccion = await tareaDireccion, Direccion = await tareaDireccion,
DistanciaAndandoMetros = caminata.line is not null && caminata.dist > 0 DistanciaAndandoMetros = caminata.Linea is not null && double.IsFinite(caminata.DistanciaMetros) && caminata.DistanciaMetros >= 0
? caminata.dist ? caminata.DistanciaMetros
: null, : null,
CalculandoDatosAdicionales = false CalculandoDatosAdicionales = false
}; };

View File

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

View File

@@ -1534,6 +1534,8 @@ public partial class PlanificadorRutas : ComponentBase
: null; : null;
var versionInspector = ++_versionInspector; var versionInspector = ++_versionInspector;
await OcultarCaminoInspectorAsync();
if (versionInspector != _versionInspector) return;
_inspectorLat = lat; _inspectorLat = lat;
_inspectorLon = lon; _inspectorLon = lon;
_inspectorElevIdee = null; _inspectorElevIdee = null;
@@ -1649,6 +1651,7 @@ public partial class PlanificadorRutas : ComponentBase
private async Task CerrarInspectorAsync() private async Task CerrarInspectorAsync()
{ {
_versionInspector++; _versionInspector++;
await OcultarCaminoInspectorAsync();
_popupInspectorVisible = false; _popupInspectorVisible = false;
_inspectorPuntoAnterior = null; _inspectorPuntoAnterior = null;
_inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>(); _inspectorLugaresCercanos = Array.Empty<LugarGeocodificadoCercano>();
@@ -1949,6 +1952,7 @@ public partial class PlanificadorRutas : ComponentBase
FactorEquilibradoAndarBus = _factorEquilibradoAndarBus, FactorEquilibradoAndarBus = _factorEquilibradoAndarBus,
UsarValhallaParaCaminatas = _walkProvider == ProveedorCaminata.Valhalla, UsarValhallaParaCaminatas = _walkProvider == ProveedorCaminata.Valhalla,
PoligonosExclusionCaminata = ObtenerPoligonosExclusionCaminata(), PoligonosExclusionCaminata = ObtenerPoligonosExclusionCaminata(),
PuntosExclusionCaminata = ObtenerPuntosExclusionCaminata(),
VelocidadBusKmH = _velocidadBusKmH, VelocidadBusKmH = _velocidadBusKmH,
MinutosPorTransbordo = _minutosTransbordo, MinutosPorTransbordo = _minutosTransbordo,
MargenTransbordoSegundos = Math.Clamp(_margenTransbordoSegundos, 0, 1800), MargenTransbordoSegundos = Math.Clamp(_margenTransbordoSegundos, 0, 1800),

View File

@@ -1322,7 +1322,7 @@ public partial class PlanificadorRutas : ComponentBase
private async Task ReiniciarSeleccion() private async Task ReiniciarSeleccion()
{ {
_proveedorSoloAPieRecalculo = null; _proveedorSoloAPieRecalculo = null;
await LimpiarZonasExclusionAsync(); await CerrarHerramientaZonasAsync();
await CerrarHerramientaZonasAsync(); await CerrarHerramientaZonasAsync();
await CerrarTodasLasPreviewsAsync(); await CerrarTodasLasPreviewsAsync();

View File

@@ -1,4 +1,6 @@
using Microsoft.JSInterop; using Microsoft.JSInterop;
using Microsoft.AspNetCore.Components;
using System.Text.Json;
namespace RutasDBUS.Components.Pages; namespace RutasDBUS.Components.Pages;
@@ -6,9 +8,11 @@ public partial class PlanificadorRutas
{ {
private sealed class ZonaExclusionCaminata private sealed class ZonaExclusionCaminata
{ {
public string Id { get; } = Guid.NewGuid().ToString("N"); public string Id { get; set; } = Guid.NewGuid().ToString("N");
public List<double[]> Vertices { get; } = new(); public List<double[]> Vertices { get; set; } = new();
public bool Cerrada { get; set; } public bool Cerrada { get; set; }
public bool Activa { get; set; } = true;
public bool EsPunto { get; set; }
} }
private readonly List<ZonaExclusionCaminata> _zonasExclusionCaminata = new(); private readonly List<ZonaExclusionCaminata> _zonasExclusionCaminata = new();
@@ -16,6 +20,57 @@ public partial class PlanificadorRutas
private bool _mostrarHerramientaZonas; private bool _mostrarHerramientaZonas;
private bool _recalculandoZonas; private bool _recalculandoZonas;
private string? _errorZonas; private string? _errorZonas;
private const string ClaveZonasLocal = "rutasdbus.zonas.v1";
private bool _zonasCargadas;
private bool _nuevaExclusionEsPunto;
private async Task CargarZonasAsync()
{
try
{
var resultado = await JS.InvokeAsync<JsonElement>("rt.loadLocalValueResult", ClaveZonasLocal);
if (!resultado.GetProperty("ok").GetBoolean())
throw new InvalidOperationException("Almacenamiento no disponible");
var json = resultado.GetProperty("value").GetString();
if (!string.IsNullOrWhiteSpace(json))
{
var zonas = JsonSerializer.Deserialize<List<ZonaExclusionCaminata>>(json) ?? new();
foreach (var zona in zonas.Take(10))
{
if (zona is null || zona.Vertices is null || zona.Vertices.Count > 50 ||
zona.Vertices.Any(p => p is null || p.Length != 2 || !CoordenadaExclusionValida(p[0], p[1])))
continue;
if (zona.EsPunto && zona.Vertices.Count > 1) continue;
if (zona.Cerrada && (zona.EsPunto ? zona.Vertices.Count != 1 :
!await JS.InvokeAsync<bool>("rt.isExclusionPolygonValid", zona.Vertices)))
continue;
zona.Id = Guid.NewGuid().ToString("N");
_zonasExclusionCaminata.Add(zona);
}
}
_zonasCargadas = true;
}
catch { _errorZonas = "No se pudieron cargar las zonas guardadas. Recarga antes de editarlas."; }
}
private async Task GuardarZonasAsync()
{
if (!_zonasCargadas) return;
try
{
var resultado = await JS.InvokeAsync<JsonElement>("rt.saveLocalValueResult", ClaveZonasLocal,
JsonSerializer.Serialize(_zonasExclusionCaminata));
if (!resultado.GetProperty("ok").GetBoolean())
_errorZonas = "No se pudieron guardar las zonas en este navegador.";
}
catch { _errorZonas = "No se pudieron guardar las zonas en este navegador."; }
}
private async Task CambiarZonaActivaAsync(ZonaExclusionCaminata zona, ChangeEventArgs args)
{
zona.Activa = args.Value is true;
await ActualizarEditorExclusionCaminataAsync();
}
private ZonaExclusionCaminata? ZonaExclusionSeleccionada => private ZonaExclusionCaminata? ZonaExclusionSeleccionada =>
_zonasExclusionCaminata.Find(zona => zona.Id == _zonaExclusionSeleccionada); _zonasExclusionCaminata.Find(zona => zona.Id == _zonaExclusionSeleccionada);
private bool _modoDibujoExclusionCaminata => private bool _modoDibujoExclusionCaminata =>
@@ -23,12 +78,19 @@ public partial class PlanificadorRutas
private List<List<double[]>>? ObtenerPoligonosExclusionCaminata() private List<List<double[]>>? ObtenerPoligonosExclusionCaminata()
{ {
var poligonos = _zonasExclusionCaminata.Where(zona => zona.Cerrada) var poligonos = _zonasExclusionCaminata.Where(zona => zona.Cerrada && zona.Activa && !zona.EsPunto)
.Select(zona => zona.Vertices.Select(p => new[] { p[1], p[0] }) .Select(zona => zona.Vertices.Select(p => new[] { p[1], p[0] })
.Append(new[] { zona.Vertices[0][1], zona.Vertices[0][0] }).ToList()).ToList(); .Append(new[] { zona.Vertices[0][1], zona.Vertices[0][0] }).ToList()).ToList();
return poligonos.Count == 0 ? null : poligonos; return poligonos.Count == 0 ? null : poligonos;
} }
private List<double[]>? ObtenerPuntosExclusionCaminata()
{
var puntos = _zonasExclusionCaminata.Where(z => z.Cerrada && z.Activa && z.EsPunto)
.Select(z => z.Vertices[0].ToArray()).ToList();
return puntos.Count == 0 ? null : puntos;
}
private async Task AbrirHerramientaZonasAsync() private async Task AbrirHerramientaZonasAsync()
{ {
_mostrarConfigAvanzada = false; _mostrarConfigAvanzada = false;
@@ -49,7 +111,6 @@ public partial class PlanificadorRutas
private async Task CerrarHerramientaZonasAsync() private async Task CerrarHerramientaZonasAsync()
{ {
_zonasExclusionCaminata.RemoveAll(zona => !zona.Cerrada);
_zonaExclusionSeleccionada = null; _zonaExclusionSeleccionada = null;
_mostrarHerramientaZonas = false; _mostrarHerramientaZonas = false;
_errorZonas = null; _errorZonas = null;
@@ -58,8 +119,8 @@ public partial class PlanificadorRutas
private async Task IniciarDibujoExclusionCaminataAsync() private async Task IniciarDibujoExclusionCaminataAsync()
{ {
if (_recalculandoZonas || _modoDibujoExclusionCaminata || _zonasExclusionCaminata.Count >= 10) return; if (!_zonasCargadas || _recalculandoZonas || _modoDibujoExclusionCaminata || _zonasExclusionCaminata.Count >= 10) return;
var zona = new ZonaExclusionCaminata(); var zona = new ZonaExclusionCaminata { EsPunto = _nuevaExclusionEsPunto };
_zonasExclusionCaminata.Add(zona); _zonasExclusionCaminata.Add(zona);
_zonaExclusionSeleccionada = zona.Id; _zonaExclusionSeleccionada = zona.Id;
_errorZonas = null; _errorZonas = null;
@@ -69,7 +130,6 @@ public partial class PlanificadorRutas
private async Task SeleccionarZonaExclusionAsync(string id) private async Task SeleccionarZonaExclusionAsync(string id)
{ {
if (_recalculandoZonas) return; if (_recalculandoZonas) return;
_zonasExclusionCaminata.RemoveAll(zona => !zona.Cerrada);
_zonaExclusionSeleccionada = id; _zonaExclusionSeleccionada = id;
_errorZonas = null; _errorZonas = null;
await ActualizarEditorExclusionCaminataAsync(); await ActualizarEditorExclusionCaminataAsync();
@@ -89,6 +149,7 @@ public partial class PlanificadorRutas
_zonasExclusionCaminata.Clear(); _zonasExclusionCaminata.Clear();
_zonaExclusionSeleccionada = null; _zonaExclusionSeleccionada = null;
_errorZonas = null; _errorZonas = null;
await GuardarZonasAsync();
await JS.InvokeVoidAsync("rt.clearExclusionPolygonEditor"); await JS.InvokeVoidAsync("rt.clearExclusionPolygonEditor");
} }
@@ -115,7 +176,7 @@ public partial class PlanificadorRutas
private async Task CalcularRutaEvitandoPoligonoCaminataAsync() private async Task CalcularRutaEvitandoPoligonoCaminataAsync()
{ {
if (_recalculandoZonas || _modoDibujoExclusionCaminata || _coordenadasOrigen is null || _coordenadasDestino is null) return; if (_recalculandoZonas || _modoDibujoExclusionCaminata || _coordenadasOrigen is null || _coordenadasDestino is null) return;
foreach (var zona in _zonasExclusionCaminata) foreach (var zona in _zonasExclusionCaminata.Where(z => z.Cerrada && z.Activa && !z.EsPunto))
{ {
if (!await JS.InvokeAsync<bool>("rt.isExclusionPolygonValid", zona.Vertices)) if (!await JS.InvokeAsync<bool>("rt.isExclusionPolygonValid", zona.Vertices))
{ {
@@ -145,18 +206,20 @@ public partial class PlanificadorRutas
var zona = ZonaExclusionSeleccionada!; var zona = ZonaExclusionSeleccionada!;
if (zona.Vertices.Count >= 50) return; if (zona.Vertices.Count >= 50) return;
zona.Vertices.Add(new[] { latitud, longitud }); zona.Vertices.Add(new[] { latitud, longitud });
if (zona.EsPunto) zona.Cerrada = true;
await ActualizarEditorExclusionCaminataAsync(); await ActualizarEditorExclusionCaminataAsync();
StateHasChanged(); StateHasChanged();
} }
[JSInvokable] [JSInvokable]
public Task OnExclusionPolygonVertexDragged(string id, int indice, double latitud, double longitud) public async Task OnExclusionPolygonVertexDragged(string id, int indice, double latitud, double longitud)
{ {
var zona = _zonasExclusionCaminata.Find(zona => zona.Id == id); var zona = _zonasExclusionCaminata.Find(zona => zona.Id == id);
if (_mostrarHerramientaZonas && !_recalculandoZonas && zona is not null && if (_mostrarHerramientaZonas && !_recalculandoZonas && zona is not null &&
indice >= 0 && indice < zona.Vertices.Count && CoordenadaExclusionValida(latitud, longitud)) indice >= 0 && indice < zona.Vertices.Count && CoordenadaExclusionValida(latitud, longitud))
zona.Vertices[indice] = new[] { latitud, longitud }; zona.Vertices[indice] = new[] { latitud, longitud };
return InvokeAsync(StateHasChanged); await GuardarZonasAsync();
await InvokeAsync(StateHasChanged);
} }
private static bool CoordenadaExclusionValida(double latitud, double longitud) => private static bool CoordenadaExclusionValida(double latitud, double longitud) =>
@@ -164,9 +227,10 @@ public partial class PlanificadorRutas
private async Task ActualizarEditorExclusionCaminataAsync() private async Task ActualizarEditorExclusionCaminataAsync()
{ {
await GuardarZonasAsync();
if (!_mostrarHerramientaZonas) return; if (!_mostrarHerramientaZonas) return;
await JS.InvokeVoidAsync("rt.updateExclusionPolygonEditor", await JS.InvokeVoidAsync("rt.updateExclusionPolygonEditor",
_zonasExclusionCaminata.Select(zona => new { id = zona.Id, points = zona.Vertices, closed = zona.Cerrada }), _zonasExclusionCaminata.Select(zona => new { id = zona.Id, points = zona.Vertices, closed = zona.Cerrada, active = zona.Activa, point = zona.EsPunto }),
_zonaExclusionSeleccionada, _recalculandoZonas); _zonaExclusionSeleccionada, _recalculandoZonas);
} }
} }

View File

@@ -9,6 +9,10 @@ public partial class PlanificadorRutas
{ {
[Inject] private ServicioValoresFabrica ValoresFabrica { get; set; } = default!; [Inject] private ServicioValoresFabrica ValoresFabrica { get; set; } = default!;
private ConfiguracionHistorialRuta? _configuracionFabrica; private ConfiguracionHistorialRuta? _configuracionFabrica;
private ConfiguracionHistorialRuta? _fabricaPendiente;
private readonly List<(string Campo, string Antes, string Despues)> _cambiosFabrica = new();
private string? _mensajeFabrica;
private bool _guardandoFabrica;
private ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaHistorial() private ConfiguracionHistorialRuta CrearConfiguracionPredeterminadaHistorial()
{ {
@@ -39,14 +43,48 @@ public partial class PlanificadorRutas
[JSInvokable] [JSInvokable]
public async Task GuardarConfiguracionFabricaDesdeAtajoAsync() public async Task GuardarConfiguracionFabricaDesdeAtajoAsync()
{ {
if (!_configuracionLocalCargada) if (!_configuracionLocalCargada || _guardandoFabrica || _fabricaPendiente is not null)
return; return;
var actual = CrearConfiguracionLocal(); var actual = CrearConfiguracionLocal();
if (!await ValoresFabrica.GuardarAsync(actual)) var anterior = await ValoresFabrica.CargarAsync() ?? CrearConfiguracionBaseHistorial();
return; _cambiosFabrica.Clear();
foreach (var propiedad in typeof(ConfiguracionHistorialRuta).GetProperties())
_configuracionFabrica = actual.Clonar(); {
if (propiedad.Name.StartsWith("Version", StringComparison.Ordinal)) continue;
var antes = System.Text.Json.JsonSerializer.Serialize(propiedad.GetValue(anterior));
var despues = System.Text.Json.JsonSerializer.Serialize(propiedad.GetValue(actual));
if (antes == despues) continue;
var nombre = System.Text.RegularExpressions.Regex.Replace(propiedad.Name, "([a-z])([A-Z])", "$1 $2");
_cambiosFabrica.Add((nombre, antes.Trim('"'), despues.Trim('"')));
}
_mensajeFabrica = null;
_fabricaPendiente = actual.Clonar();
await InvokeAsync(StateHasChanged); await InvokeAsync(StateHasChanged);
} }
private void CancelarGuardadoFabrica()
{
if (_guardandoFabrica) return;
_fabricaPendiente = null;
_mensajeFabrica = null;
}
private async Task ConfirmarGuardadoFabricaAsync()
{
if (_fabricaPendiente is null || _guardandoFabrica) return;
_guardandoFabrica = true;
try
{
if (await ValoresFabrica.GuardarAsync(_fabricaPendiente))
{
_configuracionFabrica = _fabricaPendiente.Clonar();
_fabricaPendiente = null;
_mensajeFabrica = "Valores predeterminados guardados.";
}
else
_mensajeFabrica = "No se pudo guardar. Revisa los valores y los permisos de escritura del servidor.";
}
finally { _guardandoFabrica = false; }
}
} }

View File

@@ -447,7 +447,7 @@
} }
</div> </div>
<div class="inspector-surface-row"> <div class="inspector-surface-row">
<b>Superficie:</b> <b>Superficie (V2):</b>
@if (_inspectorSuperficieCargando) @if (_inspectorSuperficieCargando)
{ {
<span class="muted">Consultando...</span> <span class="muted">Consultando...</span>
@@ -457,6 +457,10 @@
<span>@(_inspectorSuperficieTexto ?? "No disponible")</span> <span>@(_inspectorSuperficieTexto ?? "No disponible")</span>
} }
</div> </div>
<div class="inspector-surface-row">
<b>Clasificación LiDAR:</b>
<span class="muted">No disponible: falta la nube LiDAR clasificada local.</span>
</div>
@if (_inspectorLugaresCercanos.Count > 0) @if (_inspectorLugaresCercanos.Count > 0)
{ {
@@ -507,6 +511,15 @@
else else
{ {
<span>@(puntoAnterior.DistanciaAndandoMetros is { } distanciaAndando ? FormatearDistanciaPuntoInspector(distanciaAndando) : "-")</span> <span>@(puntoAnterior.DistanciaAndandoMetros is { } distanciaAndando ? FormatearDistanciaPuntoInspector(distanciaAndando) : "-")</span>
@if (_caminoInspectorAnterior is not null)
{
<button type="button" class="btn-ghost inspector-walk-eye"
title="@(_caminoInspectorVisible ? "Ocultar camino" : "Ver camino")"
aria-label="@(_caminoInspectorVisible ? "Ocultar camino" : "Ver camino")"
aria-pressed="@_caminoInspectorVisible" @onclick="AlternarCaminoInspectorAsync">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true"><path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7S2 12 2 12Z" /><circle cx="12" cy="12" r="3" /></svg>
</button>
}
} }
</span> </span>
</div> </div>
@@ -1480,9 +1493,12 @@
{ {
var zona = _zonasExclusionCaminata[i]; var zona = _zonasExclusionCaminata[i];
<div class="zones-panel__item" @key="zona.Id"> <div class="zones-panel__item" @key="zona.Id">
<input type="checkbox" checked="@zona.Activa" title="Activar zona"
aria-label="Activar zona" disabled="@_recalculandoZonas"
@onchange="e => CambiarZonaActivaAsync(zona, e)" />
<button type="button" class="btn-ghost @(zona.Id == _zonaExclusionSeleccionada ? "zones-panel__selected" : "")" <button type="button" class="btn-ghost @(zona.Id == _zonaExclusionSeleccionada ? "zones-panel__selected" : "")"
disabled="@(_recalculandoZonas || !zona.Cerrada)" @onclick="() => SeleccionarZonaExclusionAsync(zona.Id)"> disabled="@_recalculandoZonas" @onclick="() => SeleccionarZonaExclusionAsync(zona.Id)">
Zona @(i + 1) · @zona.Vertices.Count puntos @(!zona.Cerrada ? "(abierta)" : "") @(zona.EsPunto ? "Punto" : "Zona") @(i + 1) @(!zona.EsPunto ? $"· {zona.Vertices.Count} puntos" : "") @(!zona.Cerrada ? "(abierta)" : "")
</button> </button>
<button type="button" class="btn-ghost zones-panel__icon" title="Eliminar zona" aria-label="Eliminar zona" <button type="button" class="btn-ghost zones-panel__icon" title="Eliminar zona" aria-label="Eliminar zona"
disabled="@_recalculandoZonas" @onclick="() => EliminarZonaExclusionAsync(zona.Id)">×</button> disabled="@_recalculandoZonas" @onclick="() => EliminarZonaExclusionAsync(zona.Id)">×</button>
@@ -1494,13 +1510,17 @@
{ {
<button type="button" class="btn-ghost" title="Deshacer último punto" aria-label="Deshacer último punto" <button type="button" class="btn-ghost" title="Deshacer último punto" aria-label="Deshacer último punto"
disabled="@(ZonaExclusionSeleccionada!.Vertices.Count == 0)" @onclick="DeshacerVerticeExclusionCaminataAsync">↶</button> disabled="@(ZonaExclusionSeleccionada!.Vertices.Count == 0)" @onclick="DeshacerVerticeExclusionCaminataAsync">↶</button>
<button type="button" class="btn-ghost" disabled="@(ZonaExclusionSeleccionada!.Vertices.Count < 3)" <button type="button" class="btn-ghost" disabled="@(ZonaExclusionSeleccionada!.Vertices.Count < 3 || ZonaExclusionSeleccionada.EsPunto)"
@onclick="CerrarPoligonoExclusionCaminataAsync">Cerrar zona</button> @onclick="CerrarPoligonoExclusionCaminataAsync">Cerrar zona</button>
} }
else else
{ {
<button type="button" class="btn-ghost" disabled="@(_recalculandoZonas || _zonasExclusionCaminata.Count >= 10)" <button type="button" class="btn-ghost" disabled="@(_recalculandoZonas || _zonasExclusionCaminata.Count >= 10)"
@onclick="IniciarDibujoExclusionCaminataAsync">Añadir zona</button> @onclick="IniciarDibujoExclusionCaminataAsync">@(_nuevaExclusionEsPunto ? "Añadir punto" : "Añadir zona")</button>
<select class="zones-panel__type" aria-label="Tipo de exclusión" @bind="_nuevaExclusionEsPunto" disabled="@_recalculandoZonas">
<option value="false">Polígono</option>
<option value="true">Punto</option>
</select>
} }
<button type="button" class="btn-ghost" disabled="@(_recalculandoZonas || _zonasExclusionCaminata.Count == 0)" <button type="button" class="btn-ghost" disabled="@(_recalculandoZonas || _zonasExclusionCaminata.Count == 0)"
@onclick="LimpiarZonasExclusionAsync">Borrar todas</button> @onclick="LimpiarZonasExclusionAsync">Borrar todas</button>
@@ -2244,3 +2264,37 @@
</div> </div>
} }
@if (_fabricaPendiente is not null || _mensajeFabrica is not null)
{
<div class="custom-modal-backdrop" @onclick="CancelarGuardadoFabrica">
<div class="custom-modal-card fabrica-confirmacion" @onclick:stopPropagation="true" role="dialog" aria-modal="true" aria-label="Guardar valores predeterminados">
<div class="custom-modal-title">Valores predeterminados</div>
@if (_fabricaPendiente is not null)
{
<p>¿Seguro que quieres guardar esta configuración y sustituir los valores predeterminados para todos los usuarios?</p>
<div class="fabrica-cambios">
@foreach (var cambio in _cambiosFabrica)
{
<div><b>@cambio.Campo:</b> @cambio.Antes → @cambio.Despues</div>
}
@if (_cambiosFabrica.Count == 0)
{
<div>Sin cambios respecto a los valores guardados.</div>
}
</div>
}
@if (_mensajeFabrica is not null)
{
<p role="status">@_mensajeFabrica</p>
}
<div class="custom-modal-actions">
<button class="btn-ghost" disabled="@_guardandoFabrica" @onclick="CancelarGuardadoFabrica">@(_fabricaPendiente is null ? "Cerrar" : "Cancelar")</button>
@if (_fabricaPendiente is not null)
{
<button class="btn-ghost" disabled="@_guardandoFabrica" @onclick="ConfirmarGuardadoFabricaAsync">@(_guardandoFabrica ? "Guardando..." : "Guardar y sustituir")</button>
}
</div>
</div>
</div>
}

View File

@@ -42,6 +42,7 @@ public sealed class ParametrosPlanificadorRuta
// Zonas temporales que Valhalla debe excluir al calcular caminatas. // Zonas temporales que Valhalla debe excluir al calcular caminatas.
public List<List<double[]>>? PoligonosExclusionCaminata { get; set; } public List<List<double[]>>? PoligonosExclusionCaminata { get; set; }
public List<double[]>? PuntosExclusionCaminata { get; set; }
// selección candidatas // selección candidatas
public int RadioParadasCandidatasMetros { get; set; } = 1200; public int RadioParadasCandidatasMetros { get; set; } = 1200;

View File

@@ -34,7 +34,8 @@ public class ClienteValhalla : IClienteValhalla
double latitudHasta, double latitudHasta,
double longitudHasta, double longitudHasta,
string costing = "pedestrian", string costing = "pedestrian",
List<List<double[]>>? excludePolygons = null) List<List<double[]>>? excludePolygons = null,
List<double[]>? excludeLocations = null)
{ {
var cuerpo = new SolicitudValhalla var cuerpo = new SolicitudValhalla
{ {
@@ -46,6 +47,8 @@ public class ClienteValhalla : IClienteValhalla
PerfilCoste = string.IsNullOrWhiteSpace(costing) ? "pedestrian" : costing, PerfilCoste = string.IsNullOrWhiteSpace(costing) ? "pedestrian" : costing,
FormatoGeometria = "polyline6", FormatoGeometria = "polyline6",
PoligonosExclusion = excludePolygons, PoligonosExclusion = excludePolygons,
PuntosExclusion = excludeLocations?.Select(p => new LocalizacionValhalla
{ Latitud = p[0], Longitud = p[1] }).ToList(),
OpcionesIndicaciones = new OpcionesIndicacionesValhalla OpcionesIndicaciones = new OpcionesIndicacionesValhalla
{ {
Idioma = "es-ES", Idioma = "es-ES",
@@ -208,8 +211,8 @@ public class ClienteValhalla : IClienteValhalla
if (distanciaMetros <= 0) if (distanciaMetros <= 0)
continue; continue;
var superficie = EtiquetaSuperficie(arista.Superficie, arista.SinPavimentar); var superficie = arista.Superficie ?? "No disponible";
var uso = EtiquetaUso(arista.Uso); var uso = arista.Uso;
if (segmentos.Count > 0 && EsMismaSuperficie(segmentos[^1], superficie, uso, arista.SinPavimentar)) if (segmentos.Count > 0 && EsMismaSuperficie(segmentos[^1], superficie, uso, arista.SinPavimentar))
{ {
@@ -324,6 +327,10 @@ public class ClienteValhalla : IClienteValhalla
[JsonPropertyName("exclude_polygons")] [JsonPropertyName("exclude_polygons")]
public List<List<double[]>>? PoligonosExclusion { get; set; } public List<List<double[]>>? PoligonosExclusion { get; set; }
[JsonPropertyName("exclude_locations")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
public List<LocalizacionValhalla>? PuntosExclusion { get; set; }
[JsonPropertyName("directions_options")] [JsonPropertyName("directions_options")]
public OpcionesIndicacionesValhalla OpcionesIndicaciones { get; set; } = new(); public OpcionesIndicacionesValhalla OpcionesIndicaciones { get; set; } = new();
} }

View File

@@ -15,7 +15,8 @@ public interface IClienteValhalla
double latitudHasta, double latitudHasta,
double longitudHasta, double longitudHasta,
string costing = "pedestrian", string costing = "pedestrian",
List<List<double[]>>? excludePolygons = null); // "pedestrian", "bicycle", etc. List<List<double[]>>? excludePolygons = null,
List<double[]>? excludeLocations = null); // Coordenadas [lat, lon].
/// <summary> /// <summary>
/// Obtiene la superficie y el tipo de uso de los segmentos de una geometría peatonal. /// Obtiene la superficie y el tipo de uso de los segmentos de una geometría peatonal.

View File

@@ -2246,7 +2246,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
origenLatLon[1], origenLatLon[1],
destinoLatLon[0], destinoLatLon[0],
destinoLatLon[1], destinoLatLon[1],
parametros.PoligonosExclusionCaminata); parametros.PoligonosExclusionCaminata, parametros.PuntosExclusionCaminata);
if (valhalla.linea is not null && valhalla.distancia > 0) if (valhalla.linea is not null && valhalla.distancia > 0)
{ {
var duracion = UtilidadesTransitoDbus.EstimarDuracionPieSegundos(valhalla.distancia, parametros); var duracion = UtilidadesTransitoDbus.EstimarDuracionPieSegundos(valhalla.distancia, parametros);
@@ -3835,7 +3835,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
longitudOrigen, longitudOrigen,
latitudDestino, latitudDestino,
longitudDestino, longitudDestino,
parametros.PoligonosExclusionCaminata) parametros.PoligonosExclusionCaminata, parametros.PuntosExclusionCaminata)
: WalkOsrmCached(latitudOrigen, longitudOrigen, latitudDestino, longitudDestino); : WalkOsrmCached(latitudOrigen, longitudOrigen, latitudDestino, longitudDestino);
} }
@@ -3880,20 +3880,23 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
double longitudOrigen, double longitudOrigen,
double latitudDestino, double latitudDestino,
double longitudDestino, double longitudDestino,
List<List<double[]>>? excludePolygons = null) List<List<double[]>>? excludePolygons = null,
List<double[]>? excludeLocations = null)
{ {
if (excludePolygons is { Count: > 0 }) if (excludePolygons is { Count: > 0 } || excludeLocations is { Count: > 0 })
{ {
// Solo reutilizar una geometria conocida y alejada de todas las zonas. // Solo reutilizar una geometria conocida y alejada de todas las zonas.
var claveNormal = BuildWalkKey("valhalla", latitudOrigen, longitudOrigen, latitudDestino, longitudDestino); var claveNormal = BuildWalkKey("valhalla", latitudOrigen, longitudOrigen, latitudDestino, longitudDestino);
if (_cacheCaminatas.TryGetValue(claveNormal, out var normal) && normal.linea is not null && // Un punto se asocia a una arista, que puede extenderse mas alla del punto.
if (excludeLocations is not { Count: > 0 } && excludePolygons is not null &&
_cacheCaminatas.TryGetValue(claveNormal, out var normal) && normal.linea is not null &&
!ExclusionPuedeAfectarTrayecto(normal.linea, excludePolygons)) !ExclusionPuedeAfectarTrayecto(normal.linea, excludePolygons))
{ {
return normal; return normal;
} }
var claveExclusion = BuildWalkKey("valhalla-exclusion", latitudOrigen, longitudOrigen, latitudDestino, longitudDestino) var claveExclusion = BuildWalkKey("valhalla-exclusion", latitudOrigen, longitudOrigen, latitudDestino, longitudDestino)
+ "|" + JsonSerializer.Serialize(excludePolygons); + "|" + JsonSerializer.Serialize(excludePolygons) + "|" + JsonSerializer.Serialize(excludeLocations);
if (_cacheCaminatasConExclusion.TryGetValue(claveExclusion, out var cacheadoConExclusion)) if (_cacheCaminatasConExclusion.TryGetValue(claveExclusion, out var cacheadoConExclusion))
return cacheadoConExclusion; return cacheadoConExclusion;
@@ -3905,7 +3908,7 @@ public sealed class PlanificadorRutasServicio : IPlanificadorRutas
latitudDestino, latitudDestino,
longitudDestino, longitudDestino,
costing: "pedestrian", costing: "pedestrian",
excludePolygons: excludePolygons); excludePolygons: excludePolygons, excludeLocations: excludeLocations);
var valor = (linea, distancia, duracion); var valor = (linea, distancia, duracion);
if (linea is not null && distancia > 0) if (linea is not null && distancia > 0)

View File

@@ -971,7 +971,8 @@ html, body {
.popup-elev-topright.draggable-popup--free, .popup-elev-topright.draggable-popup--free,
.route-panel-overlay.draggable-popup--free, .route-panel-overlay.draggable-popup--free,
.history-panel.draggable-popup--free, .history-panel.draggable-popup--free,
.config-panel.draggable-popup--free { .config-panel.draggable-popup--free,
.zones-panel.draggable-popup--free {
position: fixed !important; position: fixed !important;
left: var(--drag-left) !important; left: var(--drag-left) !important;
top: var(--drag-top) !important; top: var(--drag-top) !important;
@@ -1137,6 +1138,7 @@ html, body {
top: 90px; top: 90px;
left: 16px; left: 16px;
width: min(340px, calc(100vw - 32px)); width: min(340px, calc(100vw - 32px));
max-width: calc(100vw - 32px);
max-height: calc(100dvh - 110px); max-height: calc(100dvh - 110px);
overflow: auto; overflow: auto;
z-index: 1600; z-index: 1600;
@@ -1151,12 +1153,21 @@ html, body {
} }
.zones-panel .config-panel-header h5 { font-size: .94rem; } .zones-panel .config-panel-header h5 { font-size: .94rem; }
@media (max-width: 700px) {
.zones-panel.draggable-popup--free {
left: 16px !important;
top: 72px !important;
max-height: calc(100dvh - 150px) !important;
}
}
.zones-panel h5 small { font-size: .74rem; color: #94a3b8; } .zones-panel h5 small { font-size: .74rem; color: #94a3b8; }
.zones-panel__list { display: grid; gap: .35rem; margin-block: .5rem; } .zones-panel__list { display: grid; gap: .35rem; margin-block: .5rem; }
.zones-panel__item { display: flex; gap: .35rem; min-width: 0; } .zones-panel__item { display: flex; gap: .35rem; min-width: 0; }
.zones-panel__item > :first-child { flex: 1; text-align: left; } .zones-panel__item > button:first-of-type { flex: 1; text-align: left; }
.zones-panel__item > input { flex: 0 0 auto; align-self: center; }
.zones-panel__actions { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-top: .5rem; } .zones-panel__actions { display: flex; flex-wrap: wrap; align-items: center; gap: .4rem; margin-top: .5rem; }
.zones-panel__actions .btn-ghost { white-space: nowrap; } .zones-panel__actions .btn-ghost { white-space: nowrap; }
.zones-panel__type { background: #161d2a; color: #e5e7eb; border: 1px solid #64748b; border-radius: 6px; min-height: 30px; max-width: 110px; font: inherit; }
.zones-panel__icon { padding-inline: .6rem; } .zones-panel__icon { padding-inline: .6rem; }
.btn-ghost.zones-panel__primary { background: #1d4ed8; color: #fff; } .btn-ghost.zones-panel__primary { background: #1d4ed8; color: #fff; }
.btn-ghost.zones-panel__primary:disabled { background: #111827; color: #94a3b8; } .btn-ghost.zones-panel__primary:disabled { background: #111827; color: #94a3b8; }
@@ -1198,8 +1209,9 @@ html, body {
} }
.rt-exclusion-vertex { .rt-exclusion-vertex {
width: 18px !important; width: 10px !important;
height: 18px !important; height: 10px !important;
box-sizing: border-box;
border: 2px solid #fff; border: 2px solid #fff;
border-radius: 50%; border-radius: 50%;
background: #a21caf; background: #a21caf;
@@ -1367,6 +1379,13 @@ html, body {
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.btn-ghost.inspector-walk-eye { display: inline-flex; padding: 2px 5px; min-height: 0; vertical-align: middle; margin-left: 5px; }
.inspector-walk-eye[aria-pressed="true"] { color: #22d3ee; border-color: #22d3ee; }
.fabrica-confirmacion { width: min(560px, calc(100vw - 24px)); max-height: calc(100dvh - 32px); overflow-y: auto; }
.fabrica-cambios { max-height: 45dvh; overflow-y: auto; font-size: .8rem; overflow-wrap: anywhere; }
.fabrica-cambios > div { padding: 5px 0; border-bottom: 1px solid #374151; }
/* LISTA DE TRAMOS TIPO “DIRECCIONES” CON NÚMEROS */ /* LISTA DE TRAMOS TIPO “DIRECCIONES” CON NÚMEROS */
.lista-tramos-ruta { .lista-tramos-ruta {

View File

@@ -36,7 +36,7 @@ window.initZonasShortcut = function (dotNetHelper) {
window.rt._zonasShortcutInicializado = true; window.rt._zonasShortcutInicializado = true;
document.addEventListener("keydown", function (e) { document.addEventListener("keydown", function (e) {
if (e.repeat || e.code !== "KeyZ" || !e.ctrlKey || !e.altKey || e.shiftKey) return; if (e.repeat || e.code !== "KeyZ" || !e.ctrlKey || !e.shiftKey || e.altKey || e.metaKey) return;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
@@ -51,10 +51,11 @@ window.initFabricaShortcut = function (dotNetHelper) {
window.rt._fabricaShortcutInicializado = true; window.rt._fabricaShortcutInicializado = true;
document.addEventListener("keydown", function (e) { document.addEventListener("keydown", function (e) {
if (e.repeat || e.code !== "KeyF" || !e.ctrlKey || !e.altKey || e.shiftKey || e.metaKey) return; if (e.repeat || e.code !== "KeyF" || !e.ctrlKey || !e.shiftKey || e.altKey || e.metaKey) return;
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
document.activeElement?.blur();
dotNetHelper.invokeMethodAsync("GuardarConfiguracionFabricaDesdeAtajoAsync").catch(function () { }); dotNetHelper.invokeMethodAsync("GuardarConfiguracionFabricaDesdeAtajoAsync").catch(function () { });
}, true); }, true);
}; };
@@ -466,7 +467,8 @@ window.rt.drawRoute = function (id, coords, options) {
"Altura: " + elevationText "Altura: " + elevationText
]; ];
if (segment && segment.surfaceText) { if (segment && segment.surfaceText) {
tooltipLines.push("Superficie: " + String(segment.surfaceText)); tooltipLines.push("Superficie (V2): " + String(segment.surfaceText));
tooltipLines.push("LiDAR: No disponible (falta la nube clasificada local)");
} }
return tooltipLines.join("<br>"); return tooltipLines.join("<br>");
}; };
@@ -766,21 +768,25 @@ window.rt.updateExclusionPolygonEditor = function (zones, selectedId, busy) {
const selected = zone.id === selectedId; const selected = zone.id === selectedId;
const points = zone.points.map(p => [p[0], p[1]]); const points = zone.points.map(p => [p[0], p[1]]);
const options = { const options = {
color: selected ? "#e879f9" : "#f97316", weight: selected ? 3 : 2, color: zone.active === false ? "#9ca3af" : selected ? "#e879f9" : "#f97316", weight: selected ? 3 : 2,
fillOpacity: zone.closed ? 0.18 : 0.08, interactive: false dashArray: zone.active === false ? "4 5" : null,
fillOpacity: zone.active === false ? 0.03 : zone.closed ? 0.18 : 0.08, interactive: false
}; };
const polygon = (zone.closed ? L.polygon(points, options) : L.polyline(points, options)).addTo(layer); const polygon = zone.point
? (points.length ? L.circleMarker(points[0], { ...options, radius: 6, fillOpacity: .5 }).addTo(layer) : null)
: (zone.closed ? L.polygon(points, options) : L.polyline(points, options)).addTo(layer);
if (!selected || busy) continue; if (!selected || busy) continue;
points.forEach((point, index) => { points.forEach((point, index) => {
const marker = L.marker(point, { const marker = L.marker(point, {
draggable: true, autoPan: true, bubblingMouseEvents: false, zIndexOffset: 3000, draggable: true, autoPan: true, bubblingMouseEvents: false, zIndexOffset: 3000,
icon: L.divIcon({ className: "rt-exclusion-vertex", html: "", icon: L.divIcon({ className: "rt-exclusion-vertex", html: "",
iconSize: [18, 18], iconAnchor: [9, 9] }) iconSize: [10, 10], iconAnchor: [5, 5] })
}).addTo(layer); }).addTo(layer);
const update = () => { const update = () => {
const pos = marker.getLatLng(); const pos = marker.getLatLng();
points[index] = [pos.lat, pos.lng]; points[index] = [pos.lat, pos.lng];
polygon.setLatLngs(points); if (zone.point) polygon?.setLatLng(points[0]);
else polygon.setLatLngs(points);
return pos; return pos;
}; };
marker.on("drag", update); marker.on("drag", update);
@@ -1761,6 +1767,17 @@ window.rt.clearInspectorMarkers = function () {
} }
}; };
window.rt.setInspectorWalk = function (points, visible) {
window.rt._inspectorWalkLayer?.remove();
window.rt._inspectorWalkLayer = null;
const map = window.__rtLeafletMap;
if (!visible || !map || !window.L || points.length < 2) return;
window.rt._inspectorWalkLayer = window.L.polyline(points, {
color: "#0891b2", weight: 5, dashArray: "2 7", lineCap: "round", interactive: false
}).addTo(map);
if (!points.some(p => map.getBounds().contains(p))) map.panTo(points[0]);
};
window.rt = window.rt || {}; window.rt = window.rt || {};

View File

@@ -50,7 +50,7 @@ var fake = new FakeValhalla();
var planner = new PlanificadorRutasServicio(null!, null!, fake, null!, null!, null!, null!); var planner = new PlanificadorRutasServicio(null!, null!, fake, null!, null!, null!, null!);
var walk = typeof(PlanificadorRutasServicio).GetMethod("WalkValhallaCached", BindingFlags.Instance | BindingFlags.NonPublic)!; var walk = typeof(PlanificadorRutasServicio).GetMethod("WalkValhallaCached", BindingFlags.Instance | BindingFlags.NonPublic)!;
async Task<(double[,]? linea, double distancia, double duracion)> Walk(List<List<double[]>>? zones = null) => async Task<(double[,]? linea, double distancia, double duracion)> Walk(List<List<double[]>>? zones = null) =>
await (Task<(double[,]?, double, double)>)walk.Invoke(planner, new object?[] { 43.31, -1.99, 43.32, -1.98, zones })!; await (Task<(double[,]?, double, double)>)walk.Invoke(planner, new object?[] { 43.31, -1.99, 43.32, -1.98, zones, null })!;
List<double[]> Ring(double lon, double lat, double size) => List<double[]> Ring(double lon, double lat, double size) =>
new() { new[] { lon, lat }, new[] { lon + size, lat }, new[] { lon + size, lat + size }, new() { new[] { lon, lat }, new[] { lon + size, lat }, new[] { lon + size, lat + size },
new[] { lon, lat + size }, new[] { lon, lat } }; new[] { lon, lat + size }, new[] { lon, lat } };
@@ -96,12 +96,17 @@ Console.WriteLine("Pruebas completadas.");
sealed class FakeValhalla : IClienteValhalla sealed class FakeValhalla : IClienteValhalla
{ {
public Task<InformacionSueloTramo?> ObtenerInformacionSueloAsync(
double[,] linea, CancellationToken cancellationToken = default)
=> Task.FromResult<InformacionSueloTramo?>(null);
public int Calls { get; private set; } public int Calls { get; private set; }
public int Zones { get; private set; } public int Zones { get; private set; }
public bool Fail { get; set; } public bool Fail { get; set; }
public Task<(double[,]? Linea, double DistanciaMetros, double DuracionSegundos)> ObtenerRutaAsync( public Task<(double[,]? Linea, double DistanciaMetros, double DuracionSegundos)> ObtenerRutaAsync(
double latitudDesde, double longitudDesde, double latitudHasta, double longitudHasta, double latitudDesde, double longitudDesde, double latitudHasta, double longitudHasta,
string costing = "pedestrian", List<List<double[]>>? excludePolygons = null) string costing = "pedestrian", List<List<double[]>>? excludePolygons = null,
List<double[]>? excludeLocations = null)
{ {
Calls++; Calls++;
Zones = excludePolygons?.Count ?? 0; Zones = excludePolygons?.Count ?? 0;

View File

@@ -14,7 +14,9 @@ const path = require("node:path");
await page.goto("http://127.0.0.1:5199", { waitUntil: "domcontentloaded" }); await page.goto("http://127.0.0.1:5199", { waitUntil: "domcontentloaded" });
await page.waitForFunction(() => !!window.rt?._dotNetHelper && !!window.__rtLeafletMap); await page.waitForFunction(() => !!window.rt?._dotNetHelper && !!window.__rtLeafletMap);
const initialErrors = errors.slice(); const initialErrors = errors.slice();
await page.getByRole("button", { name: "Abrir zonas a evitar", exact: true }).click(); await page.keyboard.press("Control+Shift+KeyZ");
await page.locator(".zones-panel").waitFor();
await page.waitForTimeout(1000);
await page.getByRole("button", { name: "Añadir zona", exact: true }).click(); await page.getByRole("button", { name: "Añadir zona", exact: true }).click();
for (const [x, y] of [[600, 420], [850, 430], [760, 640]]) { for (const [x, y] of [[600, 420], [850, 430], [760, 640]]) {
await page.mouse.click(x, y); await page.mouse.click(x, y);
@@ -24,6 +26,7 @@ const path = require("node:path");
await page.getByRole("button", { name: "Añadir zona", exact: true }).waitFor(); await page.getByRole("button", { name: "Añadir zona", exact: true }).waitFor();
assert.equal(await page.locator(".zones-panel__item").count(), 1); assert.equal(await page.locator(".zones-panel__item").count(), 1);
assert.equal(await page.locator(".rt-exclusion-vertex").count(), 3); assert.equal(await page.locator(".rt-exclusion-vertex").count(), 3);
assert.equal(Math.round((await page.locator(".rt-exclusion-vertex").first().boundingBox()).width), 10);
const polygon = page.locator('path[stroke="#e879f9"]'); const polygon = page.locator('path[stroke="#e879f9"]');
const previousShape = await polygon.getAttribute("d"); const previousShape = await polygon.getAttribute("d");
const marker = await page.locator(".rt-exclusion-vertex").first().boundingBox(); const marker = await page.locator(".rt-exclusion-vertex").first().boundingBox();
@@ -49,9 +52,32 @@ const path = require("node:path");
await page.locator(".zones-panel").waitFor({ state: "detached" }); await page.locator(".zones-panel").waitFor({ state: "detached" });
assert.equal(await page.locator(".rt-exclusion-vertex").count(), 0); assert.equal(await page.locator(".rt-exclusion-vertex").count(), 0);
assert.equal(await page.locator('path[stroke="#e879f9"], path[stroke="#f97316"]').count(), 0); assert.equal(await page.locator('path[stroke="#e879f9"], path[stroke="#f97316"]').count(), 0);
await page.getByRole("button", { name: "Abrir zonas a evitar", exact: true }).click(); await page.keyboard.press("Control+Shift+KeyZ");
await page.locator(".zones-panel").waitFor(); await page.locator(".zones-panel").waitFor();
assert.equal(await page.locator(".zones-panel__item").count(), 2, "Reabrir conserva zonas editables"); assert.equal(await page.locator(".zones-panel__item").count(), 2, "Reabrir conserva zonas editables");
await page.getByRole("checkbox", { name: "Activar zona", exact: true }).first().uncheck();
await page.waitForFunction(() => JSON.parse(localStorage.getItem("rutasdbus.zonas.v1"))[0].Activa === false);
await page.reload();
await page.waitForFunction(() => !!window.rt?._dotNetHelper && !!window.__rtLeafletMap);
await page.waitForTimeout(1000);
await page.keyboard.press("Control+Shift+KeyZ");
await page.locator(".zones-panel__item").first().waitFor();
assert.equal(await page.locator(".zones-panel__item").count(), 2, "F5 conserva zonas");
assert.equal(await page.getByRole("checkbox", { name: "Activar zona", exact: true }).first().isChecked(), false);
const panelBefore = await page.locator(".zones-panel").boundingBox();
const header = await page.locator(".zones-panel .draggable-popup-handle").boundingBox();
await page.mouse.move(header.x + 40, header.y + 15);
await page.mouse.down();
await page.mouse.move(header.x + 190, header.y + 65, { steps: 10 });
await page.mouse.up();
const panelAfter = await page.locator(".zones-panel").boundingBox();
assert.ok(Math.abs(panelAfter.x - panelBefore.x) > 50, "El modal se arrastra");
await page.screenshot({ path: path.join(artifacts, "persistencia-arrastre.png") });
await page.keyboard.press("Control+Shift+KeyF");
await page.getByRole("dialog", { name: "Guardar valores predeterminados" }).waitFor();
assert.ok(await page.getByRole("button", { name: "Guardar y sustituir", exact: true }).isVisible());
await page.screenshot({ path: path.join(artifacts, "confirmacion-fabrica.png") });
await page.getByRole("button", { name: "Cancelar", exact: true }).click();
await page.getByRole("button", { name: "Eliminar zona", exact: true }).first().click(); await page.getByRole("button", { name: "Eliminar zona", exact: true }).first().click();
await page.waitForFunction(() => document.querySelectorAll(".zones-panel__item").length === 1); await page.waitForFunction(() => document.querySelectorAll(".zones-panel__item").length === 1);
assert.equal(await page.locator(".zones-panel__item").count(), 1); assert.equal(await page.locator(".zones-panel__item").count(), 1);